Ajuste de PostgreSQL vs Percona Server: ¿quién necesita más perillas para la velocidad?

¿Te fue útil?

No eliges una base de datos porque el logotipo se vea amigable. La eliges porque a las 02:13, bajo una tarea masiva que truena,
la necesitas respondiendo “sí” más rápido de lo que puede vibrar tu pager.

La verdad incómoda: tanto PostgreSQL como Percona Server (MySQL) pueden ser rápidos. La diferencia es
qué tan pronto llegas a ser rápido, y cuántas palancas debes accionar antes de que la realidad deje de castigarte.
Esta es una comparación centrada en el ajuste y pensada para producción: qué motor necesita más perillas para un buen rendimiento, qué perillas importan,
y cómo diagnosticar cuellos de botella sin convertir tu configuración en una casa encantada.

La tesis directa: quién necesita más perillas

Si defines “buen rendimiento” como rendimiento respetable sin ajustes heroicos, PostgreSQL normalmente llega
ahí con menos parámetros “obligatorios” —para una primaría única, un esquema sensato y patrones de consulta no caóticos. Tiene perillas, desde luego,
pero muchas están pensadas para la predictibilidad y los guardianes más que para “gira estas 17 palancas o sufre”.

Percona Server (una distribución endurecida de MySQL, basada principalmente en InnoDB) puede ser extremadamente rápido, pero con más frecuencia te pide
tomar decisiones explícitas antes: dimensionamiento del buffer pool, tamaño del redo log, compensaciones de durabilidad, comportamiento de flush, configuración de replicación,
sobrecarga de instrumentación. Algunos de esos valores por defecto son seguros; otros son conservadores; otros funcionan hasta que la carga cambia
y entonces se convierten en un impuesto silencioso.

Entonces, ¿quién necesita más perillas? En la práctica:

  • Percona Server tiende a requerir más ajuste explícito para alcanzar “buen rendimiento” en OLTP con muchas escrituras, alta concurrencia,
    y topologías con mucha replicación—especialmente cuando la latencia importa y el conjunto de datos ya no cabe en memoria.
  • PostgreSQL tiende a necesitar menos perillas para “buen rendimiento” en cargas mixtas y consultas complejas—pero exige higiene continua
    (autovacuum, control de bloat, estadísticas) o envejecerá como leche en una sala de servidores cálida.

El truco oculto: la pregunta no es “cuál tiene más perillas”, sino “qué perillas son inevitables y cuán costoso es equivocarlas.”
Ajustes incorrectos de InnoDB flushing pueden convertir un sistema rápido en uno entrecortado. Autovacuum mal ajustado puede hacer que PostgreSQL parezca
“lentamente aleatorio” cuando en realidad se está ahogando en tuplas muertas.

Primera broma (corta y merecida): Ajustar bases de datos es como afinar guitarras: la mayoría gira las clavijas hasta que suena distinto, no mejor.

Algunos datos interesantes (y por qué importan)

Esto no es trivia para un quiz de bar. Explican por qué la superficie de ajuste se siente diferente.

  1. El diseño MVCC de PostgreSQL almacena versiones antiguas de filas en el heap hasta que el vacuum las recupera. Por eso
    el ajuste de autovacuum es ajuste de rendimiento, no limpieza doméstica.
  2. El buffer pool de InnoDB es el centro de gravedad del rendimiento para MySQL/Percona. Si está subdimensionado, todo se vuelve limitado por I/O;
    si está sobredimensionado sin margen, la caché de página del SO y el sistema de archivos empiezan a competir.
  3. Percona Server existe porque “MySQL stock” no fue suficiente para muchos operadores — históricamente enfatizó la observabilidad,
    diagnósticos extras y mejoras de rendimiento manteniendo compatibilidad. Esa cultura se nota en la cantidad de variables ajustables y métricas expuestas.
  4. PostgreSQL incorporó consultas paralelas en incrementos importantes desde 9.6+, y sigue siendo un lugar donde unas pocas perillas
    (como max_parallel_workers_per_gather) pueden hacer que una consulta de reporte pase de “pausa para el café” a “parpadeo”.
  5. La línea de replicación de MySQL empujó mucho ajuste operacional: row vs statement, formato de binlog, ajustes de sync, variantes de group replication.
    La topología es parte del rendimiento.
  6. WAL y checkpoints de PostgreSQL se comportan distinto que redo/undo de InnoDB. Ajustar checkpoints en Postgres suele tratar de suavizar picos de escritura;
    el ajuste de redo en InnoDB suele ir de sostener el throughput de escrituras sin thrashing.
  7. Percona introdujo/avanzó características como mejoras al slow query log y variables de estado adicionales que hacen que “medir primero” sea más práctico.
    La contrapartida: puedes medir hasta generar sobrecarga si habilitas todo.
  8. Ambos ecosistemas aprendieron a la mala que “más caché” no siempre es más rápido. Presión de memoria, efectos NUMA y comportamiento del asignador
    pueden convertir “más RAM para la BD” en “más latencia para todos”.

Filosofía de ajuste base: comportamiento por defecto vs realidad de producción

Qué significa realmente “buen rendimiento” en operaciones

En producción, la velocidad no es un solo número. Es:

  • latencia p95 y p99 bajo concurrencia real
  • estabilidad del throughput durante compactación/vacuum/checkpoints
  • comportamiento de cola predecible durante backups, catch-up de replicación y failover
  • tiempo para diagnosticar cuando se pone lento (observabilidad como rendimiento)

Defaults de PostgreSQL: corteses, conservadores y a veces ingenuos

Los valores por defecto de PostgreSQL están diseñados para “funciona en un portátil” y “no deja la máquina hecha pedazos”. No agarrarán automáticamente
la mayor parte de tu RAM. Eso es buena educación. También es la razón por la que nuevas instalaciones pueden decepcionar hasta que configures unos parámetros clave.

Lo positivo: una vez que fijas memoria y comportamiento de checkpoints adecuadamente, PostgreSQL suele comportarse de forma predecible. Aún afinarás
para patrones de consulta y carga de mantenimiento específicos, pero el sistema tiene menos probabilidad de requerir una larga lista de toggles “o si no”.

