En un VPS, las bases de datos no fallan de forma escandalosa. Fallan de forma costosa. Primero es «el sitio se siente lento», luego es «necesitamos una instancia más grande», y finalmente alguien descubre en voz baja que pagabas por RAM que tu base de datos no podía usar eficazmente y por IOPS que nunca probaste.
Esta es la realidad del VPS: vecinos ruidosos, créditos por ráfaga, latencia de almacenamiento inconsistente y un techo presupuestario rígido. En ese mundo, «velocidad por dólar» no es una captura de benchmark. Es la capacidad de mantener la latencia p95 predecible mientras tu director financiero te mantiene con límites.
Qué significa realmente “velocidad por dólar” en un VPS
La gente dice «rendimiento» como si fuera un solo número. En un VPS es un montón de compensaciones y modos de fallo. Si quieres velocidad por dólar, necesitas definir la «velocidad» que estás comprando:
- Latencia predecible (p95/p99), no solo rendimiento medio.
- Eficiencia por vCPU cuando la CPU es mediocre y compartida.
- Amplificación de escritura y comportamiento de fsync cuando el almacenamiento es el factor limitante.
- Coste operativo: con qué frecuencia llamas a alguien porque el mantenimiento quedó atrás.
MariaDB y PostgreSQL pueden ser rápidos. La diferencia es cómo se comportan cuando el VPS no lo es. PostgreSQL tiende a ser más honesto respecto al trabajo que debe hacer (visibilidad, VACUUM, WAL). MariaDB/InnoDB puede ser asombrosamente tolerante para cargas OLTP con muchas lecturas, pero también puede ocultar problemas hasta el día en que ya no puede más.
La respuesta breve y con opinión (con matices)
Si estás en un VPS modesto y quieres la mayor velocidad por dólar para OLTP típico de SaaS (muchas lecturas/escrituras pequeñas, joins simples, muchas sesiones concurrentes), PostgreSQL suele ganar a largo plazo porque se mantiene consistente bajo cargas desordenadas y te ofrece mejores herramientas para diagnosticar y controlar el sistema.
Si tu carga es muy lectora, de esquema simple y necesitas buen rendimiento con mínimo ajuste, MariaDB puede ser más barato de operar en hardware pequeño—especialmente si puedes mantener la mayor parte de los datos calientes en el InnoDB buffer pool y evitar alta contención de escrituras.
El matiz importante: en muchos proveedores VPS, la variabilidad de la latencia de almacenamiento domina. En ese caso, la «base de datos más rápida» es la que puedes configurar para evitar bloqueos síncronos sin sacrificar corrección. PostgreSQL tiene valores predeterminados orientados a la corrección; MariaDB puede ajustarse para ser aterradoramente rápido relajando la durabilidad, lo cual puede estar bien—hasta que no lo está.
Regla práctica: si apuestas la compañía a que la base de datos sea aburrida, elige PostgreSQL. Si apuestas por exprimir costes y tus requisitos de durabilidad son negociables, MariaDB puede ser el coche de carreras más barato.
Broma #1: Un VPS es como un espacio de coworking: el café está bien, el Wi‑Fi es «compartido» y siempre hay alguien ejecutando un minero de crypto a dos escritorios.
Hechos e historia que aún afectan tu ajuste
Esto no son datos para la noche de trivial. Explican por qué existen ciertos controles, por qué los valores predeterminados parecen conservadores y por qué las migraciones a veces se sienten como mudarse durante un huracán.
- El diseño MVCC de PostgreSQL (control de concurrencia de múltiples versiones) ha sido central desde los años 90. Es la razón por la que las lecturas no bloquean las escrituras y por qué VACUUM es una preocupación operativa real.
- InnoDB se convirtió en el motor por defecto de MySQL a finales de los 2000 porque ofrecía transacciones, bloqueo a nivel de fila y recuperación ante fallos que la gente necesitaba en producción.
- MariaDB se bifurcó de MySQL tras la adquisición de Sun por parte de Oracle. Ese evento corporativo es la razón por la que «reemplazo drop-in» fue un punto de venta y por qué algunas esquinas de compatibilidad siguen siendo filosas.
- La replicación de PostgreSQL maduró a partir del envío de WAL (write-ahead log). La historia de replicación está ligada a WAL, fsync y ajuste de checkpoints—núcleo de la «velocidad por dólar» en discos lentos.
- La caché de consultas de MySQL fue eliminada (y la historia de MariaDB varía según la versión). Esta historia explica por qué verás consejos antiguos que ahora son dañinos para la concurrencia.
- La planificación de consultas de PostgreSQL tiene una cultura de corrección sobre «ingeniosidad» sorpresa. No siempre será el plan más rápido, pero tiende a ser explicable, y eso importa a las 3 a.m.
- El buffer de doble escritura de InnoDB existe porque ocurren escrituras parciales de página. En almacenamiento VPS poco fiable, esa protección puede salvarte—con el coste de sobrecarga de escritura.
- PostgreSQL ha adoptado extensiones (como pg_stat_statements) como herramientas operativas de primera clase. Eso cambia dramáticamente la experiencia diaria de depuración.
Dónde los VPS dañan a las bases de datos (y por qué cambia la elección)
1) El almacenamiento es el recaudador de impuestos
En un VPS puedes comprar más vCPU y más RAM, y la base de datos seguirá bloqueándose en fsync. El punto no es «SSD vs HDD». El punto es la latencia de cola: unas pocas sincronizaciones lentas por segundo pueden arruinar tu p99. PostgreSQL y MariaDB dependen de logs de escritura adelantada (WAL / redo logs). En el momento en que tu dispositivo de logs hace un tic, tu base de datos se convierte en un sistema de colas con sentimientos.
2) El tiempo de “steal” de CPU es real
La virtualización puede robar ciclos de CPU (literalmente expuesto como steal time). Si tu instancia es barata, los hilos de la base de datos pueden ejecutarse a ráfagas. Eso perjudica a PostgreSQL cuando tienes demasiadas conexiones activas y cambios de contexto. Perjudica a MariaDB cuando los hilos de purge/flush no pueden mantenerse al día y se acumulan páginas sucias.
3) La memoria es oro y trampa a la vez
La RAM es el multiplicador de rendimiento más barato—hasta que la sobreasignas. PostgreSQL usa memoria por conexión para sorts/hashes; MariaDB también usa buffers por hilo, pero el buffer pool de InnoDB es la palanca principal. En VPS pequeños, una configuración de memoria equivocada puede desencadenar intercambio, y entonces estarás midiendo el subsistema de VM de Linux, no tu base de datos.
4) La latencia de red distorsiona la velocidad percibida
Muchos reportes de «base de datos lenta» son en realidad variación de red entre la app y la BD. PostgreSQL y MariaDB responden de forma similar: la consulta es rápida; la petición no lo es. Si tu app y BD no están co‑localizadas, «velocidad por dólar» se convierte en «latencia por arrepentimiento».
MariaDB en VPS: en qué destaca
Cuando MariaDB tiende a ganar en coste
- OLTP con muchas lecturas y muchas búsquedas puntuales pequeñas (clave primaria / lecturas indexadas).
- Esquemas simples donde no dependes de funciones SQL complejas.
- Equipos con experiencia en MySQL que pueden operarlo con seguridad sin ajustes «creativos».
- Cargas con patrones de acceso predecibles donde el buffer pool mantiene las páginas calientes residentes.
Los controles que importan (y por qué)
En un VPS, el rendimiento de MariaDB vive y muere por InnoDB:
- innodb_buffer_pool_size: hazlo lo bastante grande para contener tu conjunto de trabajo caliente. Si es demasiado pequeño, pagas IOPS de lectura aleatoria. Si es demasiado grande, el kernel empieza a reclamar agresivamente y obtienes paradas.
- innodb_flush_log_at_trx_commit: el valor por defecto es lo más seguro (1). Bajarlo (2 o 0) compra velocidad aceptando pérdida de datos ante un fallo. Esto no es una «optimización gratis», es una decisión de negocio.
- innodb_log_file_size / innodb_redo_log_capacity: logs más grandes reducen la presión de checkpoints y suavizan escrituras. Demasiado grandes y el tiempo de recuperación crece; demasiado pequeños y chocas checkpoints y golpeas el almacenamiento.
- innodb_io_capacity: decirle a InnoDB lo que tu almacenamiento realmente puede hacer evita que sea perezoso o frenético.
Donde MariaDB puede perderte dinero silenciosamente
MariaDB puede parecer rápida mientras acumula deuda: páginas sucias acumulándose, retraso de purge, lag de replicación que está «bien» hasta que ocurre un failover, o una sola tabla caliente que se convierte en un festival de mutex. No siempre lo ves hasta que llegas al borde.
Además, la divergencia entre versiones y forks importa. Una configuración en VPS estable para una versión mayor puede volverse extraña tras una actualización, especialmente alrededor de elecciones del optimizador y ajustes predeterminados.
PostgreSQL en VPS: en qué destaca
Cuando PostgreSQL tiende a ganar en coste
- Cargas mixtas: lecturas, escrituras, consultas tipo analítica y «el producto decidió que necesitamos reporting» en la misma base de datos.
- Consultas complejas donde la calidad del planner y las opciones de indexación importan.
- Claridad operativa: estadísticas integradas y una cultura de introspección.
- Mantenibilidad a largo plazo: semántica predecible, menos «depende del motor».
Los controles que importan (y por qué)
El ajuste de PostgreSQL en un VPS trata principalmente de: (1) evitar sobreasignación de memoria, (2) suavizar WAL/checkpoints, (3) mantener VACUUM sano.
- shared_buffers: no «tan grande como sea posible». En Linux, demasiado grande puede privar la caché del sistema de archivos y perjudicar. Usualmente 15–25% de la RAM es un comienzo sensato en un VPS.
- work_mem: por ordenación/hash, por nodo, por consulta. Si lo pones a lo héroe, tu máquina hará OOM como villano.
- effective_cache_size: le dice al planner qué caché es probable que esté disponible (OS cache + shared_buffers). No asigna memoria, pero cambia planes.
- checkpoint_timeout, max_wal_size, checkpoint_completion_target: estos pueden convertir «picos periódicos de latencia» en «aburrido». Aburrido es bueno.
- autovacuum settings: autovacuum es tu compañero de piso que limpia. Si lo desactivas, disfrutarás la vibra hasta que la basura llegue al techo.
Dónde PostgreSQL puede malgastar tu presupuesto
La mayor trampa de coste es la gestión de conexiones. Miles de conexiones cliente en un VPS pequeño harán que Postgres gaste dinero en cambios de contexto y memoria, no en consultas. Si no usas un pooler, comprarás una instancia más grande solo para alojar conexiones inactivas. Eso no es «escalar». Eso es subvencionar malas prácticas.
Broma #2: Desactivar autovacuum porque «usa CPU» es como quitar el detector de humo porque hace ruido.
Una cita que mapea la vida en producción
Werner Vogels (CTO de Amazon) dijo: «Todo falla todo el tiempo.»
Guion rápido de diagnóstico
Cuando el rendimiento cae en un VPS, tu trabajo no es adivinar. Tu trabajo es localizar el cuello de botella rápido y decidir si estás limitado por CPU, memoria, E/S, o sufres contención y encolamiento.
Primero: confirma la forma del síntoma
- Patrón de picos de latencia: periódico (checkpoints, vacuum, backups) vs sostenido (saturación de I/O, contención de locks).
- Alcance: una consulta/tabla vs todo.
- Tiempo de recuperación: segundos (ráfaga) vs horas (deuda de mantenimiento).
Segundo: decide cuál es el limitador primario
- Almacenamiento: alto iowait, tiempos fsync altos, pocas IOPS, colas largas en disco.
- CPU: alto user/sys, alto steal, cola de ejecución > núcleos.
- Memoria: swapping, thrash de la page cache, OOM kills, fallos por conjunto de trabajo grande.
- Bloqueos/contención: esperas por locks, contenciones de buffer, demasiadas conexiones.
Tercero: elige la clase de «arreglo» adecuada
- Si estás limitado por almacenamiento: suavizar checkpoints/WAL/redo, evitar tormentas de fsync, reducir lecturas aleatorias mediante caché/indexación, considerar volumen separado para logs.
- Si estás limitado por CPU: reducir concurrencia, arreglar consultas lentas, añadir índices, evitar funciones por fila, ajustar paralelismo con cuidado.
- Si estás limitado por memoria: redimensionar buffers, reducir work_mem / buffers por hilo, implementar pooling, detener el swapping.
- Si estás limitado por contención: duración de transacciones, filas calientes, orden de locks, nivel de aislamiento, mejores índices, agrupar escrituras.
12+ tareas prácticas: comandos, salidas, decisiones
Estas son las tareas que realmente ejecutas en un VPS cuando intentas comprar velocidad con ingeniería en lugar de con la tarjeta de crédito. Cada tarea incluye un comando realista, un esbozo de salida, qué significa la salida y la decisión que tomas.
Task 1: Check CPU steal and iowait (is the VPS lying to you?)
cr0x@server:~$ mpstat -P ALL 1 5
Linux 6.1.0 (vps01) 12/29/2025 _x86_64_ (4 CPU)
12:00:01 AM CPU %usr %nice %sys %iowait %irq %soft %steal %idle
12:00:01 AM all 22.10 0.00 6.12 18.33 0.00 0.55 8.40 44.50
...
Significado: %steal alrededor de 8% significa que tus «4 vCPU» no son tuyos cuando los necesitas. %iowait cercano a 18% indica que los stalls de almacenamiento están dominando.
Decisión: No ajustes SQL primero. Empieza por suavizar I/O (WAL/checkpoints/redo) y reduce escritores concurrentes. Si el steal sigue alto, considera otra clase de VPS/proveedor antes de comprar más núcleos.
Task 2: Find disk queueing and latency (the “why is p99 awful” check)
cr0x@server:~$ iostat -x 1 5
Linux 6.1.0 (vps01) 12/29/2025 _x86_64_
Device r/s w/s rkB/s wkB/s await aqu-sz %util
vda 85.0 60.0 3400.0 5200.0 22.5 1.80 98.0
...
Significado: await 22.5ms y %util 98% indican almacenamiento saturado con una cola persistente (aqu-sz ~1.8).
Decisión: Estás limitado por I/O. Prioriza reducir la frecuencia de fsyncs, incrementar la tasa de aciertos de caché y eliminar escrituras innecesarias (índices, logs verbosos). Considera mover WAL/redo a un almacenamiento más rápido si es posible.
Task 3: Confirm filesystem and mount options (barriers, atime, discard)
cr0x@server:~$ findmnt -no SOURCE,FSTYPE,OPTIONS /var/lib
/dev/vda1 ext4 rw,relatime,errors=remount-ro
Significado: ext4 con relatime es normal. Si ves opciones exóticas o sistemas de archivos en red, para y reconsidera tus decisiones de vida.
Decisión: Manténlo sencillo. Si atime está habilitado (en lugar de relatime/noatime), considera noatime para cargas de solo lectura intensas. Evita «tunear» opciones de montaje que no entiendas bien; la base de datos ya hace la lógica de durabilidad.
Task 4: Detect swapping and memory pressure
cr0x@server:~$ free -h
total used free shared buff/cache available
Mem: 8.0Gi 6.9Gi 120Mi 180Mi 1.0Gi 420Mi
Swap: 2.0Gi 1.6Gi 420Mi
Significado: Estás intercambiando fuertemente; «available» es bajo. La latencia de la base de datos se volverá no lineal.
Decisión: Reduce configuraciones de memoria ahora (Postgres work_mem/shared_buffers; buffer pool de MariaDB + buffers por hilo). Considera añadir RAM solo después de confirmar que no hay fugas de memoria por tormentas de conexiones.
Task 5: Check per-process memory and OOM kills
cr0x@server:~$ dmesg -T | tail -n 8
[Mon Dec 29 00:10:12 2025] Out of memory: Killed process 2113 (postgres) total-vm:5242880kB, anon-rss:3100000kB, file-rss:0kB, shmem-rss:0kB
[Mon Dec 29 00:10:12 2025] oom_reaper: reaped process 2113 (postgres), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB
Significado: El kernel mató un backend de PostgreSQL. Eso es adyacente a corrupción de datos si ocurre en el momento equivocado, y absolutamente arruinará tu lunes.
Decisión: Arregla la configuración de memoria y pon pooling de conexiones de inmediato. No culpes al planner; esto es disciplina básica de capacidad.
Task 6: PostgreSQL—find top queries by total time (stop guessing)
cr0x@server:~$ sudo -u postgres psql -d appdb -c "SELECT queryid, calls, total_exec_time::bigint AS total_ms, mean_exec_time::numeric(10,2) AS mean_ms, rows, left(query, 80) AS q FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 5;"
queryid | calls | total_ms | mean_ms | rows | q
----------+-------+----------+---------+------+-----------------------------------------------
91230123 | 12000 | 980000 | 81.67 | 120 | SELECT * FROM orders WHERE user_id=$1 ORDER BY created_at DESC LIMIT $2
11223344 | 1500 | 410000 | 273.33 | 1 | UPDATE inventory SET qty=qty-$1 WHERE sku=$2
...
Significado: Tienes un pequeño número de consultas dominantes. La primera probablemente necesita un índice compuesto. El update puede ser contención por fila caliente.
Decisión: Optimiza las 1–3 consultas principales antes de tocar ajustes globales. Índices y reescrituras de consultas superan a «más shared_buffers» la mayoría de las veces.
Task 7: PostgreSQL—check what you’re waiting on (locks vs I/O vs CPU)
cr0x@server:~$ sudo -u postgres psql -d appdb -c "SELECT wait_event_type, wait_event, count(*) FROM pg_stat_activity WHERE state='active' GROUP BY 1,2 ORDER BY 3 DESC;"
wait_event_type | wait_event | count
-----------------+---------------------+-------
IO | DataFileRead | 12
Lock | relation | 4
LWLock | buffer_content | 3
Significado: Mayormente esperando DataFileRead = estás perdiendo caché (o haciendo muchas lecturas aleatorias). Algunas esperas por locks indican contención, pero no es el problema dominante.
Decisión: Aumenta la tasa de aciertos de caché mediante índices, reducir bloat de tablas (vacuum) y dimensionar shared_buffers/effective_cache_size correctamente. Para esperas por locks, acorta transacciones y elimina patrones de hotspots.
Task 8: PostgreSQL—measure cache hit ratio, but don’t worship it
cr0x@server:~$ sudo -u postgres psql -d appdb -c "SELECT datname, blks_hit, blks_read, round(100.0*blks_hit/nullif(blks_hit+blks_read,0),2) AS hit_pct FROM pg_stat_database WHERE datname='appdb';"
datname | blks_hit | blks_read | hit_pct
---------+----------+-----------+---------
appdb | 92000000 | 7800000 | 92.17
Significado: 92% no es terrible, pero en un VPS con almacenamiento mediocre, ese 8% aún puede ser tu p99.
Decisión: Si no puedes mejorar índices/consultas, añade RAM (o reduce el tamaño del dataset) para mejorar el ratio de aciertos. También revisa scans secuenciales en tablas grandes.
Task 9: PostgreSQL—spot autovacuum debt and bloat candidates
cr0x@server:~$ sudo -u postgres psql -d appdb -c "SELECT relname, n_live_tup, n_dead_tup, last_autovacuum FROM pg_stat_user_tables ORDER BY n_dead_tup DESC LIMIT 5;"
relname | n_live_tup | n_dead_tup | last_autovacuum
----------------+------------+------------+--------------------------
events | 8200000 | 1900000 | 2025-12-28 21:14:09+00
sessions | 1500000 | 420000 | 2025-12-28 22:05:51+00
...
Significado: Las tuplas muertas son muchas; el vacuum está atrasado. Eso incrementa I/O aleatorio, perjudica índices y puede provocar riesgo de wraparound de transaction ID si se descuida lo suficiente.
Decisión: Ajusta autovacuum por tabla (factores de escala, límites de costo) y programa ventanas de mantenimiento para vacuum/analyze intensivos si hace falta. En un VPS pequeño, la «deuda de vacuum» se convierte en «deuda presupuestaria».
Task 10: MariaDB—inspect InnoDB buffer pool behavior
cr0x@server:~$ sudo mariadb -e "SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool%';"
+---------------------------------------+-----------+
| Variable_name | Value |
+---------------------------------------+-----------+
| Innodb_buffer_pool_pages_total | 524288 |
| Innodb_buffer_pool_pages_free | 1200 |
| Innodb_buffer_pool_read_requests | 980000000 |
| Innodb_buffer_pool_reads | 22000000 |
+---------------------------------------+-----------+
Significado: Las páginas libres son pocas (el pool está «lleno», normal). Reads vs read_requests indica cuántas veces faltas de caché. Un Innodb_buffer_pool_reads alto es caro en almacenamiento VPS.
Decisión: Si los misses son altos en relación con el volumen de solicitudes, aumenta el buffer pool (si la RAM lo permite) o arregla índices y reduce scans de tabla.
Task 11: MariaDB—check redo log pressure and flushing
cr0x@server:~$ sudo mariadb -e "SHOW GLOBAL STATUS LIKE 'Innodb_log_waits'; SHOW GLOBAL STATUS LIKE 'Innodb_os_log_fsyncs';"
+------------------+-------+
| Variable_name | Value |
+------------------+-------+
| Innodb_log_waits | 842 |
+------------------+-------+
+---------------------+--------+
| Variable_name | Value |
+---------------------+--------+
| Innodb_os_log_fsyncs| 520000 |
+---------------------+--------+
Significado: Innodb_log_waits > 0 significa que sesiones esperan por espacio en el redo log o progreso de flush—clásico cuello de botella de escrituras.
Decisión: Aumenta la capacidad de redo (según opciones de versión), ajusta el flushing y reduce la ráfaga de transacciones. Si el almacenamiento es lento, considera tradeoffs de durabilidad solo si el negocio los acepta.
Task 12: MariaDB—confirm what durability level you’re actually running
cr0x@server:~$ sudo mariadb -e "SHOW VARIABLES LIKE 'innodb_flush_log_at_trx_commit'; SHOW VARIABLES LIKE 'sync_binlog';"
+-------------------------------+-------+
| Variable_name | Value |
+-------------------------------+-------+
| innodb_flush_log_at_trx_commit| 1 |
+-------------------------------+-------+
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| sync_binlog | 1 |
+---------------+-------+
Significado: Ambos a 1 significa durabilidad fuerte (fsync por commit y por binlog group según configuración). Esto cuesta IOPS pero compra integridad.
Decisión: Si necesitas velocidad y puedes aceptar cierto riesgo, podrías relajar uno de estos. Si no puedes explicar el riesgo a un stakeholder no técnico, mantenlos en 1 y optimiza en otro lado.
Task 13: Detect excessive connections (PostgreSQL)
cr0x@server:~$ sudo -u postgres psql -d appdb -c "SELECT state, count(*) FROM pg_stat_activity GROUP BY state ORDER BY count(*) DESC;"
state | count
--------+-------
idle | 320
active | 25
null | 2
Significado: Cientos de conexiones idle desperdician memoria y aumentan la sobrecarga de gestión.
Decisión: Añade un pooler (o arregla el pooling en la app). Reduce max_connections y aplica disciplina. En VPS, las conexiones idle son un artículo de lujo.
Task 14: Detect replication lag (both worlds) before it ruins failover
cr0x@server:~$ sudo mariadb -e "SHOW SLAVE STATUS\G" | egrep 'Seconds_Behind_Master|Slave_IO_Running|Slave_SQL_Running'
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Seconds_Behind_Master: 37
Significado: 37 segundos de lag. No es catastrófico, pero si haces failover durante un incidente, eliges pérdida de datos o downtime.
Decisión: Investiga ráfagas de escritura, disco lento y transacciones de larga duración. Considera semi-sync o hardware mejor para réplicas si el RPO es importante.
Task 15: Check fsync timing pain indirectly (PostgreSQL checkpoint stats)
cr0x@server:~$ sudo -u postgres psql -d appdb -c "SELECT checkpoints_timed, checkpoints_req, round(checkpoint_write_time/1000.0,1) AS write_s, round(checkpoint_sync_time/1000.0,1) AS sync_s FROM pg_stat_bgwriter;"
checkpoints_timed | checkpoints_req | write_s | sync_s
------------------+-----------------+---------+--------
120 | 85 | 980.4 | 210.2
Significado: Alto tiempo de sync implica almacenamiento luchando durante checkpoints; eso se traduce en picos de latencia visibles al usuario.
Decisión: Aumenta max_wal_size, ajusta checkpoint_completion_target y reduce escrituras en ráfaga. Si puedes separar WAL en un volumen mejor, hazlo.
Tres mini-historias del mundo corporativo (anonimizadas, plausibles, dolorosas)
1) Incidente causado por una suposición errónea: “Más RAM significa más velocidad”
La empresa tenía una pequeña flota de VPS alojando un monolito y su base de datos. La base de datos era MariaDB. El tráfico creció, y el equipo hizo lo que hacen los equipos bajo presión: duplicaron la RAM y se declararon victoriosos. Los gráficos parecieron mejores por una semana.
Luego llegó una campaña de marketing. La latencia se disparó hasta timeouts. Los servidores de aplicación estaban bien. La CPU estaba bien. La base de datos tenía mucha memoria. Todos miraron el dashboard como si les debiera una disculpa.
La suposición errónea fue simple: «Si hay RAM libre, la base de datos debe poder usarla». Habían aumentado la RAM pero mantuvieron un innodb_buffer_pool_size conservador de días anteriores, y la carga había cambiado. Las lecturas ahora fallaban en caché y pegaban al disco. La nueva RAM se convirtió mayormente en caché del sistema de archivos y «libre», mientras la base de datos seguía pagando I/O aleatorio en un volumen de almacenamiento ruidoso.
Peor, tuvieron un efecto de segundo orden: más RAM significó mayor posibilidad de backlog de páginas sucias y conjuntos de trabajo más grandes, lo que hizo que las ráfagas fueran más punitivas cuando los hilos de flush se atrasaron. Cuando llegó la campaña, el comportamiento de flushing de InnoDB creó paradas periódicas que coincidieron exactamente con el pico de tráfico.
La solución no fue heroica. Dimensionaron correctamente el buffer pool, validaron los ajustes de capacidad de I/O y ejecutaron una reproducción de carga para confirmar que el p99 se estabilizó. La lección cara: en VPS, la RAM es rendimiento solo si la base de datos está configurada para gastarla.
2) Optimización que salió mal: “Vamos a subir work_mem”
Otra organización ejecutaba PostgreSQL en un VPS de 2 vCPU / 8 GB. Las consultas de reporting eran lentas y alguien sugirió aumentar work_mem «para que los sorts se hagan en memoria». Lo aumentaron agresivamente, porque las consultas eran grandes y las quejas de usuarios eran mayores.
Por un día, los reports volaron. Luego todo el sistema se volvió raro. Picos aleatorios. Autovacuum empezó a retrasarse. El kernel comenzó a intercambiar. La app mostró 502s intermitentes. Conoces el patrón: todo parece un problema de red cuando el host está en llamas.
El problema era previsible: work_mem es por operación, por consulta, por backend. Bajo concurrencia, Postgres asignó felizmente mucho más de la RAM física, y Linux respondió con swapping. No un poco de swapping. El tipo de swapping que hace que un VPS parezca estar corriendo en una unidad USB.
La solución incluyó bajar work_mem, añadir un pooler de conexiones para limitar la concurrencia y reescribir algunos reports para preagregar. También aprendieron a medir con concurrencia realista. Los benchmarks de un solo usuario son cómo compras el servidor equivocado.
3) Práctica aburrida pero correcta que salvó el día: “Mantenemos vacuum y backups rutinarios”
Un pequeño SaaS ejecutaba PostgreSQL en un VPS modesto con una réplica de lectura. No tenían infraestructura sofisticada, pero sí disciplina: autovacuum estaba ajustado por tabla para datos de alta rotación y tenían mantenimiento programado para los peores casos. Cada cambio de esquema venía con un plan de índices y rollback.
Una tarde, la latencia de almacenamiento en el primario se degradó. No fue una caída completa—solo lo suficiente para crear miseria. Los checkpoints tardaron más, WAL se flushó más lento y la latencia de la app subió. El equipo tenía dos ventajas: sabían qué era «normal» y su sistema no cargaba deuda de mantenimiento.
Porque el vacuum no estaba atrasado, las tablas no estaban hinchadas, así que el sistema no amplificó lecturas innecesariamente. Porque las copias de seguridad fueron probadas, podían hacer cambios sin miedo. Porque la replicación se monitoreaba, sabían que la réplica estaba sana y podía servir lecturas temporalmente.
El incidente dolió, pero no se convirtió en una catástrofe de varios días. La práctica «aburrida» no era un truco secreto de rendimiento. Era simplemente negarse a dejar que la base de datos se convierta en un vertedero. En presupuestos VPS, la limpieza es rendimiento.
Errores comunes: síntoma → causa raíz → solución
1) Síntoma: picos periódicos de latencia de 2–10 segundos
Causa raíz: checkpoints (PostgreSQL) o flushing agresivo (InnoDB) provocando I/O síncrono en ráfaga; a veces backups compitiendo por disco.
Solución: Suaviza escrituras: aumenta la capacidad de WAL/redo, ajusta la cadencia de checkpoints, asegura que los backups usen throttling o se ejecuten fuera de pico. Confirma con iostat await/%util y estadísticas del bgwriter de Postgres.
2) Síntoma: “La CPU está baja pero todo está lento”
Causa raíz: latencia de almacenamiento o esperas por locks. La CPU parece inactiva porque los hilos están esperando.
Solución: Revisa iowait, cola de disco y eventos de espera en la BD. Para locks, identifica los bloqueadores y acorta las transacciones.
3) Síntoma: Postgres se vuelve más lento con el tiempo sin cambios de código
Causa raíz: deuda de vacuum/analyze, bloat de tablas/índices, estadísticas del planner obsoletas.
Solución: Ajusta autovacuum (especialmente factors) para tablas de alta rotación; monitorea tuplas muertas; programa vacuum/analyze; considera particionado para datos con alta rotación.
4) Síntoma: lag de replicación de MariaDB aumenta durante picos de escritura
Causa raíz: la réplica I/O no puede mantenerse (coste de redo/binlog fsync, disco lento), o transacciones largas retrasan la aplicación.
Solución: Reduce la presión de fsync, asegura que el almacenamiento de la réplica sea adecuado, evita transacciones gigantes y monitorea la tasa de apply. Si tu plan de failover asume lag cero, deja de asumirlo.
5) Síntoma: alta carga promedio pero uso de CPU modesto
Causa raíz: la cola de ejecutables incluye tareas bloqueadas en espera de I/O no interrumpible (estado D). La carga promedio las cuenta.
Solución: Confirma con iostat y estados de procesos. La solución es almacenamiento, no más vCPU.
6) Síntoma: OOM kills repentinos después de “tuning para velocidad”
Causa raíz: explosión de memoria por conexión (Postgres work_mem, maintenance_work_mem; buffers de sort/join de MariaDB) combinada con demasiadas conexiones.
Solución: Implementa pooling, reduce max connections y fija knobs de memoria basados en la concurrencia peor prevista.
7) Síntoma: inserts lentos, selects bien (PostgreSQL)
Causa raíz: latencia de fsync de WAL, demasiados índices o settings de synchronous_commit no acordes al hardware.
Solución: reduce número de índices, agrupa escrituras, ajusta WAL/checkpoints, considera commit asíncrono solo si es aceptable y asegura que WAL no compita con lecturas aleatorias.
8) Síntoma: “Subimos el tamaño de instancia y no ganamos nada”
Causa raíz: el cuello de botella es almacenamiento o contención por locks; más CPU/RAM no lo cambia.
Solución: mide iowait, await y esperas por locks primero; luego gasta dinero.
Listas de verificación / plan paso a paso
A. Elegir el motor para velocidad por dólar (lista de decisión)
- ¿Necesitas SQL complejo, diagnósticos claros y comportamiento predecible? Elige PostgreSQL.
- ¿Mayormente OLTP simple, equipo con experiencia MySQL, dataset caliente cabe en buffer pool? MariaDB es una buena apuesta de coste.
- ¿La calidad del almacenamiento es desconocida o inconsistente? Prefiere PostgreSQL si necesitas corrección fuerte con menos tentaciones «rápidas pero arriesgadas»; de lo contrario te meterás en un rincón al ajustar.
- ¿Alto número de conexiones y la app es charly? PostgreSQL con pooler; MariaDB también es sensible, pero Postgres te castigará más directamente.
B. Baselinear un nuevo VPS (haz esto antes de culpar a la base de datos)
- Verifica CPU steal y latencia de almacenamiento en reposo y bajo carga.
- Confirma sistema de archivos, opciones de montaje y espacio libre de seguridad.
- Configura métricas básicas: CPU, iowait, disk await, conexiones BD, lag de replicación.
- Ejecuta un benchmark de concurrencia realista (no un cliente, no una consulta).
C. Plan de ajuste para PostgreSQL en VPS (seguro y efectivo)
- Implementa pool de conexiones; limita sesiones activas.
- Define shared_buffers de forma conservadora; establece effective_cache_size de forma realista.
- Fija work_mem modestamente; verifica la concurrencia pico antes de aumentarlo.
- Ajusta checkpoint/WAL para reducir picos de latencia.
- Valida que autovacuum está al día; ajusta por tabla para datos de alta rotación.
- Añade índices solo cuando compensen; cada índice es un impuesto a la escritura.
D. Plan de ajuste para MariaDB en VPS (InnoDB primero, cordura primero)
- Dimensiona innodb_buffer_pool_size al conjunto de trabajo caliente, dejando margen para el OS.
- Asegura que la capacidad de redo sea suficiente; evita log waits.
- Establece innodb_io_capacity basado en la realidad, no en el optimismo.
- Sé explícito sobre settings de durabilidad; no los relajes «por accidente».
- Vigila lag de replicación y longitud de historial de purge (según versión/herramientas).
- Audita índices: elimina los añadidos «por si acaso».
E. La lista “no compres más grande todavía”
- Has identificado las consultas principales y medido su impacto.
- Has confirmado si las esperas son I/O, locks o CPU.
- Has eliminado swapping y tormentas de conexiones.
- Has suavizado el comportamiento de checkpoints/flushing.
- Has validado que replicación y backups no compiten con producción.
Preguntas frecuentes
1) ¿Cuál es más rápido en un VPS barato: MariaDB o PostgreSQL?
Depende de la forma de la carga, pero PostgreSQL suele ofrecer un rendimiento más predecible bajo cargas mixtas. MariaDB puede ser más rápido para OLTP simple y con muchas lecturas cuando el buffer pool cubre el working set.
2) ¿Cuál es el mayor cuello de botella común en VPS para ambos?
La variabilidad de la latencia de almacenamiento. No el ancho de banda—la latencia. El comportamiento de WAL/redo y fsync hace visibles pequeñas paradas a los usuarios.
3) ¿Debería poner WAL/redo en un disco separado?
Si tu proveedor VPS ofrece un volumen separado con características de rendimiento independientes, sí, puede ayudar. Si está en la misma capa subyacente, solo añades complejidad.
4) ¿Es seguro relajar la durabilidad para ganar velocidad?
A veces. Pero «seguro» es una política de negocio, no un setting de base de datos. Si puedes tolerar perder el último segundo de transacciones tras un fallo, puedes comprar velocidad. Si no, optimiza en otro lado.
5) ¿Por qué PostgreSQL necesita VACUUM y MariaDB no (tan visible)?
El MVCC de PostgreSQL deja tuplas muertas hasta que vacuum las reclama y mantiene metadatos de visibilidad sanos. InnoDB gestiona undo/redo y purge de forma diferente. Ambos requieren mantenimiento; PostgreSQL simplemente lo hace más difícil de ignorar.
6) ¿Cómo sé si necesito un pooler para PostgreSQL?
Si tienes cientos de conexiones mayormente idle o ráfagas de churn de conexiones, necesitas pooling. En un VPS pequeño, es una de las mejores mejoras de «velocidad por dólar» que puedes aplicar sin comprar hardware.
7) ¿Qué métricas debería alertar primero?
Disk await/%util, iowait, uso de swap, conexiones activas, lag de replicación y eventos de espera de BD (para Postgres). Alerta sobre tendencias, no solo sobre umbrales.
8) ¿Puedo tunear para salir de un almacenamiento VPS malo?
Puedes reducir el daño—suavizar escrituras, aumentar la tasa de aciertos de caché, reducir concurrencia—pero no vencerás la física y a los vecinos ruidosos para siempre. A veces el ajuste correcto es cambiar de proveedor o clase de almacenamiento.
9) Para velocidad por dólar, ¿es mejor escalar verticalmente o añadir una réplica?
Si las lecturas dominan, una réplica puede comprar más que escalar verticalmente. Si las escrituras dominan y estás limitado por I/O en WAL/redo, escalar verticalmente (o mejorar almacenamiento) suele ganar primero.
10) ¿Cuál es más fácil de diagnosticar bajo presión?
PostgreSQL, típicamente. Las herramientas de introspección y la visibilidad de esperas son mejores por defecto, y la historia de rendimiento es más fácil de razonar cuando estás cansado.
Siguientes pasos que puedes hacer esta semana
- Mide la realidad de tu VPS: ejecuta mpstat e iostat durante un periodo lento y uno ocupado. Si iowait y await son altos, deja de fingir que tu cuello de botella es la sintaxis SQL.
- Encuentra las consultas principales: usa pg_stat_statements (Postgres) o slow query log/performance schema (MariaDB) y arregla a los peores primero.
- Elimina el swapping: ajusta las configuraciones de memoria y pon pooling. El swapping convierte un «VPS barato» en una «caída cara».
- Suaviza las escrituras: ajusta checkpoints (Postgres) o flushing/log (MariaDB) para reducir picos, luego verifica con estadísticas y gráficas de latencia.
- Haz el mantenimiento aburrido: la salud de autovacuum (Postgres) y la salud de logs/flush de InnoDB (MariaDB) no son tareas «más adelante». Son la renta.
Si quieres la mayor velocidad por dólar, trata la base de datos como un sistema de producción, no como una librería mágica. Mide. Cambia una cosa. Mide de nuevo. Eso no es solo virtud de ingeniería—es la mejora de rendimiento más barata que podrás comprar.