¿Cómo escalar sus aplicaciones Node.js?

Jan 25 2023
Veamos un breve resumen sobre cómo escalar uno de los marcos de trabajo más populares en la actualidad para crear aplicaciones. Entonces, comencemos con la motivación: ¿cómo crear una API de buen rendimiento y replicar esa lógica para los usuarios de X? Tenemos tres formas de escalar nuestra aplicación: Vertical, Horizontal y mixta (vertical y horizontal).

Veamos un breve resumen sobre cómo escalar uno de los marcos de trabajo más populares en la actualidad para crear aplicaciones.

Entonces, comencemos con la motivación: ¿ cómo crear una API de buen rendimiento y replicar esa lógica para los usuarios de X?

Tenemos tres formas de escalar nuestra aplicación: Vertical , Horizontal y mixta (vertical y horizontal).

Escalado horizontal

El escalado horizontal significa que la mayoría de las veces se duplican los servidores o nodos. Con esta réplica o réplicas, necesitaremos un balanceador de carga, para poder enviarles todas las solicitudes de forma equitativa.

Tenemos que tener en cuenta que esta solución viene con complejidad al implementar la aplicación porque el nuevo balanceador de carga debe estar bien configurado, de lo contrario, todo el tráfico podría ir a un servidor y perderíamos dinero.

Escalado vertical

Vertical significa agregar más potencia de cómputo o RAM/discos.

Esto es bastante sencillo, si su computadora en la nube se ejecuta en una máquina de 16 gb de RAM y 16 núcleos/32 subprocesos, dupliquemos eso y puede proporcionar el doble de usuarios .

Pero para ello tenemos que tener en cuenta que en algún momento tendremos un límite, además el precio aumentará exponencialmente en las últimas configuraciones disponibles.

Entonces, ¡comencemos!

Este será un maletín corto para Escalado horizontal .

Un comienzo fácil sería utilizar el módulo de clúster .

Con este módulo podemos crear procesos secundarios que comparten todos los puertos del servidor. ¡Basta de palabras, aquí viene el código!

import cluster from 'node:cluster';
import http from 'node:http';
import { availableParallelism } from 'node:os';
import process from 'node:process';

const numCPUs = availableParallelism();

if (cluster.isPrimary) {
  console.log(`Primary ${process.pid} is running`);

  // Fork workers.
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }

  cluster.on('exit', (worker, code, signal) => {
    console.log(`worker ${worker.process.pid} died`);
  });
} else {
  // Workers can share any TCP connection
  // In this case it is an HTTP server
  http.createServer((req, res) => {
    res.writeHead(200);
    res.end('hello world\n');
  }).listen(8000);

  console.log(`Worker ${process.pid} started`);
}

$ node server.js
Primary 3596 is running
Worker 4324 started
Worker 4520 started
Worker 6056 started
Worker 5644 started

Para entender un poco mejor esto de los trabajadores , hagamos un ejemplo:

Lea un archivo json grande y para cada línea y para cada objeto dentro de él, convierta esa información a XML.

Primero el código sin subprocesos de trabajo =>

/* syncMode.js */

// Read all JSON file contents into an array
const contents = getContents()

/* Execution time start */
const start = hrtime.bigint()

// Convert each JSON file content to XML format
const result = contents.map((content) => {
  content = JSON.parse(content)
  return js2xmlparser.parse('user', content)
})

/* Execution time end */
const end = hrtime.bigint()
console.info(`Execution time: ${(end - start) / BigInt(10 ** 6)}ms`)

Digamos que tenemos un directorio con 1000 archivos JSON y tenemos la tarea de convertir cada archivo JSON en un archivo XML.

Cada archivo JSON tiene una matriz de 1000 objetos, y cada objeto tiene 6 pares clave-valor, como se muestra en el siguiente ejemplo.

{
  "id": 1,
  "first_name": "abc",
  "last_name": "xyz",
  "email": "[email protected]",
  "gender": "Male",
  "ip_address": "163.213.59.129"
}

$ node syncMode.js
Execution time: 1036ms

Para administrar los subprocesos de trabajo más fácilmente, usaremos una biblioteca llamada Piscina , ¡que es perfecta para el trabajo!

/* ThreadMode.js */

const Piscina = require('piscina')

// Read all JSON file contents into an array
const contents = getContents()

/* Execution time start */

// Divide the content into two chunks
const chunks = splitToChunks(contents, 2)

// Create a new thread pool
const pool = new Piscina()
const options = { filename: resolve(__dirname, 'worker-pool') }

// Run operation on the chunks parallely
const result = await Promise.all([pool.run(chunks[0], options), pool.run(chunks[1], options)])

/* Execution time end */

$node ThreadMode.js
Execution time: 687ms

¡ Vimos un aumento de rendimiento increíble de más del 66%! con sólo un par de líneas. Aquí es un caso muy específico, pero comenzamos a ver cómo aumentar el rendimiento en Node.js usando funcionalidades integradas.

En los próximos pasos, usaremos el equilibrio de carga para distribuir la carga en varias API en el nodo con Docker, ¡así que manténgase al tanto!