Defaults de Percona Server: capaces, pero esperando que elijas una postura

Percona Server hereda mucho de los defaults de MySQL, y muchos son seguros-pero-no-rápidos. Pero la audiencia de Percona suele ejecutar OLTP serio,
así que la suposición operativa es diferente: vas a configurar buffer pool, redo logs, comportamiento de flush y replicación. Si no lo haces,
la base de datos seguirá funcionando—pero dejarás velocidad sobre la mesa, y a veces la mesa se prende fuego bajo carga.

PostgreSQL: las perillas que realmente mueven la aguja

1) Memoria: shared_buffers, work_mem, maintenance_work_mem

PostgreSQL tiene múltiples dominios de memoria. Esto confunde a quienes crecieron con “fija buffer pool y vete”.
Tu tarea es evitar la muerte por mil asignaciones de work_mem mientras mantienes suficiente caché y margen para mantenimiento.

  • shared_buffers: caché compartida primaria usada por PostgreSQL. Punto de partida común: 20–30% de la RAM en Linux.
    Más no siempre es mejor; la caché del SO sigue siendo importante.
  • work_mem: por nodo de ordenamiento/hash, por consulta, por worker. Demasiado alto y la concurrencia se vuelve una ruleta de swap.
    Demasiado bajo y derramas a disco. Úsalo con intención.
  • maintenance_work_mem: vacuum y creación de índices. Demasiado bajo hace lento el mantenimiento; demasiado alto puede dejar sin recursos al resto.

2) WAL y checkpoints: evitar el precipicio de escritura

Las quejas de rendimiento en PostgreSQL a menudo se leen como: “Es rápido, luego no lo es, luego vuelve a serlo.”
Eso suele ser checkpoints, comportamiento del background writer o saturación del almacenamiento.

  • checkpoint_timeout y max_wal_size: aumentar para reducir la frecuencia de checkpoints.
  • checkpoint_completion_target: apunta a repartir el I/O del checkpoint a lo largo del tiempo.
  • wal_compression: puede ayudar cuando el volumen de WAL es alto y hay CPU disponible.

3) Autovacuum: o lo ajustas, o él te ajusta a ti

Autovacuum es un sistema de mantenimiento en segundo plano que previene el bloat y el wraparound de transaction ID. Si se queda atrás, obtienes:
tablas hinchadas, consultas lentas, índices ineficientes y I/O “misterioso”.

Palancas clave:

  • autovacuum_max_workers: más workers ayuda con muchas tablas; demasiados pueden generar picos de I/O.
  • autovacuum_vacuum_scale_factor y autovacuum_analyze_scale_factor: bájalos para tablas calientes.
  • autovacuum_vacuum_cost_limit y …_delay: controlan cuán agresivo es.

4) Realismo del planner: estadísticas y “effective_cache_size”

El planner de PostgreSQL suele ser excelente, pero no es psíquico. Si las estadísticas están obsoletas o son demasiado toscas, elige el plan equivocado.
Si cree que la caché es menor de lo real, puede favorecer planes que hagan demasiado I/O.

  • effective_cache_size: di al planner cuánto caché puede asumir (shared buffers + caché del SO).
  • default_statistics_target y objetivos de estadísticas por columna: súbelos para distribuciones sesgadas.

5) Manejo de conexiones: el clásico “demasiados backends”

PostgreSQL usa un modelo proceso-por-conexión (con variaciones y mejoras a lo largo del tiempo). Altos conteos de conexiones aumentan el uso de memoria
y el cambio de contexto. No arreglas esto con pensamiento deseoso; lo arreglas con un pool.

  • max_connections: no lo pongas en 5000 solo porque puedes.
  • Usar un pooler (como pgBouncer) cuando tu app abre muchas conexiones de corta duración.

Percona Server: las perillas que no puedes ignorar

1) InnoDB buffer pool: la grande

Si solo ajustas una cosa en Percona Server/MySQL, ajusta esta. El buffer pool cachea datos e índices; es donde tus IOPS de lectura van a retirarse.
Guía típica: 60–75% de la RAM en un host dedicado para BD, dejando espacio para el SO, conexiones, replicación y caché del sistema de archivos (especialmente si usas O_DIRECT las elecciones difieren).

  • innodb_buffer_pool_size: fíjalo intencionalmente.
  • innodb_buffer_pool_instances: ayuda la concurrencia en pools grandes; demasiadas instancias añaden overhead.

2) Redo logs y flushing: durabilidad vs rendimiento no es un debate moral

El rendimiento de escritura en InnoDB está fuertemente moldeado por el tamaño del redo log y el comportamiento de flushing.
Puedes hacerlo rápido asumiendo riesgos de durabilidad. A veces eso es aceptable; muchas veces no.

  • innodb_redo_log_capacity (o el dimensionamiento antiguo de los archivos de log): demasiado pequeño causa checkpoints frecuentes y churn.
  • innodb_flush_log_at_trx_commit: 1 es lo más seguro; 2 es común para rendimiento con algún riesgo; 0 es picante.
  • sync_binlog: la durabilidad del binlog también importa si dependes de replicación o recuperación punto-en-tiempo.

3) Capacidad de I/O y páginas sucias: enseña al motor tu almacenamiento

InnoDB intenta adaptarse, pero aún necesita pistas. NVMe moderno se comporta distinto que SSD en red o dispositivos de bloque en la nube con créditos de ráfaga.

  • innodb_io_capacity y innodb_io_capacity_max: ajústalos a capacidades de IOPS realistas.
  • innodb_max_dirty_pages_pct y ajustes relacionados: controlan cuánto dato sucio puede acumularse antes de que comiencen tormentas de flushing.

4) Hilos y concurrencia: menos “magia”, más decisiones

MySQL tiene una larga historia de ajuste de concurrencia. Muchas cargas funcionan con los defaults, hasta que no.
Entonces descubres que estás limitado por mutexes de CPU o sufres por overhead de planificación de hilos.

  • thread_cache_size: reduce el overhead de creación de hilos.
  • max_connections: demasiado alto crea presión de memoria y contención de locks.

