No es escalabilidad, es ansiedad: PostgreSQL con 10 millones de filas
Durante las últimas semanas me ha tocado entrevistar a muchos ingenieros, y en esas conversaciones me ha llamado mucho la atención que muchos sienten que una tabla con 10.000 filas es demasiado para una base de datos. Basados en esa hipótesis, plantean ideas para resolver el problema y suelen ser las mismas: escalar la base de datos (sharding), crear microservicios, y otras alternativas más complejas que hacen que sus aplicaciones sean súper complejos de entender (y de mantener).
Tener la misma conversación muchas veces me hizo cuestionarme: de verdad PostgreSQL se complica con 10.000 filas?. No me haría sentido que un software hecho para trabajar con datos tenga una capacidad tan limitada. Pero me gusta el dicho "dato mata relato" así que decidí hacer una pequeña prueba: un experimento simple para ver con datos en mano hasta dónde aguanta una base de datos local sin problemas.
Objetivo
Evaluar cómo varía el tiempo de ejecución de una consulta simple en PostgreSQL a medida que aumenta la cantidad de filas en una tabla. La idea es comprobar si el tamaño de la tabla realmente impacta en la performance (como muchos creen) o si, en la práctica, el efecto es mínimo.
Metodología
Hice el experimento en mi computador, un Thinkpad del año 2017 con un procesador Intel i5-6300U y 20 GB de memoria RAM. Corriendo PostgreSQL versión 18 en Arch Linux (y otras aplicaciones abiertas, incluyendo Chrome con 14 pestañas abiertas).
Creé una tabla sencilla:
Tener la misma conversación muchas veces me hizo cuestionarme: de verdad PostgreSQL se complica con 10.000 filas?. No me haría sentido que un software hecho para trabajar con datos tenga una capacidad tan limitada. Pero me gusta el dicho "dato mata relato" así que decidí hacer una pequeña prueba: un experimento simple para ver con datos en mano hasta dónde aguanta una base de datos local sin problemas.
Objetivo
Evaluar cómo varía el tiempo de ejecución de una consulta simple en PostgreSQL a medida que aumenta la cantidad de filas en una tabla. La idea es comprobar si el tamaño de la tabla realmente impacta en la performance (como muchos creen) o si, en la práctica, el efecto es mínimo.
Metodología
Hice el experimento en mi computador, un Thinkpad del año 2017 con un procesador Intel i5-6300U y 20 GB de memoria RAM. Corriendo PostgreSQL versión 18 en Arch Linux (y otras aplicaciones abiertas, incluyendo Chrome con 14 pestañas abiertas).
Creé una tabla sencilla:
CREATE TABLE test_performance ( id SERIAL PRIMARY KEY, nombre TEXT, email TEXT, monto NUMERIC, fecha TIMESTAMP DEFAULT now() );
Luego fui llenando la tabla con distintos volúmenes de datos usando generate_series():
INSERT INTO test_performance (nombre, email, monto) SELECT md5(random()::text), md5(random()::text) || '@example.com', random() * 1000 FROM generate_series(1, N);
Probé con N = 10.000, 100.000, 1.000.000 y 10.000.000.
Después ejecuté las mismas 2 consultas en cada caso. La primera es un COUNT que obliga a hacer un procesamiento adicional en la base de datos y luego un SELECT que simplemente obtiene datos.
Count:
SELECT COUNT(*) FROM test_performance WHERE monto > 500;
Select:
SELECT nombre, email, monto FROM test_performance WHERE monto > 800 ORDER BY monto DESC LIMIT 10;
Para medir el tiempo, usé \timing on en psql y tomé el promedio de tres ejecuciones.
El proceso anterior lo repetí dos veces: una vez sin ningún tipo de optimización en la base de datos y luego utilicé un índice en la columna monto para ver cómo varían los resultados. El índice lo creé de la siguiente manera:
CREATE INDEX idx_monto ON test_performance (monto);
Resultados
Sin índice:
10.000 filas COUNT: 12.040 ms SELECT: 19.179 ms 100.000 filas COUNT: 71.841 ms SELECT: 99.996 ms 1.000.000 filas COUNT: 401.677 ms SELECT: 348.940 ms 10.000.000 filas COUNT: 2085.249 ms SELECT: 2497.200 ms
Con índice:
10.000 filas COUNT: 9.922 ms SELECT: 1.290 ms 100.000 filas COUNT: 36.927 ms SELECT: 1.236 ms 1.000.000 filas COUNT: 380.938 ms SELECT: 2.793 ms 10.000.000 filas COUNT: 1356.059 ms SELECT: 1.035 ms
Análisis
Como se puede ver en los resultados, el tiempo de ejecución crece de manera estable, sin saltos abruptos en ambos casos. Incluso con un millón de filas, las consultas se mantienen bajo medio segundo. Recién al llegar a los 10 millones de filas se observa una diferencia más perceptible, pero sigue siendo una respuesta en el rango de los 2 segundos.
En la primera prueba, sin ningún tipo de optimización ni índice, PostgreSQL se comporta con total normalidad. Pero luego, cuando agregué un índice sobre la columna monto, los tiempos cambiaron por completo: los SELECT bajaron drásticamente sus tiempos de ejecución. Por otro lado el COUNT(*) no mejoró demasiado porque los índices sirven para acceder más rápido a un subconjunto de datos, pero no para contar todos los registros. Cuando ejecutamos el COUNT(*) la base de datos sigue teniendo que recorrer gran parte de la tabla.
En ambos casos, lo que cambia es la estrategia interna del motor de PostgreSQL: cuando implementamos índices, lo que está pasando internamente es que pasa de hacer sequential scans (leer toda la tabla fila por fila) a index scans (leer solo lo necesario y en el orden correcto).
Conclusión
Con este pequeño ejercicio podemos ver que PostgreSQL maneja millones de registros sin problema, incluso sin aplicar ningún tipo de optimización. Y con un índice bien puesto, las diferencias son enormes.
No necesitamos más capas, más servicios ni más complejidad, solo un poco más de confianza en las herramientas que ya tenemos.