Suena la alarma. La aplicación está “arriba” pero todo parece moverse en melaza. La latencia de la API se dispara, las colas crecen y tu CEO descubre el botón de actualizar.
Tienes dos problemas: una ralentización y la incertidumbre. El segundo es peor.
PostgreSQL y Percona Server (compatible con MySQL) pueden ambos rendir rápido a escala. La diferencia, durante un incidente, es qué tan rápido te permiten demostrar qué está ocurriendo realmente:
CPU, E/S, bloqueos, presión de checkpoints, fallos de buffer pool, deuda de autovacuum, estancamiento de replicación, planes malos, o “alguien cambió una cosa diminuta”.
La conclusión: la visibilidad es una característica de producto
Si te importa la respuesta a incidentes, PostgreSQL por lo general te da más “verdad explicable” desde el inicio: eventos de espera, estadísticas por consulta robustas con
pg_stat_statements, y una cultura de exponer el estado interno en SQL claro. Puedes llegar rápido a “estamos bloqueados por locks” o “estamos limitados por E/S en lecturas”.
La superpotencia de Percona Server es que toma la historia de instrumentación de MySQL (que puede ser excelente pero a veces necesita incentivo) y la hace más operativa:
métricas adicionales, mejores valores por defecto para observabilidad y una cadena de herramientas probada (Performance Schema, esquema sys, slow log, pt-query-digest)
que es brutalmente efectiva en producción.
Entonces, ¿quién ofrece mejor visibilidad? En la práctica:
- PostgreSQL gana cuando necesitas una historia unificada sobre ejecución de consultas, esperas, bloqueos, vacuums y comportamiento de planes—especialmente con acceso sólo por SQL.
- Percona Server gana cuando quieres un ecosistema MySQL rico y operativo, instrumentación mejorada y flujos de trabajo probados tipo “toma el slow log, digerelo, arregla los mayores culpables”.
La verdad incómoda: ninguno ayuda si no activaste los ajustes correctos antes del incidente. La visibilidad no es una vibra. Es un archivo de configuración y una política de retención.
Datos históricos y contexto interesante (8 cosas que importan)
- El linaje de PostgreSQL es de investigación ante todo. Desciende de POSTGRES (años 80), y se nota: el estado interno suele exponerse en tablas/vistas que parecen hechas para depurar.
- La historia temprana de rendimiento de MySQL se apoyó en la simplicidad. Durante años, “es rápido” a veces significaba “es opaco”, y el ecosistema compensó con logs y herramientas externas.
- Performance Schema fue un punto de inflexión. El Performance Schema de MySQL evolucionó a un framework de instrumentación serio; es potente, pero no siempre está habilitado/retiene correctamente por defecto.
- Percona popularizó el “MySQL operativo”. Percona Server y sus herramientas hicieron que diagnosticar problemas de MySQL en producción fuera menos arte y más checklist.
-
pg_stat_statementscambió cómo operan los equipos Postgres. Una vez que tienes estadísticas normalizadas de consultas, “consultas principales” se vuelve dato, no discusión. - Los eventos de espera en PostgreSQL son de primera clase. El reporte de wait events de PostgreSQL (y vistas relacionadas) provee una narrativa nítida de “en qué estamos esperando”.
- El buffer pool de InnoDB es bendición y trampa. Cuando está bien dimensionado, todo luce genial. Cuando está mal, persigues “E/S misteriosa” por horas.
- Autovacuum es el recaudador de impuestos de PostgreSQL. Puedes ignorarlo por un tiempo, pero cobrará—con intereses—a través de bloat, escaneos de tablas y picos brutales de latencia.
Qué significa “mejor visibilidad” en incidentes reales
Visibilidad no es “tengo un dashboard.” Visibilidad es la capacidad de responder cuatro preguntas rápidamente, con evidencia:
- ¿Qué cambió? Forma de la consulta, volumen de datos, plan, configuración, esquema, mezcla de carga, comportamiento del host, red.
- ¿A dónde se va el tiempo? CPU, lecturas de disco, escrituras de disco, esperas de locks, fsync, replicación, checkpoints, vacuum/purge, contención.
- ¿Quién es responsable? Qué patrones de consulta, qué usuarios, qué tablas/índices, qué camino de código, qué job.
- ¿Cuál es la mitigación más segura? Matar, limitar, añadir índice, cambiar plan, ajustar memoria, reducir durabilidad, pausar jobs, hacer failover.
PostgreSQL y Percona Server pueden ambos responder estas preguntas. La diferencia real es cuántos pasos se necesitan y con qué frecuencia te ves obligado a adivinar.
Dos métricas importan más que tu orgullo:
- Tiempo hasta la primera hipótesis creíble. No “es la base de datos”, sino “es contención de locks en la tabla X causada por el patrón de consulta Y”.
- Tiempo hasta una mitigación segura. La solución no tiene que ser perfecta; tiene que detener la hemorragia sin corromper datos ni causar una segunda caída.
PostgreSQL: dónde la luz es brillante (y dónde no)
Fortalezas de Postgres: introspección nativa en SQL y verdad de esperas
PostgreSQL expone mucho de “qué hace el motor” a través de vistas del sistema. En un incidente, eso significa que a menudo puedes quedarte dentro de SQL y aún obtener respuestas:
sesiones, locks, eventos de espera, texto de consulta, tiempos de E/S (si están habilitados), progreso de vacuum, replicación y estadísticas de consultas.
Los eventos de espera son la característica subestimada clave. Si las sesiones están atascadas en esperas de Lock, pasas de “por qué va lento” a “quién está bloqueando a quién”.
Si las esperas muestran presión de IO o LWLock, sabes si es disco, churn de caché o contención interna.
Puntos ciegos de Postgres: todavía necesitas habilitar lo bueno antes
PostgreSQL es generoso con la introspección, pero no almacenará historial por consulta indefinidamente a menos que se lo indiques, y no capturará mágicamente
cada sentencia lenta a menos que el logging y las estadísticas estén configurados sensatamente.
pg_stat_statementsdebe estar instalado y dimensionado. Demasiado pequeño y las consultas más calientes desalojan la evidencia.- La instrumentación de tiempos de E/S tiene coste. Puedes activarla, pero no pretendas que es gratis en discos muy cargados.
- El desglose de esperas a nivel de consulta no es tan granular como podrías desear. Sabrás qué espera un backend, pero no siempre una atribución perfecta por consulta sin herramientas extra.
La experiencia práctica de depuración en Postgres
Estar de guardia con Postgres a menudo se siente como: “Ejecuta un puñado de consultas SQL y el motor confiesa.” No siempre es así, pero ocurre con suficiente frecuencia como para que los equipos desarrollen memoria muscular alrededor de ello.
El modo de fallo más común no es la falta de visibilidad—es ahogarse en ella. Si no tienes un flujo de trabajo, terminas mirando
pg_stat_activity como si fuera a parpadear dos veces para decir “el disco está lleno.”
Percona Server: dónde brilla (y sus puntos ciegos)
Fortalezas de Percona: ecosistema MySQL, métricas mejoradas y tooling para incidentes
Percona Server es compatible con MySQL, pero afinado para producción: mejores valores por defecto de instrumentación, variables de estado adicionales y una cultura de “aquí está la perilla, aquí está la métrica, aquí está la forma.”
El flujo de trabajo de Percona toolbox—slow log + digest—sigue siendo una de las vías más rápidas de “va lento” a “aquí están las huellas principales de consultas y su impacto.”
Performance Schema puede ser espectacular cuando está bien configurado: resúmenes de sentencias, eventos de espera, instrumentación de locks, stages y metadata locks.
Combínalo con vistas del esquema sys y obtienes una narrativa cercana a la historia de esperas de Postgres—solo que con más trampas.
Puntos ciegos de Percona: la instrumentación es opcional, y lo opcional suele faltar a las 2 a.m.
La visibilidad en MySQL/Percona históricamente ha dependido de si la activaste. Performance Schema puede estar deshabilitado, dimensionado demasiado pequeño o configurado para no retener lo que necesitas.
Los slow logs pueden estar apagados por “espacio en disco”, y entonces debugueas con intuiciones y una plegaria.
También hay una sutileza: MySQL tiene múltiples formas de observar cosas similares (slow log, Performance Schema, tablas INFORMATION_SCHEMA, variables de estado).
Esa es flexibilidad—hasta que deja de serlo. En un incidente quieres un flujo de trabajo canónico.
Broma #1: Una consulta lenta es como una reunión corporativa—nadie sabe por qué existe, pero todos esperan por un lock.
Playbook de diagnóstico rápido (verificar 1.º/2.º/3.º)
Este es el playbook que quiero en la pared junto al letrero “no reiniciar la base de datos”.
Primero: ¿es la base de datos o el servidor?
- Verificar saturación del host: CPU, iowait, presión de memoria, swapping, profundidad de cola del disco.
- Verificar latencia del almacenamiento: read/write await, picos de fsync, dispositivos atascados, sistemas de archivos llenos.
- Verificar la red: retransmisiones, saturación, latencia extraña hacia los clientes.
Decisión: si el host está en llamas, arregla el host primero. Ninguna optimización de consultas vence a un disco con lecturas de 80ms.
Segundo: ¿la carga está bloqueada (locks) o lenta (recursos)?
- Postgres: eventos de espera + vistas de locks +
pg_stat_activity. - Percona: Performance Schema waits + estado de locks/transacciones de InnoDB + processlist.
Decisión: si está bloqueado, identifica el bloqueador y el tipo de lock. Luego decide si matar, limitar o cambiar el patrón de escritura.
Tercero: identifica las huellas principales de consultas que causan dolor
- Postgres:
pg_stat_statementspor tiempo total y tiempo medio; correlaciona con recuentos de ejecución y E/S. - Percona: slow log +
pt-query-digest; resúmenes de sentencias de Performance Schema.
Decisión: escoge el cambio más pequeño y seguro que reduzca el tiempo total: añadir índice, reescribir consulta, corregir parametrización, actualizar estadísticas, reducir concurrencia o deshabilitar temporalmente un job en lote.
Cuarto: confirma que no es mantenimiento o durabilidad
- Postgres: frecuencia de checkpoints, actividad de autovacuum, retraso en aplicación de replicación.
- Percona: retraso de purge/history list length, edad de checkpoint, stalls de flush, lag de replicación.
Decisión: si el mantenimiento es el culpable, eliges entre rendimiento y deuda. Pagar ahora (vacuum/purge/optimize), o pagar más tarde con peor interés.
Tareas prácticas (comandos, salidas, decisiones)
Estas son tareas reales que puedes ejecutar durante un incidente. Cada una incluye: comando, qué significa la salida y la decisión que tomas.
Mezclo verificaciones a nivel de host con verificaciones a nivel de base de datos porque “la base de datos está lenta” a menudo es “el disco está triste.”
Task 1 — Host: snapshot de CPU e iowait
cr0x@server:~$ mpstat -P ALL 1 3
Linux 6.1.0 (db01) 12/30/2025 _x86_64_ (32 CPU)
11:02:10 AM CPU %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle
11:02:11 AM all 22.10 0.00 6.30 18.40 0.00 0.60 0.00 0.00 0.00 52.60
11:02:11 AM 7 85.00 0.00 10.00 0.00 0.00 0.00 0.00 0.00 0.00 5.00
Significado: un %iowait alto en all sugiere que la latencia del almacenamiento está limitando el progreso; una CPU caliente indica un punto caliente monocore (compresión, consulta única o flush en background).
Decisión: si iowait es alto, pivotar hacia comprobaciones de almacenamiento antes de empezar a reescribir consultas.
Task 2 — Latencia de disco y profundidad de cola
cr0x@server:~$ iostat -x 1 3
Linux 6.1.0 (db01) 12/30/2025 _x86_64_ (32 CPU)
Device r/s w/s r_await w_await aqu-sz %util
nvme0n1 820.0 210.0 18.40 22.10 14.20 98.50
Significado: r_await/w_await en decenas de ms y %util cercano a 100%: el dispositivo está saturado. aqu-sz alto: cola profunda, las peticiones se acumulan.
Decisión: limitar la carga (pool de conexiones, jobs en lote), confirmar que no hay checkpoints/flush desbocados, considerar failover si el almacenamiento del replicante está más sano.
Task 3 — Presión de memoria y swapping
cr0x@server:~$ free -h
total used free shared buff/cache available
Mem: 128Gi 120Gi 1.2Gi 2.0Gi 6.8Gi 3.5Gi
Swap: 16Gi 9.5Gi 6.5Gi
Significado: el uso de swap en un host de base de datos raramente es “aceptable.” Normalmente significa que tu caché está siendo desalojada y la latencia está a punto de convertirse en arte interpretativo.
Decisión: reducir la huella de memoria (bajar el número de conexiones, arreglar work_mem/sort buffers enormes, revisar si el SO está sin caché), y planear un reinicio solo si puedes hacerlo con seguridad.
Task 4 — PostgreSQL: encontrar consultas activas y sus esperas
cr0x@server:~$ psql -X -c "SELECT pid, usename, state, wait_event_type, wait_event, now()-query_start AS age, left(query,120) AS query FROM pg_stat_activity WHERE state <> 'idle' ORDER BY age DESC LIMIT 10;"
pid | usename | state | wait_event_type | wait_event | age | query
------+--------+--------+-----------------+---------------+----------+----------------------------------------------------------
4821 | app | active | Lock | relation | 00:01:42 | UPDATE orders SET status=$1 WHERE id=$2
4977 | app | active | IO | DataFileRead | 00:01:10 | SELECT * FROM orders WHERE customer_id=$1 ORDER BY created
Significado: la primera sesión espera un lock de relación; la segunda está bloqueada por lecturas de archivos de datos. Esos son dos caminos de incidente diferentes: contención de locks vs limitación por E/S.
Decisión: si ves esperas de Lock, ve a los bloqueadores. Si ves esperas de IO, ve al comportamiento de disco y caché.
Task 5 — PostgreSQL: mostrar bloqueadores y sesiones bloqueadas
cr0x@server:~$ psql -X -c "SELECT blocked.pid AS blocked_pid, blocked.query AS blocked_query, blocker.pid AS blocker_pid, blocker.query AS blocker_query FROM pg_locks bl JOIN pg_stat_activity blocked ON blocked.pid=bl.pid JOIN pg_locks kl ON kl.locktype=bl.locktype AND kl.database IS NOT DISTINCT FROM bl.database AND kl.relation IS NOT DISTINCT FROM bl.relation AND kl.page IS NOT DISTINCT FROM bl.page AND kl.tuple IS NOT DISTINCT FROM bl.tuple AND kl.virtualxid IS NOT DISTINCT FROM bl.virtualxid AND kl.transactionid IS NOT DISTINCT FROM bl.transactionid AND kl.classid IS NOT DISTINCT FROM bl.classid AND kl.objid IS NOT DISTINCT FROM bl.objid AND kl.objsubid IS NOT DISTINCT FROM bl.objsubid AND kl.pid != bl.pid JOIN pg_stat_activity blocker ON blocker.pid=kl.pid WHERE NOT bl.granted AND kl.granted LIMIT 5;"
blocked_pid | blocked_query | blocker_pid | blocker_query
------------+----------------------------------------+------------+------------------------------------------
4821 | UPDATE orders SET status=$1 WHERE id=$2 | 4602 | ALTER TABLE orders ADD COLUMN note text
Significado: un ALTER TABLE está bloqueando actualizaciones de producción. Esto no es una “consulta lenta”, es un fallo de coordinación de DDL.
Decisión: decidir si terminar el proceso que bloquea, posponer el cambio de esquema o redirigir las escrituras. Si lo matas, confirma el tiempo de rollback y el impacto.
Task 6 — PostgreSQL: consultas principales por tiempo total (requiere pg_stat_statements)
cr0x@server:~$ psql -X -c "SELECT calls, round(total_exec_time::numeric,1) AS total_ms, round(mean_exec_time::numeric,1) AS mean_ms, rows, left(query,120) AS query FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 5;"
calls | total_ms | mean_ms | rows | query
-------+----------+---------+--------+----------------------------------------------------------
92000 | 540000.0 | 5.9 | 120000 | SELECT * FROM orders WHERE customer_id=$1 ORDER BY created_at DESC LIMIT $2
4100 | 210000.0 | 51.2 | 4100 | UPDATE orders SET status=$1 WHERE id=$2
Significado: la primera consulta domina el tiempo total por volumen. La segunda es individualmente algo lenta pero no necesariamente la culpable principal.
Decisión: optimizar la huella dominante primero (índice, índice covering, reducir columnas seleccionadas, mejor paginación), luego abordar las latencias de cola.
Task 7 — PostgreSQL: comprobar deuda de autovacuum y señales de riesgo de bloat
cr0x@server:~$ psql -X -c "SELECT relname, n_dead_tup, n_live_tup, last_autovacuum, vacuum_count, autovacuum_count FROM pg_stat_user_tables ORDER BY n_dead_tup DESC LIMIT 5;"
relname | n_dead_tup | n_live_tup | last_autovacuum | vacuum_count | autovacuum_count
-----------+------------+------------+-------------------------+--------------+------------------
orders | 8420000 | 21000000 | 2025-12-29 02:14:11+00 | 1 | 14
events | 3200000 | 9000000 | | 0 | 0
Significado: muchos dead tuples y autovacuum faltante en events sugiere bloat y poca cobertura del visibility map, lo que implica más E/S y escaneos lentos.
Decisión: investigar ajustes de autovacuum, transacciones largas que impiden limpieza y programar vacuum dirigido (con cuidado, en producción).
Task 8 — PostgreSQL: comprobar presión de checkpoints
cr0x@server:~$ psql -X -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, buffers_checkpoint FROM pg_stat_bgwriter;"
checkpoints_timed | checkpoints_req | write_s | sync_s | buffers_checkpoint
------------------+-----------------+---------+--------+-------------------
1200 | 3400 | 9800.2 | 420.1 | 81233421
Significado: muchos checkpoints solicitados y buffers de checkpoint enormes pueden correlacionar con picos periódicos de latencia, especialmente en cargas con muchas escrituras.
Decisión: ajustar parámetros de checkpoint (intervalo, completion_target) y verificar que el almacenamiento pueda absorber el patrón de escritura. No “arregles” esto convirtiendo la durabilidad en rumor.
Task 9 — Percona/MySQL: ver quién está ejecutando y quién espera
cr0x@server:~$ mysql -e "SHOW FULL PROCESSLIST\G"
*************************** 1. row ***************************
Id: 31244
User: app
Host: 10.0.12.34:52210
db: prod
Command: Query
Time: 87
State: Waiting for table metadata lock
Info: ALTER TABLE orders ADD COLUMN note TEXT
*************************** 2. row ***************************
Id: 31251
User: app
Host: 10.0.12.18:50122
db: prod
Command: Query
Time: 83
State: updating
Info: UPDATE orders SET status='shipped' WHERE id=?
Significado: los metadata locks pueden congelar toda una aplicación en MySQL/Percona. Si ves “Waiting for table metadata lock”, no estás optimizando consultas—estás resolviendo un enfrentamiento.
Decisión: encontrar el poseedor del lock y matar o esperar; luego arreglar tu proceso de migraciones (tooling DDL online, pasos más pequeños, horas de baja carga).
Task 10 — Percona/MySQL: snapshot de transacciones y estado de locks de InnoDB
cr0x@server:~$ mysql -e "SHOW ENGINE INNODB STATUS\G" | sed -n '1,120p'
=====================================
2025-12-30 11:04:21 INNODB MONITOR OUTPUT
=====================================
TRANSACTIONS
------------
Trx id counter 845112334
Purge done for trx's n:o < 845100000 undo n:o < 0 state: running
History list length 987654
...
LATEST DETECTED DEADLOCK
------------------------
*** (1) TRANSACTION:
TRANSACTION 845112100, ACTIVE 12 sec updating or deleting
...
Significado: un “History list length” muy grande indica que el purge está atrasado, a menudo por transacciones de larga duración. Eso incrementa la retención de undo y puede afectar el rendimiento.
Los deadlocks son normales; los patrones repetidos no lo son.
Decisión: identificar transacciones largas (clientes dormidos, jobs por lotes), corregir el alcance de transacciones en la aplicación y considerar ajustar purga/undo si es crónico.
Task 11 — Percona/MySQL: sentencias principales vía Performance Schema
cr0x@server:~$ mysql -e "SELECT DIGEST_TEXT, COUNT_STAR, ROUND(SUM_TIMER_WAIT/1000000000000,2) AS total_s, ROUND(AVG_TIMER_WAIT/1000000000000,4) AS avg_s FROM performance_schema.events_statements_summary_by_digest ORDER BY SUM_TIMER_WAIT DESC LIMIT 5\G"
*************************** 1. row ***************************
DIGEST_TEXT: SELECT * FROM orders WHERE customer_id = ? ORDER BY created_at DESC LIMIT ?
COUNT_STAR: 92510
total_s: 532.44
avg_s: 0.0058
Significado: misma idea que pg_stat_statements: fingerprints normalizados con recuentos y tiempo. Si esta tabla está vacía, probablemente no configuraste la retención de Performance Schema.
Decisión: optimizar el digest superior y asegurar que los consumidores de Performance Schema estén habilitados y dimensionados para tu carga.
Task 12 — Percona/MySQL: digest del slow log (rápido “quién nos dolió”)
cr0x@server:~$ pt-query-digest /var/log/mysql/mysql-slow.log --limit=5
# 200ms user time, 20ms system time, 30.00M rss, 120.00M vsz
# Rank Query ID Response time Calls R/Call V/M Item
# ==== ================== ============= ===== ======= ===== ====
# 1 0xA1B2C3D4E5F6A7B8 540.2311 50.2% 92000 0.0059 0.02 SELECT orders
# 2 0x1111222233334444 210.1145 19.5% 4100 0.0512 0.10 UPDATE orders
Significado: obtienes una lista clasificada inmediata de lo que consume latencia. Por eso el slow log sigue siendo relevante en 2025: funciona bajo presión.
Decisión: arreglar el puesto #1 primero a menos que puedas probar que el #2 está causando amplificación de locks o fallos en cascada.
Task 13 — PostgreSQL: examinar un plan con tiempos reales de ejecución
cr0x@server:~$ psql -X -c "EXPLAIN (ANALYZE, BUFFERS, VERBOSE) SELECT * FROM orders WHERE customer_id=42 ORDER BY created_at DESC LIMIT 50;"
Limit (cost=0.43..25.88 rows=50 width=312) (actual time=120.114..120.220 rows=50 loops=1)
Buffers: shared hit=120 read=1840
-> Index Scan using orders_customer_created_idx on public.orders (cost=0.43..5120.33 rows=10000 width=312) (actual time=0.080..119.900 rows=50 loops=1)
Index Cond: (orders.customer_id = 42)
Planning Time: 0.320 ms
Execution Time: 120.300 ms
Significado: muchos buffers de read implican fallos de caché; el índice existe pero aún genera E/S (quizás por mala localidad, caché fría o filas demasiado anchas).
Decisión: considerar un índice covering, reducir columnas seleccionadas, mejorar la tasa de aciertos de caché (dimensionamiento de memoria) o abordar la latencia de almacenamiento.
Task 14 — Percona/MySQL: explain con actuales (si se soporta) o analizar la forma del plan
cr0x@server:~$ mysql -e "EXPLAIN SELECT * FROM orders WHERE customer_id=42 ORDER BY created_at DESC LIMIT 50\G"
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: orders
partitions: NULL
type: ref
possible_keys: idx_customer_created
key: idx_customer_created
key_len: 8
ref: const
rows: 10000
filtered: 100.00
Extra: Using where; Using filesort
Significado: “Using filesort” con un ORDER BY puede significar que el índice no satisface el orden, o que el optimizador eligió otra ruta de acceso. Es un multiplicador clásico de latencia.
Decisión: ajustar el orden del índice, asegurar que el ORDER BY coincide con el índice y verificar estadísticas/cardinalidad.
Task 15 — Comprobación de lag de replicación (Postgres)
cr0x@server:~$ psql -X -c "SELECT application_name, state, write_lag, flush_lag, replay_lag FROM pg_stat_replication;"
application_name | state | write_lag | flush_lag | replay_lag
-----------------+-----------+-----------+-----------+------------
replica01 | streaming | 00:00:00 | 00:00:02 | 00:00:15
Significado: replay lag indica que el replicante está aplicando WAL lentamente. Durante la respuesta a incidentes esto importa para la seguridad del failover y expectativas de escalado de lectura.
Decisión: si el lag crece, evita hacer failover a ese replicante a menos que aceptes más pérdida/latencia. Investiga I/O/CPU en el replicante.
Task 16 — Comprobación de lag de replicación (Percona/MySQL)
cr0x@server:~$ mysql -e "SHOW REPLICA STATUS\G" | egrep 'Replica_IO_Running|Replica_SQL_Running|Seconds_Behind_Source|Last_SQL_Error'
Replica_IO_Running: Yes
Replica_SQL_Running: Yes
Seconds_Behind_Source: 48
Last_SQL_Error:
Significado: 48 segundos de retraso no es necesariamente fatal, pero es una advertencia para failover y para expectativas de lectura tras escritura.
Decisión: si el lag se correlaciona con almacenamiento o bloqueos, arregla el cuello de botella subyacente primero; no “resuelvas” reiniciando la replicación a ciegas.
Broma #2: Nada acelera un análisis de causa raíz como darse cuenta de que la configuración “temporal” de depuración está activada desde el último trimestre.
Tres mini-historias corporativas desde la trinchera
Mini-historia 1: El incidente causado por una suposición errónea
Una empresa mediana ejecutaba un SaaS multi-tenant sobre PostgreSQL. Tenían un modelo mental ordenado: “Las lecturas son baratas, las escrituras son caras.”
Así que cuando vieron picos de latencia, asumieron que un job background intensivo en escrituras estaba saturando el disco.
El on-call abrió métricas del host: la utilización del disco era alta, claro. Pero pg_stat_activity mostró que el dolor más fuerte estaba en consultas de lectura.
Los eventos de espera no eran “IO DataFileRead” en todas partes: muchas sesiones estaban atascadas en Lock, y las consultas bloqueadas eran lecturas.
La suposición errónea fue sutil: creían que las lecturas no bloquean. En PostgreSQL, las lecturas pueden esperar si intentan acceder a un objeto bloqueado por DDL,
o si necesitan un buffer fijado por otra cosa, o si se amontonan detrás de una cola de locks. Una migración de esquema había añadido un índice con un modo de bloqueo que no comprendieron totalmente.
La solución no fue “añadir IOPS.” La solución fue operativa: cambiar el proceso de migración para usar patrones online más seguros,
programar DDL invasivo en baja carga e implementar un paso de preflight que verifique el riesgo de adquisición de locks y el tiempo de ejecución esperado.
La lección: la forma más rápida de perder una hora es depurar el problema que esperabas en vez del que tus esperas están reportando.
Mini-historia 2: La optimización que salió mal
Otro equipo ejecutaba Percona Server con un buffer pool agresivamente ajustado y un orgulloso “nunca tocamos el disco”.
Aumentaron el buffer pool “todo lo posible” tras un incremento de tráfico. Los gráficos se veían heroicos por una semana.
Luego vino un incidente en cámara lenta: la latencia tail subió, pero el throughput no. La CPU parecía bien. El disco parecía “ocupado pero no una locura.”
Performance Schema mostró más tiempo en flushing de InnoDB y stages de E/S de archivos que antes, y el slow log empezó a reportar ráfagas de stalls por escrituras.
El desmadre fue la hambruna de memoria del SO. Con el buffer pool comiéndose casi todo, la caché de página del SO y la caché de metadatos del sistema de archivos sufrieron,
y el comportamiento de flushing en background se volvió errático. La máquina empezó a hacer swap en picos raros—lo suficiente para arruinar el percentil 99.
La solución fue embarazosamente sencilla: reducir un poco el buffer pool, dar aire al SO, limitar la concurrencia de conexiones y ajustar el comportamiento de flush para suavizar picos.
La “optimización” había mejorado promedios mientras rompía la cola. La producción vive en la cola.
La lección: no ganas todo tomando toda la memoria para la base de datos. El kernel tiene voto, y vota con latencia.
Mini-historia 3: La práctica aburrida pero correcta que salvó el día
Un equipo de gran empresa ejecutaba tanto PostgreSQL como Percona en productos distintos.
Su trabajo de fiabilidad más valioso no fue tuning exótico; fue instrumentación estandarizada antes de incidentes:
logging de consultas lentas con umbrales sensatos, agregación de sentencias (pg_stat_statements y digests de Performance Schema) y retención consistente.
Una tarde, un despliegue de aplicación causó una ralentización súbita de BD. Nadie discutió de quién era la culpa porque los datos ya estaban.
En Postgres, el fingerprint de consulta principal duplicó su tiempo total con el mismo número de llamadas; EXPLAIN (ANALYZE, BUFFERS) mostró lecturas extra.
En Percona, pt-query-digest marcó una forma de consulta alterada y un nuevo comportamiento “Using filesort”.
La práctica “aburrida” fue un chequeo pre-merge que registraba planes de consulta para endpoints críticos y los comparaba tras cambios.
Eso hizo que la decisión de revertir fuera obvia. Revirtieron rápido, luego arreglaron la consulta y añadieron el índice faltante en un despliegue controlado.
El incidente fue corto no porque fueran genios, sino porque tenían recibos.
Errores comunes: síntoma → causa raíz → solución
Aquí hay modos de fallo que aparecen repetidamente en producción, con soluciones específicas. Esta es la sección que lees cuando tu café tiembla.
1) Síntoma: ralentización global súbita, muchas sesiones “waiting”
- Causa raíz (Postgres): DDL bloqueando o cola de locks detrás de una transacción larga.
- Solución: identificar bloqueador vía
pg_locks/pg_stat_activity, terminar o completar; cambiar estrategia de migraciones.
- Causa raíz (Percona): contención de metadata lock (MDL), a menudo por DDL.
- Solución: encontrar propietario del lock en processlist/Performance Schema; matar sesión; usar prácticas de cambio de esquema online.
2) Síntoma: picos periódicos de latencia cada pocos minutos
- Causa raíz (Postgres): tormentas de checkpoints (demasiado frecuentes o muy puntuales), a veces combinadas con almacenamiento lento.
- Solución: ajustar intervalo de checkpoints y completion_target; asegurar que WAL y datos estén en almacenamiento sensato; verificar comportamiento del background writer.
- Causa raíz (Percona): tormentas de flush, presión de edad de checkpoints, picos de fsync.
- Solución: ajustar flushing de InnoDB y tamaño del redo log; suavizar la carga de escritura; confirmar latencia de escritura del dispositivo.
3) Síntoma: consultas de lectura se ralentizan con el paso de días/semanas
- Causa raíz (Postgres): bloat de tabla/índice por deuda de autovacuum o transacciones largas que impiden limpieza.
- Solución: encontrar dead tuples y historial de vacuum; arreglar transacciones largas; ajustar umbrales de autovacuum por tabla; programar mantenimiento.
- Causa raíz (Percona): fragmentación y estadísticas obsoletas, o churn del buffer pool por crecimiento de datos.
- Solución: actualizar estadísticas; ajustar buffer pool; considerar rebuild/optimize con cuidado; arreglar uso de consultas/índices.
4) Síntoma: “CPU baja pero consultas lentas”
- Causa raíz: espera de I/O, esperas de locks o contención interna. CPU baja no es un cumplido; es una pista.
- Solución: revisar esperas (wait_event_type en Postgres; stages/waits en MySQL), luego latencia de almacenamiento y límites de concurrencia.
5) Síntoma: lag de replicación crece durante picos y luego se recupera
- Causa raíz: el replicante está limitado por I/O al aplicar cambios; o transacciones largas/lotes grandes causan stalls al aplicar.
- Solución: ajustar paralelismo de aplicación donde proceda, reducir transacciones grandes, mover lecturas pesadas fuera del replicante y confirmar almacenamiento del replicante.
6) Síntoma: la “solución” a una consulta lenta ayuda breve y luego empeora
- Causa raíz: trataste un síntoma. Culpables comunes: efectos de calentamiento de caché, cambios de parámetros (inestabilidad de plan) o deriva de estadísticas.
- Solución: confirmar con fingerprints de consultas a lo largo del tiempo, comparar planes para diferentes parámetros y establecer rutina de stats/ANALYZE (o mejorarla) con monitorización.
Listas de verificación / plan paso a paso para depuración repetible
Checklist A: Instrumentación pre-incidente (hazlo un martes tranquilo)
- Habilitar y dimensionar agregación de consultas. Postgres:
pg_stat_statements. Percona: digest de sentencias de Performance Schema + vistas sys. - Habilitar logging de consultas lentas con un umbral real. No lo pongas en 10 segundos y lo llames observabilidad.
- Retener suficiente historial para cubrir tu incidente. Si rotas logs cada hora, perderás la escena del crimen.
- Capturar métricas de esperas/locks. Eventos de espera en Postgres; waits/stages en MySQL; vistas de locks.
- Estandarizar flujos de EXPLAIN. Postgres:
EXPLAIN (ANALYZE, BUFFERS)para consultas principales en staging. MySQL: EXPLAIN más muestreo en tiempo de ejecución via Performance Schema. - Basar la latencia de almacenamiento. Conocer tu normal de
r_await/w_awaity comportamiento de fsync.
Checklist B: Durante el incidente (bucle de contención de 15 minutos)
- Confirmar impacto visible al usuario. ¿Qué endpoints? ¿Lectura vs escritura? ¿Inquilino único o global?
- Salud del host primero. CPU, iowait, memoria/swap, saturación de disco.
- Identificar bloqueos. Grafo de locks en Postgres; MDL/locks de InnoDB en MySQL.
- Huellas principales de consultas. Sentencias de Postgres; digests de MySQL o digest del slow log.
- Mitigar de forma segura. Matar al bloqueador, limitar concurrencia, pausar jobs en lote o hacer failover si un replicante está sano y consistente.
- Probar mejora. Volver a ejecutar las mismas comprobaciones. No declares victoria porque el gráfico “se ve más tranquilo”.
Checklist C: Después del incidente (convertir dolor en ingeniería)
- Escribir la cronología de una página. Qué cambió, cuándo, cómo se detectó, qué se arregló.
- Añadir guardarraíles. Controles de migración, tests de regresión de planes, límites de concurrencia, circuit breakers.
- Instrumentar señales que faltaron. Si tuviste que adivinar, añade una métrica o log para no adivinar la próxima vez.
- Hacer la solución aburrida. La mejor respuesta a incidentes es un script y un runbook, no un héroe.
Preguntas frecuentes
1) Si sólo puedo escoger una característica de visibilidad “imprescindible”, ¿cuál es?
La agregación de fingerprints de consultas con tiempo y recuentos. En Postgres eso es pg_stat_statements. En Percona/MySQL son los digests de sentencias (Performance Schema) y/o slow log + digest.
Sin eso, perseguirás consultas individuales en vez de la forma de la carga de trabajo.
2) ¿Cuál de los dos es mejor para decirme “estamos esperando por locks”?
PostgreSQL suele ser más claro y rápido: wait_event_type más joins de locks sencillos te dan una historia inmediata.
Percona puede hacerlo perfectamente, pero necesitas saber dónde mirar (estados del processlist, Performance Schema waits, estado de InnoDB) y tenerlo habilitado.
3) ¿El slow query log está obsoleto si tengo Performance Schema?
No. El slow query log es una grabadora de caja negra de baja tecnología. A menudo sobrevive cuando tu instrumentación sofisticada no estuvo dimensionada correctamente.
Usa ambos si puedes asumir la sobrecarga y almacenamiento.
4) ¿Por qué veo “Using filesort” en MySQL si pensé que tenía el índice correcto?
Porque el índice puede no coincidir con la dirección/columnas del ORDER BY, o el optimizador puede elegir otra vía basada en las estadísticas.
Arregla con un índice que coincida con el sort, verifica cardinalidad y confirma que no estás seleccionando tantas columnas que se vuelve caro de todos modos.
5) PostgreSQL se siente más lento después de añadir más índices. ¿No es al revés?
Los índices aceleran lecturas y penalizan escrituras. Más índices significan mayor amplificación de escritura, más trabajo de vacuum y a veces peor comportamiento de caché.
Mantén los índices que “pagan renta”. Elimina los que no.
6) ¿Cuál es la razón más común por la que Postgres “aleatoriamente” se ralentiza?
Una mezcla de deuda de autovacuum y transacciones largas que bloquean la limpieza. No es aleatorio; es mantenimiento diferido que se hace visible.
7) ¿Cuál es la razón más común por la que MySQL/Percona “aleatoriamente” se ralentiza?
Presión de flush/I/O más contención de locks (incluyendo MDL), a menudo desencadenado por un cambio de carga o una migración. Y sí, a veces es un buffer pool subdimensionado o sobredimensionado.
8) ¿Puedo depurar ralentizaciones de forma segura sin ejecutar comandos pesados?
Sí. Usa snapshots ligeros primero: tablas de sentencias top, processlist/pg_stat_activity y métricas I/O de alto nivel.
Reserva EXPLAIN ANALYZE pesados para una muestra controlada y evita ejecutarlos en sistemas ya saturados salvo que conozcas el coste.
9) ¿Cuál es más fácil de operar con conocimientos limitados de base de datos en guardia?
Si inviertes en runbooks, cualquiera sirve. De serie, el modelo de PostgreSQL “consulta vistas SQL, obtén la verdad” es más fácil para generalistas.
Percona puede ser igual de diagnosticable, pero solo si estandarizas Performance Schema y workflows de slow log antes de tiempo.
Próximos pasos: cómo elegir y cómo instrumentar
Aquí viene una cita, porque es la moraleja operativa de la historia. La postura ampliamente repetida sobre fiabilidad de Werner Vogels puede resumirse así
(idea parafraseada): Si lo construyes, lo operas
— Werner Vogels.
La depurabilidad es parte de operarlo, no un añadido opcional cuando las cosas se ponen picantes.
Si el mayor riesgo de tu organización es “no sabemos qué pasa durante incidentes”, elige el sistema y la configuración que haga más fácil recuperar la verdad.
Mi sesgo:
-
Elige PostgreSQL si quieres visibilidad nativa fuerte en SQL, reporte de esperas de primera clase y un camino directo desde sesiones → esperas → locks → sentencias → planes.
Recompensa la disciplina en vacuum y higiene de estadísticas. -
Elige Percona Server si estás comprometido con el ecosistema MySQL, quieres mejoras operativas y vas a ejecutar realmente Performance Schema y slow logging correctamente.
Recompensa tooling estandarizado y manejo cuidadoso de concurrencia/flush.
Próximos pasos prácticos que puedes hacer esta semana
- Habilitar fingerprints de consultas y verificar retención. Confirma que puedes responder “top 5 consultas por tiempo total en la última hora”.
- Activar logging lento con un umbral defendible y rotación. Conserva suficiente historial para cubrir la duración típica de un incidente.
- Escribir el runbook de ralentización de 15 minutos. Comprobaciones de host, checks de locks, top consultas, opciones de mitigación.
- Practicar un game day. Inyectar carga, simular un lock, simular saturación de I/O y cronometrar qué tan rápido el on-call llega a una hipótesis creíble.
- Hacer las migraciones aburridas. La mayoría de “ralentizaciones de base de datos” son fallos de coordinación en cambios de esquema disfrazados.
La mejor visibilidad es la que no piensas hasta que la necesitas—porque ya sabes exactamente dónde está y ya está recolectando la evidencia.