5) Replicación y binlog: el rendimiento es consciente de la topología

El ajuste de replicación existe también en PostgreSQL, pero la vida operativa de MySQL/Percona a menudo gira alrededor de la replicación.
Formato de binlog, orden de commits y paralelismo de aplicación en réplicas afectan tanto la velocidad como las expectativas de corrección.

  • binlog_format: ROW es común por corrección; STATEMENT puede ser más pequeño pero más arriesgado; MIXED es un compromiso.
  • replica_parallel_workers (o los antiguos slave_*): ayuda a que las réplicas se pongan al día, pero cuidado con la contención.

Patrones de carga: dónde cada motor “simplemente funciona” (y dónde no)

OLTP de alta concurrencia con consultas simples

Percona Server (InnoDB) está en su hábitat natural aquí. Con un buffer pool bien dimensionado y ajustes de flush sensatos, puede sostener altas tasas de escritura.
PostgreSQL también puede manejar OLTP bien, pero prestarás atención a autovacuum y diseño de índices antes porque las tuplas muertas se acumulan bajo patrones con muchas actualizaciones.

Si tu carga es “actualizaciones en todas partes, todo el tiempo”, PostgreSQL necesita el mantenimiento como preocupación de primera clase. Si lo tratas como poner y olvidar,
tu historia de rendimiento empeorará con los meses.

Consultas complejas, analítica, joins y “¿por qué este informe está lento?”

PostgreSQL generalmente tiene la ventaja en sofisticación del planner, extensibilidad y facilidad para expresar SQL complejo.
No es que MySQL no pueda hacer joins; es que tiendes a encontrar más bordes filosos antes en la planificación de consultas y en las elecciones de índices.

Aquí el ajuste en PostgreSQL es menos sobre “más perillas” y más sobre la calidad de las estadísticas, índices apropiados y
evitar regresiones de plan tras upgrades o cambios de esquema.

Picos de escritura y trabajos por lotes

El comportamiento de checkpoint/WAL de PostgreSQL puede crear ráfagas periódicas de I/O si está mal ajustado.
Percona también puede sufrir tormentas de flushing cuando la gestión de páginas sucias está mal configurada, o cuando el almacenamiento miente sobre su rendimiento.

Bajo cargas por lotes, tu objetivo es el mismo: suavizar el patrón de escritura, mantener la latencia predecible y evitar que el dispositivo de almacenamiento
sea el punto único de verdad y sufra.

Simplicidad operativa vs control operativo

PostgreSQL puede sentirse más simple porque menos ajustes son “obligatorios”. Pero su corrección operativa depende del vacuum y de una gestión sensata de conexiones.
Percona se siente más “rica en perillas” porque ofrece muchos puntos de control explícitos, especialmente en durabilidad e I/O.

Elige según quién lo operará a las 02:13. Si tienes un equipo que disfruta controles explícitos y fuerte observabilidad, el ecosistema de Percona resulta cómodo.
Si valoras defaults sensatos y SQL potente con menos superficie de ajuste para alcanzar rendimiento aceptable, PostgreSQL suele ser más amigable—siempre que te comprometas con la higiene de vacuum.

Almacenamiento y SO: la parte que querías ignorar

No puedes sobreajustar un almacenamiento malo. Solo puedes hacerlo fallar de maneras más interesantes.
Ambos, PostgreSQL y Percona, son brutalmente honestos: si la latencia de tu almacenamiento es inconsistente, tu p99 parecerá una película de terror.

La latencia vence al throughput para la mayoría de OLTP

La mayoría de cargas OLTP se preocupan más por latencia consistente submilisegundo a milisegundos bajos que por MB/s crudos.
Un dispositivo que hace 3 GB/s secuencial pero ocasionalmente se queda detenido 200 ms es un bromista, no un disco de base de datos.

Sistemas de archivos y opciones de montaje importan (pero menos de lo que temes)

Los sistemas de archivos modernos de Linux están bien. Lo que importa es alinear el patrón de I/O de tu BD con la pila de almacenamiento:

  • Cuidado con la doble caché (caché DB + caché del SO) cuando la memoria es limitada.
  • Sé prudente con configuraciones de writeback agresivas que crean paradas periódicas.
  • Entiende si estás en NVMe local, almacenamiento de bloques en red o “SSD en la nube” con comportamiento de ráfaga.

Una cita operativa para mantener la honestidad

La esperanza no es una estrategia. — General Gordon R. Sullivan

Es una cita de gestión, pero los SRE la adoptaron porque duele de forma útil. No “esperes” que tus defaults sean suficientes. Mide y decide.

Tareas prácticas: comandos, salidas y decisiones (12+)

Estas son las tareas que ejecutas cuando un sistema está lento y necesitas decidir qué cambiar sin empezar un club de folklore de configuración.
Cada tarea incluye: un comando, salida típica, qué significa y la decisión que tomas.

Task 1: Confirmar si estás limitado por CPU o I/O (Linux)

cr0x@server:~$ iostat -xz 1 3
Linux 6.5.0 (db1)  12/30/2025  _x86_64_  (16 CPU)

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          32.10    0.00    7.90   18.50    0.00   41.50

Device            r/s     rkB/s   rrqm/s  %rrqm r_await rareq-sz     w/s     wkB/s   wrqm/s  %wrqm w_await wareq-sz  aqu-sz  %util
nvme0n1         420.0  28500.0     10.0   2.3    3.10    67.9    780.0  112000.0    50.0   6.0   18.20   143.6   15.2   98.0

Significado: Alto %iowait y utilización del dispositivo cercana al 100% con alto w_await apunta a un problema de latencia de escritura.
Esto no es un momento para “más índices”.

Decisión: Priorizar el ajuste del camino de escritura (checkpoints/WAL en Postgres, flushing/redo en InnoDB) e investigar el almacenamiento antes de tocar consultas.

Task 2: Verificar presión de memoria y swapping (Linux)

