- JavaScript
- Performance
- Backend
- Optimización
- Big Data
Cuando trabajas con grandes volúmenes de datos en JavaScript (cientos de miles o millones de registros), iterar de forma incorrecta puede romper tu aplicación: consumo excesivo de memoria, bloqueos del event loop y tiempos de respuesta inaceptables.
En este artículo vamos directo a lo importante: cómo iterar millones de registros de forma eficiente.
El problema real
Un ejemplo típico:
const data = new Array(1_000_000).fill(0);
data.forEach(item => {
// procesamiento pesado
});
Esto parece inocente, pero tiene varios problemas:
- Bloquea el hilo principal
- No permite pausas (event loop bloqueado)
- Puede consumir mucha memoria
- No es controlable
Métodos de iteración y su impacto
for clásico (el más rápido)
for (let i = 0; i < data.length; i++) {
const item = data[i];
}
Ventajas:
- Más rápido
- Menor overhead
- Control total del índice
Ideal para procesamiento intensivo.
for...of
for (const item of data) {
}
Ventajas:
- Más limpio
- Legible
Desventaja:
- Ligeramente más lento que el for tradicional
forEach (evitar en grandes volúmenes)
data.forEach(item => {});
Problemas:
- No puedes usar break o continue
- Menor rendimiento
- Difícil de controlar
Procesamiento por bloques (la clave)
La mejor estrategia para millones de registros es dividir el trabajo.
const chunkSize = 1000;
for (let i = 0; i < data.length; i += chunkSize) {
const chunk = data.slice(i, i + chunkSize);
chunk.forEach(item => {
// procesar
});
}
Ventajas:
- Controlas memoria
- Evitas bloqueos largos
- Más escalable
Liberar el event loop (muy importante)
Si estás en frontend o Node.js, necesitas evitar bloquear el hilo:
const processChunk = async (data, chunkSize = 1000) => {
for (let i = 0; i < data.length; i += chunkSize) {
const chunk = data.slice(i, i + chunkSize);
for (let j = 0; j < chunk.length; j++) {
// procesar
}
await new Promise(resolve => setTimeout(resolve, 0));
}
};
Esto permite que el sistema respire.
Streams (nivel pro)
Para datos realmente grandes (archivos, DB, etc):
const fs = require('fs');
const readline = require('readline');
const stream = fs.createReadStream('file.txt');
const rl = readline.createInterface({
input: stream,
});
rl.on('line', (line) => {
// procesar línea por línea
});
Ventajas:
- No cargas todo en memoria
- Escalable
- Ideal para millones de registros
Paralelismo (cuando aplica)
const promises = data.map(item => asyncProcess(item));
await Promise.all(promises);
Cuidado:
- Puede saturar CPU/memoria
- Mejor usar límites de concurrencia
Ejemplo controlado:
import pLimit from 'p-limit';
const limit = pLimit(10);
await Promise.all(
data.map(item => limit(() => asyncProcess(item)))
);
Recomendaciones prácticas
Usa esto
- for clásico para rendimiento máximo
- procesamiento por chunks
- streams para archivos grandes
- control de concurrencia
Evita esto
- forEach en datasets enormes
- cargar todo en memoria sin necesidad
- Promise.all sin límites
Conclusión
Iterar millones de registros en JavaScript no es solo recorrer arrays, es un problema de arquitectura.
La diferencia entre una app que escala y una que colapsa está en:
- Cómo iteras
- Cuánta memoria usas
- Cómo manejas el event loop
Si trabajas con grandes volúmenes de datos, dominar estas técnicas no es opcional.
Optimizar iteraciones no es micro-optimización, es supervivencia en producción.