cr0x@server:~$ vmstat 1 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 3  0      0 120432  98200 8123456  0    0   120  2400  920 2100 35  8 40 17  0
 6  1   8192  20480  11000 7012000  0  256  1024  8900 1200 4800 42 10 25 23  0

Significado: Actividad de swap-out (so) y caída de memoria libre bajo carga sugiere sobrecompromiso de memoria.
Para PostgreSQL esto muchas veces significa work_mem multiplicado por la concurrencia. Para MySQL, buffer pool sobredimensionado más muchas conexiones puede causarlo.

Decisión: Reducir el riesgo de memoria por conexión (pooling, disminuir work_mem, bajar max_connections) antes de “añadir RAM” como reflejo.

Task 3: PostgreSQL — ver principales esperas (¿en qué están atascados los backends?)

cr0x@server:~$ psql -X -c "SELECT wait_event_type, wait_event, count(*) FROM pg_stat_activity WHERE state='active' GROUP BY 1,2 ORDER BY 3 DESC LIMIT 10;"
 wait_event_type |     wait_event      | count
-----------------+---------------------+-------
 IO              | DataFileRead         |    18
 Lock            | transactionid        |     7
 LWLock          | WALWrite             |     5
                 |                     |     0

Significado: Muchos DataFileRead significa misses de caché / lecturas I/O-limitadas. WALWrite indica presión de escritura en WAL.
transactionid lock waits pueden señalar contención o transacciones largas.

Decisión: Si domina I/O, inspeccionar tasas de hit de caché y latencia del almacenamiento. Si domina WALWrite, ajustar WAL/checkpoints y confirmar latencia de fsync.

Task 4: PostgreSQL — comprobar ratio de aciertos de buffer cache (direccional, no religión)

cr0x@server:~$ psql -X -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 ORDER BY hit_pct ASC;"
  datname  | blks_hit | blks_read | hit_pct
-----------+----------+-----------+---------
 appdb     | 93210012 |  8200455  | 91.93
 postgres  |  1023012 |    12045  | 98.84

Significado: 92% puede ser suficiente o terrible dependiendo de la carga. El punto es la tendencia y la correlación con la latencia.
Una caída repentina sugiere crecimiento del working set, patrones de consulta malos o memoria insuficiente.

Decisión: Si la tasa de aciertos baja y las lecturas aumentan, considera mejoras de índice o más memoria (shared_buffers + caché del SO), pero valida primero con planes de consulta.

Task 5: PostgreSQL — identificar retraso de autovacuum y riesgo de bloat

cr0x@server:~$ psql -X -c "SELECT relname, n_dead_tup, n_live_tup, last_autovacuum, last_autoanalyze FROM pg_stat_user_tables ORDER BY n_dead_tup DESC LIMIT 10;"
    relname     | n_dead_tup | n_live_tup |     last_autovacuum     |     last_autoanalyze
----------------+------------+------------+-------------------------+-------------------------
 orders         |    8923012 |   40211234 |                         | 2025-12-30 00:41:13+00
 order_items    |    6112001 |   88012210 | 2025-12-29 22:10:02+00  | 2025-12-30 00:40:59+00
 sessions       |    4100122 |    1802210 |                         | 2025-12-30 00:40:15+00

Significado: Muchas tuplas muertas y ausencia de last_autovacuum en tablas calientes es una señal roja. Las consultas se ralentizan porque índices y heap se agrandan,
y las comprobaciones de visibilidad se vuelven más costosas.

Decisión: Ajustar autovacuum por tabla (scale factors, cost limits) y arreglar transacciones largas que impiden la limpieza.

Task 6: PostgreSQL — encontrar transacciones largas que bloquean vacuum

cr0x@server:~$ psql -X -c "SELECT pid, usename, now()-xact_start AS xact_age, state, left(query,80) AS q FROM pg_stat_activity WHERE xact_start IS NOT NULL ORDER BY xact_age DESC LIMIT 5;"
 pid  | usename |  xact_age   | state  | q
------+---------+-------------+--------+--------------------------------------------------------------------------------
 8421 | app     | 03:12:44    | idle   | BEGIN; SELECT * FROM customers WHERE id=...
 9122 | app     | 00:18:02    | active | UPDATE orders SET status='paid' WHERE id=...

Significado: Una sesión “idle in transaction” que mantiene snapshots puede impedir que vacuum elimine tuplas muertas,
causando bloat y degradación de rendimiento.

Decisión: Corregir el alcance de las transacciones en la aplicación. Matar sesiones solo como emergencia y luego arreglar el código.

Task 7: PostgreSQL — comprobar comportamiento de checkpoints

cr0x@server:~$ psql -X -c "SELECT checkpoints_timed, checkpoints_req, checkpoint_write_time, checkpoint_sync_time, buffers_checkpoint FROM pg_stat_bgwriter;"
 checkpoints_timed | checkpoints_req | checkpoint_write_time | checkpoint_sync_time | buffers_checkpoint
-------------------+-----------------+-----------------------+----------------------+--------------------
               120 |              95 |              18234012 |              3100221 |           90211234

Significado: Alto checkpoints_req significa que alcanzas límites de WAL y fuerzas checkpoints.
Grandes tiempos de escritura/sync en checkpoints se correlacionan con picos de latencia.

Decisión: Aumentar max_wal_size, ajustar checkpoint_timeout y suavizar con checkpoint_completion_target.
Si el almacenamiento no puede seguirle, arregla el almacenamiento o reduce la amplificación de escritura (batching, menos índices, cambios de esquema).

Task 8: MySQL/Percona — ver en qué esperan los hilos (vista rápida)

cr0x@server:~$ mysql -e "SHOW PROCESSLIST\G" | head -n 40
*************************** 1. row ***************************
     Id: 12091
   User: app
   Host: 10.0.1.25:52144
     db: appdb
Command: Query
   Time: 12
  State: Waiting for table metadata lock
   Info: ALTER TABLE orders ADD COLUMN note TEXT
*************************** 2. row ***************************
     Id: 12102
   User: app
   Host: 10.0.1.18:51902
     db: appdb
Command: Query
   Time: 10
  State: update
   Info: UPDATE orders SET status='paid' WHERE id=...

Significado: Esperas de metadata lock pueden congelar el tráfico durante cambios de esquema. Esto puede parecer “la base de datos está lenta”, pero en realidad es “DDL bloqueando”.

Decisión: Usar enfoques de cambio de esquema online cuando sea necesario; programar DDL; reducir transacciones largas que mantienen locks.

Task 9: MySQL/Percona — comprobar salud del InnoDB buffer pool

cr0x@server:~$ mysql -e "SHOW ENGINE INNODB STATUS\G" | sed -n '1,120p'
*************************** 1. row ***************************
  Type: InnoDB
  Name:
Status:
=====================================
2025-12-30 01:12:09 INNODB MONITOR OUTPUT
=====================================
BUFFER POOL AND MEMORY
----------------------
Total large memory allocated 274877906944
Buffer pool size   16777216
Free buffers       1024
Database pages     16776190
Old database pages 6192000
Modified db pages  210400
Pages read 98200123, created 2210021, written 81230111
...

Significado: Buffers libres muy bajos y altas tasas de lectura pueden ser normales bajo carga, pero si las lecturas y la latencia son altas,
el pool puede ser demasiado pequeño para el working set o los patrones de consulta forzan scans.

Decisión: Si la memoria lo permite, aumenta innodb_buffer_pool_size. Si no, arregla consultas e índices primero; no dejes al SO sin recursos.

Task 10: MySQL/Percona — confirmar presión de redo log y flushing

cr0x@server:~$ mysql -e "SHOW GLOBAL STATUS LIKE 'Innodb_os_log_fsyncs'; SHOW GLOBAL STATUS LIKE 'Innodb_log_waits'; SHOW VARIABLES LIKE 'innodb_flush_log_at_trx_commit';"
+---------------------+----------+
| Variable_name       | Value    |
+---------------------+----------+
| Innodb_os_log_fsyncs| 18220031 |
+---------------------+----------+
+------------------+-------+
| Variable_name    | Value |
+------------------+-------+
| Innodb_log_waits | 12044 |
+------------------+-------+
+--------------------------------+-------+
| Variable_name                  | Value |
+--------------------------------+-------+
| innodb_flush_log_at_trx_commit | 1     |
+--------------------------------+-------+

Significado: Innodb_log_waits indica sesiones esperando por espacio en redo log / comportamiento de flush.
Con innodb_flush_log_at_trx_commit=1, la latencia de fsync está directamente en el camino de la transacción.

Decisión: Si las esperas son significativas, aumenta la capacidad de redo y valida la latencia de fsync del almacenamiento. Considera compensaciones de durabilidad solo con un acuerdo claro de RPO/RTO.

Task 11: MySQL/Percona — encontrar declaraciones principales rápido (resumen Performance Schema)

cr0x@server:~$ mysql -e "SELECT DIGEST_TEXT, COUNT_STAR, ROUND(SUM_TIMER_WAIT/1e12,2) AS total_s FROM performance_schema.events_statements_summary_by_digest ORDER BY SUM_TIMER_WAIT DESC LIMIT 3\G"
*************************** 1. row ***************************
DIGEST_TEXT: SELECT * FROM orders WHERE customer_id = ? ORDER BY created_at DESC LIMIT ?
 COUNT_STAR: 1822011
   total_s: 9120.55
*************************** 2. row ***************************
DIGEST_TEXT: UPDATE order_items SET status = ? WHERE order_id = ?
 COUNT_STAR: 801122
   total_s: 6122.17

Significado: Alto tiempo total puede ser “se llama frecuentemente” o “cada llamada es lenta”. Los resúmenes por digest te ayudan a elegir batallas.

Decisión: Toma los digests peores y ejecuta EXPLAIN, revisa índices y mide filas examinadas vs filas devueltas.

Task 12: PostgreSQL — encontrar consultas con mayor tiempo total (pg_stat_statements)

cr0x@server:~$ psql -X -c "SELECT query, calls, round(total_exec_time::numeric,1) AS total_ms, round(mean_exec_time::numeric,2) AS mean_ms FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 5;"
                           query                           | calls  | total_ms | mean_ms
-----------------------------------------------------------+--------+----------+---------
 SELECT * FROM orders WHERE customer_id=$1 ORDER BY ...     | 1822011| 812034.2 |   0.45
 UPDATE order_items SET status=$1 WHERE order_id=$2         |  801122| 610221.7 |   0.76

Significado: La misma historia que los digests de MySQL: encuentra los sumideros reales de tiempo. PostgreSQL te da tiempo medio y tiempo total; ambos importan.

Decisión: Para consultas de muchas llamadas, busca soporte de índices y evita traer filas innecesarias. Para tiempos medios lentos, inspecciona planes e I/O.

Task 13: PostgreSQL — inspeccionar un plan de consulta por “mentiras” y malas estimaciones

cr0x@server:~$ psql -X -c "EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM orders WHERE customer_id=123 ORDER BY created_at DESC LIMIT 50;"
 Limit  (cost=0.43..12.86 rows=50 width=128) (actual time=0.210..4.882 rows=50 loops=1)
   Buffers: shared hit=120 read=320
   ->  Index Scan Backward using idx_orders_customer_created on orders  (cost=0.43..9012.12 rows=35210 width=128) (actual time=0.207..4.865 rows=50 loops=1)
         Index Cond: (customer_id = 123)
         Buffers: shared hit=120 read=320
 Planning Time: 0.220 ms
 Execution Time: 4.910 ms

Significado: Los read=320 buffers indican lecturas físicas durante la ejecución. Si esta consulta es caliente y ese número crece, estás limitado por I/O.
Las malas estimaciones aparecen cuando “rows=” está muy alejado de “actual rows=”.

Decisión: Si las malas estimaciones son severas, aumenta el objetivo de estadísticas o añade estadísticas extendidas. Si dominan las lecturas, aumenta la caché o reduce el working set con mejores índices.

Task 14: MySQL/Percona — comprobar lag de replicación y presión de apply

cr0x@server:~$ mysql -e "SHOW REPLICA STATUS\G" | egrep 'Seconds_Behind_Source|Replica_SQL_Running|Replica_IO_Running|Last_SQL_Error'
Replica_IO_Running: Yes
Replica_SQL_Running: Yes
Seconds_Behind_Source: 87
Last_SQL_Error:

Significado: Lag sin errores significa que la réplica no puede aplicar lo suficientemente rápido. Puede ser I/O, CPU o limitaciones de aplicación single-threaded.

Decisión: Aumentar paralelismo de apply en réplicas donde sea seguro, reducir amplificación de escritura y asegurar que el almacenamiento de réplica no sea más lento que el primario.

Task 15: Comprobar síntomas de latencia del sistema de archivos (dmesg en Linux)

cr0x@server:~$ dmesg -T | tail -n 8
[Mon Dec 30 01:11:44 2025] nvme nvme0: I/O 123 QID 6 timeout, completion polled
[Mon Dec 30 01:11:44 2025] blk_update_request: I/O error, dev nvme0n1, sector 182291234 op 0x1:(WRITE) flags 0x800 phys_seg 32 prio class 0

Significado: Si el kernel registra timeouts o errores, deja de culpar a la base de datos.

Decisión: Trátalo como un incidente de infraestructura: reemplaza el dispositivo, arregla controlador/firmware, valida la salud del volumen en la nube, y solo entonces vuelve a los ajustes de BD.

Guía rápida de diagnóstico

Cuando las cosas están lentas, no empiezas editando configs. Empiezas probando a dónde va el tiempo.
Esta guía está afinada para “la producción está en llamas, pero aún queremos comportarnos como adultos”.

Primero: ¿es la base de datos, o la base de datos está esperando otra cosa?

  1. Comprueba latencia y saturación del almacenamiento (iostat -xz, métricas del volumen en la nube). Si %util está al máximo y los await son altos,
    tu “consulta lenta” puede ser una historia de “disco lento”.
  2. Comprueba presión de memoria (vmstat, swapping). El swap convierte a ambos motores en arte trágico de rendimiento.
  3. Comprueba saturación de CPU (load average vs núcleos CPU, top, pidstat).
    Si la CPU está al máximo, pregunta: ¿es ejecución de consultas, mantenimiento en segundo plano, o overhead por compresión/encriptación?

Segundo: ¿las sesiones esperan por locks o por I/O?

  • PostgreSQL: esperas en pg_stat_activity (IO vs Lock vs LWLock). Muchas esperas de Lock significan contención; muchas de IO significan caché/almacenamiento.
  • Percona: SHOW PROCESSLIST estado + InnoDB status. Metadata locks, row locks y log waits cuentan historias diferentes.

Tercero: Identificar consultas principales por tiempo total, no por impresiones

  • PostgreSQL: pg_stat_statements, luego EXPLAIN (ANALYZE, BUFFERS) en los peores offenders.
  • Percona: resúmenes por digest de Performance Schema, luego EXPLAIN y revisión de índices.

Cuarto: Revisa los “asesinos silenciosos” específicos del motor

  • PostgreSQL: retraso de autovacuum, tuplas muertas, transacciones largas, frecuencia de checkpoints.
  • Percona: esperas de redo log, misses de buffer pool, tormentas de flushing, lag de replicación que carga al primario.

Segunda broma (y ya terminamos): Si tu base de datos está lenta y no has comprobado la latencia del disco, básicamente estás depurando con danza interpretativa.

Tres mini-historias del mundo corporativo (anonimizadas, técnicamente reales)

Mini-historia 1: El incidente causado por una suposición equivocada

Una compañía SaaS mediana ejecutaba PostgreSQL para tráfico transaccional y había añadido recientemente una función de “línea de tiempo de actividad del cliente”.
Era una tabla clásica de muchos apends con actualizaciones periódicas de estado. Todo se veía bien en staging.

En producción, la latencia p99 empezó a subir lentamente en semanas. El equipo supuso que era “solo crecimiento” y planeó escalar verticalmente.
Subieron CPU y RAM, se felicitaron y esperaron a que los gráficos se comportaran.

No ocurrió. La RAM adicional ayudó por un tiempo, luego el sistema volvió a su deriva lenta. Durante el siguiente incidente, alguien finalmente ejecutó
pg_stat_user_tables y vio una montaña de tuplas muertas en un puñado de tablas calientes. Autovacuum no daba abasto.
Peor: había transacciones de larga duración de un worker en segundo plano que mantenían snapshots abiertos por horas mientras procesaba una cola.

La suposición equivocada fue simple: “Autovacuum es automático, así que está cubierto.” Es automático como la ropa es automática si tienes lavadora.
Aún tienes que poner la ropa, y realmente no deberías dejar toallas mojadas ahí una semana.

La solución fue aburrida y quirúrgica: acortar el alcance de transacciones en el worker, reducir scale factors de vacuum en tablas calientes, aumentar el número de workers de autovacuum,
y programar un VACUUM (FULL) controlado solo donde fuera absolutamente necesario. El rendimiento se estabilizó. No fue necesaria una compra de hardware heroica.

Mini-historia 2: La optimización que salió mal

Una plataforma de e-commerce ejecutaba Percona Server con tráfico de escrituras pesado: órdenes, pagos, reservas de inventario. Perseguían mayor throughput,
y alguien propuso un cambio “seguro”: aumentar el buffer pool de “grande” a “casi toda la RAM”. La idea era reducir lecturas a disco.

El cambio se desplegó en una ventana de baja carga. Nada explotó. Al día siguiente, bajo pico, el sistema empezó a colgarse.
No lentamente. Colgaba de tal forma que los timeouts de la aplicación parecían problemas de red. La CPU no estaba al máximo. El throughput de disco parecía bien.
Todos miraban dashboards y no aprendían nada.

El culpable: presión de memoria. Al dar a InnoDB casi toda la RAM, dejaron al SO sin margen y sin espacio para memoria por conexión,
buffers de replicación y comportamiento del sistema de archivos. El host empezó a hacer swapping intermitente, lo que convirtió fsync y flush de páginas en picos de latencia impredecibles.
La base de datos no estaba “lenta”. A veces se congelaba.

Revertir el tamaño del buffer pool arregló los síntomas inmediatos. La solución a largo plazo fue más matizada:
ajustar el buffer pool para dejar margen real, limitar max connections y añadir pooling en la capa de aplicación.
También dejaron de hacer DDL al mediodía, lo que, aunque no relacionado con el buffer pool, mejoró la felicidad de todos.

La lección: “más caché” no es un bien universal. Es un trade-off. El SO es parte del sistema, no un detalle molesto al que puedes desalojar.

Mini-historia 3: La práctica aburrida pero correcta que salvó el día

Un equipo de servicios financieros ejecutaba ambos motores: PostgreSQL para reporting y MySQL/Percona para un sistema OLTP legado.
No eran el equipo más llamativo. Eran, sin embargo, silenciosamente efectivos.

Su arma secreta era una rutina estricta y aburrida: revisión semanal del top SQL por tiempo total, chequeo mensual de bloat y salud de vacuum en Postgres,
y validación trimestral del tiempo de recuperación en ambos sistemas. Mantenían configs bajo control de versiones y requerían una nota de cambio explicando
el efecto esperado y el plan de rollback para cada modificación de ajuste.

Un día, un problema de firmware de almacenamiento aumentó la latencia de escritura. Ambas bases se ralentizaron, pero el equipo lo identificó rápidamente porque tenían mediciones base
y sabían cómo se veía un comportamiento normal de fsync y checkpoints. No perdieron horas ajustando settings de base de datos para compensar un dispositivo moribundo.

La victoria operativa no fue heroica. Fue disciplina: una línea base conocida, el hábito de medir antes de cambiar y recuperación ensayada.
El informe del incidente fue corto. El fin de semana se salvó en gran parte. Así se ve el éxito en producción—silencioso.

Errores comunes: síntoma → causa raíz → solución

1) Picos p99 en PostgreSQL cada pocos minutos

Síntoma: Picos de latencia que se correlacionan con ráfagas de I/O de escritura.

Causa raíz: Checkpoints demasiado frecuentes o demasiado “picudos” debido a max_wal_size bajo o mal suavizado de checkpoints.

Solución: Aumentar max_wal_size, aumentar checkpoint_timeout dentro de lo razonable, fijar checkpoint_completion_target (a menudo 0.7–0.9), y verificar la latencia de fsync del almacenamiento.

2) PostgreSQL se deteriora lentamente durante semanas

Síntoma: Consultas que antes eran rápidas se vuelven consistentemente más lentas; el uso de disco crece inesperadamente.

Causa raíz: Autovacuum no da abasto; se acumulan tuplas muertas; transacciones largas impiden la limpieza; las estadísticas se quedan obsoletas.

Solución: Ajustar autovacuum para tablas calientes (bajar scale factors), arreglar transacciones largas, aumentar cuidadosamente workers de autovacuum, y reanalizar tablas críticas.

3) Percona/MySQL se queda estancado bajo carga de escrituras con “log waits”

Síntoma: Throughput colapsa durante escrituras pico; el estado muestra aumento de log waits.

Causa raíz: Redo log demasiado pequeño o fsync del almacenamiento lento; configuraciones de flush que empujan fsync al camino de commit.

Solución: Aumentar capacidad de redo, confirmar latencia de NVMe/volumen en la nube, y revisar perillas de durabilidad solo con una decisión explícita del negocio.

4) Pausas de tráfico MySQL durante cambios de esquema

Síntoma: Muchos hilos muestran “Waiting for table metadata lock”.

Causa raíz: DDL bloquea o está bloqueado por transacciones largas; los metadata locks serializan acceso.

Solución: Usar métodos de cambio de esquema online, matar/evitar transacciones largas, programar DDL en ventanas de baja carga y diseñar migraciones conscientes de locks.

5) Ambos motores: “aumentamos max_connections y empeoró”

Síntoma: Más timeouts, mayor latencia, más cambio de contexto, posible swapping.

Causa raíz: Sobrecarga de concurrencia: demasiadas sesiones activas saturan CPU, memoria o gestor de locks; las colas se trasladan de la app a la base de datos.

Solución: Añadir pooling, fijar límites sensatos de conexión, implementar backpressure y escalar lecturas donde sea posible en lugar de inflar conexiones hasta el caos.

6) Consultas más lentas después de una “optimización de índices”

Síntoma: Latencia de escritura aumenta; lag de replicación aumenta; CPU e I/O suben.

Causa raíz: Demasiados índices aumentan la amplificación de escritura; la “optimización de lectura” no compensa el coste global.

Solución: Eliminar índices no usados, consolidar índices multicolumna y validar con estadísticas de carga reales (no con suposiciones).

Listas de verificación / plan paso a paso

Paso a paso: llevar PostgreSQL a “buen rendimiento” con mínimo ajuste

  1. Fija shared_buffers a una fracción razonable (empieza alrededor del 20–30% de RAM en un host dedicado).
  2. Fija effective_cache_size para reflejar shared buffers + caché del SO (comúnmente 50–75% de RAM).
  3. Establece work_mem de forma conservadora, luego súbelo selectivamente por rol/sesión para consultas intensas; no lo pongas alto globalmente.
  4. Habilita y usa pg_stat_statements para rankear consultas por tiempo total.
  5. Afina checkpoints (max_wal_size, checkpoint_timeout, checkpoint_completion_target) para evitar acantilados de I/O.
  6. Audita la salud de autovacuum semanalmente; aplica ajustes por tabla para tablas calientes.
  7. Usa connection pooling si tienes muchas conexiones cortas o servicios con alto fan-out.
  8. Establece una línea base de latencia de almacenamiento y obsérvala como parte de tu aplicación (porque lo es).

Paso a paso: llevar Percona Server a “buen rendimiento” sin pensamiento mágico

  1. Dimensiona innodb_buffer_pool_size intencionalmente; deja margen para el SO y concurrencia.
  2. Fija capacidad de redo para no estar checkpointeando constantemente; valida log waits.
  3. Decide durabilidad explícitamente: alinea innodb_flush_log_at_trx_commit y sync_binlog con el RPO del negocio.
  4. Establece innodb_io_capacity para coincidir con la realidad del almacenamiento, no con especificaciones de marketing.
  5. Habilita resúmenes por digest y úsalos regularmente.
  6. Mantén max_connections sensato; arregla el comportamiento de conexiones de la app en lugar de inflar el límite.
  7. Planifica el ajuste de replicación (parallel apply, formato de binlog) como parte del rendimiento, no como algo posterior.
  8. Practica cambios de esquema con conciencia de locks; los metadata locks no son una característica sorpresa.

Lista de decisión: “¿Necesito más perillas, o menos problemas?”

  • Si no puedes medir puntos calientes de consultas, ajustar es apostar. Habilita las estadísticas correctas primero.
  • Si no puedes describir tus requisitos de durabilidad, no toques las perillas de flush.
  • Si no puedes acotar la concurrencia, tu base de datos lo hará por ti—con latencia.
  • Si la latencia de tu almacenamiento es inconsistente, tus resultados de ajuste también lo serán.

Preguntas frecuentes

1) ¿Cuál es más rápido por defecto?

Depende de la carga, pero PostgreSQL a menudo se siente “aceptable” antes porque requieren menos ajustes para evitar trampas obvias.
Percona/MySQL suele necesitar elecciones sobre buffer pool y redo/flush para alcanzar su mejor comportamiento bajo OLTP intensivo.

2) ¿Es PostgreSQL “autoajustable” por autovacuum?

No. Autovacuum es automático, no omnisciente. Las tablas calientes frecuentemente necesitan ajuste por tabla, y las transacciones largas pueden sabotearlo.
Trata la salud del vacuum como parte de la gestión de rendimiento.

3) ¿Percona Server es solo MySQL con perillas extra?

Es compatible con MySQL y se ha enfocado históricamente en mejoras de rendimiento y observabilidad. Las “perillas extra” suelen existir porque los operadores pidieron control.
Más control es bueno—hasta que se convierte en complejidad ingobernada.

4) ¿Cuál es la perilla más importante en PostgreSQL?

Si te obligan a elegir una: mantener el mantenimiento saludable—efectividad de autovacuum más prevenir picos de checkpoint. La memoria importa, pero el bloat y los acantilados de escritura arruinan todo.

5) ¿Cuál es la perilla más importante en Percona/MySQL?

innodb_buffer_pool_size. Si está mal, o vas a thrashar disco o vas a dejar al SO sin recursos y a estancarlo. En segundo lugar: configuración de redo/flush alineada con el almacenamiento.

6) ¿Debería fijar shared_buffers de PostgreSQL al 80% de la RAM como un buffer pool?

Usualmente no. PostgreSQL se beneficia de la caché del SO y necesita margen para work_mem, mantenimiento y el resto del sistema.
Empieza más pequeño y mide; “toda la RAM a shared_buffers” es una herida autoinfligida común.

7) ¿Puedo arreglar consultas lentas añadiendo índices hasta que sea rápido?

Puedes, por un tiempo, hasta que las escrituras y la replicación se vuelvan más lentas y la caché se convierta en un cajón de trastos.
Los índices son un coste. Usa estadísticas de consultas para justificar cada uno y elimina el peso muerto.

8) ¿Por qué mis consultas PostgreSQL se vuelven más lentas aunque la CPU esté ociosa?

A menudo I/O waits, locks o bloat. Revisa esperas en pg_stat_activity, observa tuplas muertas y confirma la latencia del almacenamiento.
CPU ociosa no es signo de salud; a veces es señal de estar esperando.

9) ¿Por qué mis consultas MySQL se “atascan” durante migraciones?

Metadata locks. DDL necesita locks y transacciones largas pueden mantenerlos. Usa métodos de migración conscientes de locks y mantén transacciones cortas.

10) ¿Cuál necesita más perillas para buen rendimiento en almacenamiento de bloque en la nube?

Percona/MySQL tiende a demandar más ajuste explícito de I/O y durabilidad porque el comportamiento de fsync es tan central para la latencia de commits.
PostgreSQL también se preocupa profundamente por la latencia de fsync (WAL), pero a menudo son menos perillas para llegar a un comportamiento estable—suponiendo que se ajuste checkpoints.

Próximos pasos que no te avergüenzan

Si eliges entre PostgreSQL y Percona Server estrictamente por “quién necesita más perillas”, haces la pregunta correcta de la manera equivocada.
Pregunta en su lugar: qué perillas son obligatorias para mi carga, y qué tan rápido puede mi equipo diagnosticar problemas cuando la carga cambie.

Pasos prácticos:

  1. Elige una carga representativa (OLTP lecturas, OLTP escrituras, mixto, reporting) y haz benchmark con concurrencia parecida a producción.
  2. Habilita estadísticas de consultas (pg_stat_statements o digests de Performance Schema) y crea un ritual semanal de “top SQL”.
  3. Establece líneas base para latencia de almacenamiento, comportamiento de checkpoint/flush y lag de replicación. Escríbelas.
  4. Limita la concurrencia deliberadamente con pooling y límites sensatos de conexión. No dejes que la app DDoSée la BD educadamente.
  5. Cambia una cosa a la vez, mide y ten un plan de rollback. Controla la configuración en versiones como si importara—porque importa.

PostgreSQL recompensa el mantenimiento constante y buena higiene SQL. Percona Server recompensa elecciones explícitas y control operativo estricto.
Ninguno es “más rápido por naturaleza.” El más rápido es el que puedes operar sin adivinar.

← Anterior
Migas de pan + Navegación Anterior/Siguiente para Sitios de Documentación (Limpio, Rápido, Accesible)
Siguiente →
Por qué la VRAM importará aún más después de 2026

Deja un comentario