03:07. Suena el pager. La aplicación está “caída”, el canal ejecutivo está “arriba” y alguien está escribiendo
“¿perdimos datos?” en mayúsculas. Ahora no se otorgan puntos por arquitectura.
Se otorgan puntos por restaurar el servicio y saber en qué confiar.
Esta es una comparación práctica de operar PostgreSQL frente a Percona Server (compatible con MySQL) cuando estás cansado,
bajo presión y el radio de impacto es real. No se trata de cuál es “mejor”. Se trata de qué es más sencillo de operar a las 3AM,
con las perillas, registros y rutas de recuperación que realmente existen en producción.
Qué significa realmente “más sencillo a las 3AM”
“Más sencillo” no es menos características. Es menos incertidumbre en la ruta de fallo. A las 3AM necesitas:
(1) visibilidad clara de qué está roto, (2) una o dos palancas de recuperación confiables,
(3) comportamiento de rendimiento predecible bajo carga y (4) copias de seguridad que restauren en el primer intento.
PostgreSQL y Percona Server pueden operarse bien. Pero tienden a castigar errores diferentes.
PostgreSQL castiga la negligencia (autovacuum, tablas hinchadas, consultas malas que derraman a disco).
Percona Server castiga las asunciones vagas sobre replicación y la sensación de “es solo MySQL” cuando el subsistema InnoDB
está silenciosamente sufriendo.
Mi sesgo, basado en incidentes: PostgreSQL suele ser más sencillo de recuperar correctamente porque las semánticas de durabilidad
son explícitas y la replicación es coherente. Percona Server suele ser más sencillo de escalar rápidamente porque su
ecosistema (y las herramientas de Percona) facilitan patrones comunes de MySQL—hasta que aparezca una deriva sutil de replicación,
confusión de GTID o fantasías de multi-escritura.
Otro sesgo, también ganado: la base de datos para la que ya tienes buenos runbooks es más sencilla. “Familiar” vence a “mejor”
en el momento, por eso la madurez operativa importa más que los benchmarks.
Un chiste corto, como corresponde: A las 3AM toda base de datos es un sistema distribuido, porque tus compañeros están distribuidos entre zonas horarias y opiniones.
Hechos interesantes y contexto histórico (los fragmentos que explican las cicatrices)
- Linaje de PostgreSQL: PostgreSQL surgió del proyecto POSTGRES en UC Berkeley en los años 80, y su ADN de “hacerlo bien” se nota en las semánticas transaccionales y la extensibilidad.
- Equipaje por defecto de MySQL en sus inicios: MySQL históricamente se distribuía con valores por defecto no transaccionales (como MyISAM), lo que formó a una generación para tratar la durabilidad como opcional. InnoDB cambió las reglas, pero los ecos son culturales.
- Historia de origen de Percona Server: Percona creó una distribución alrededor de MySQL con instrumentación y mejoras de rendimiento porque los operadores querían más visibilidad de la que ofrecía el upstream.
- WAL vs binlog: el write-ahead log (WAL) de PostgreSQL es fundamental para la seguridad ante fallos y la replicación. El binary log (binlog) de MySQL es combustible para replicación y un historial lógico—potente, pero también fuente de deriva si se trata con ligereza.
- MVCC en ambos, pero no igual: Ambos sistemas usan ideas MVCC, pero el vacuum de PostgreSQL es una tarea operativa de primera clase, mientras que el comportamiento de undo/redo y purge de InnoDB suele manifestarse como “longitud de la lista de historial” o problemas con undo tablespace.
- Evolución de la replicación: la replicación de MySQL comenzó basada en sentencias, luego en filas y luego mixta. Esa historia importa porque los hábitos basados en sentencias aún filtran supuestos sobre determinismo.
- Maturidad de la replicación física de PostgreSQL: la replicación por streaming y los replication slots hicieron que el archivado continuo y las réplicas fueran más robustos, pero introdujeron un nuevo pie-ciego: los slots pueden retener WAL para siempre si los olvidas.
- Impacto de las herramientas de backup de Percona: XtraBackup se volvió amigo del operador para copias físicas en caliente en el mundo MySQL, especialmente cuando las volcadas lógicas eran demasiado lentas. Pero también creó una brecha operativa: “backup completado, restauración… quizá” si no se practican los restores.
- Conservadurismo en la configuración por defecto: los valores por defecto de PostgreSQL son intencionalmente conservadores; debes ajustar memoria y comportamiento de checkpoints para cargas reales. Los valores por defecto de MySQL también han mejorado, pero aún verás sistemas en producción con buffers de InnoDB peligrosamente grandes y ajustes de flush riesgosos porque alguien persiguió un gráfico.
Modelos mentales operativos: cómo falla cada base de datos
PostgreSQL: “todo va bien hasta que el autovacuum no lo hace”
PostgreSQL suele fallar de formas diagnósticables: alta carga, consultas largas, contención de locks, espera de I/O,
picos de checkpoints o bloat que hace que todo vaya más lento. Cuando se cae, la recuperación suele ser determinista:
reproducir WAL y volver a la vida. La parte complicada no es la recuperación tras un crash; es mantenerse fuera de la espiral lenta
donde el bloat y los planes malos generan más I/O, lo que provoca consultas más largas, más tuplas muertas y peor vacuum.
Las palancas a las 3AM en PostgreSQL suelen ser: cancelar consultas fuera de control, reducir la contención de locks, arreglar índices/planes malos,
ajustar memoria y checkpoints, y asegurarse de que el archivado de WAL esté sano. Es un sistema que recompensa
la higiene operativa aburrida.
Percona Server: “la replicación es fácil hasta que no lo es”
Percona Server hereda el modelo operativo de MySQL: un primario (o “source”), réplicas (“replica”/“slave”),
replicación asíncrona y una caja de herramientas con muchas opciones. Percona añade visibilidad (mejoras en performance schema,
compatibilidad con Percona Toolkit y parches operativos según la versión).
A las 3AM, tu mayor enemigo no siempre es “la base de datos está caída”. Es “la base de datos está arriba, pero no consistente”,
o “las réplicas están retrasadas”, o “fallamos y ahora las escrituras van al lugar equivocado”.
InnoDB también puede quedarse en estados en que está vivo pero efectivamente bloqueado por I/O, presión del redo log o transacciones largas que impiden el purge.
Una cita (idea parafraseada), por su requisito: idea parafraseada: en ingeniería de confiabilidad, la esperanza no es una estrategia; los sistemas necesitan bucles de retroalimentación y recuperaciones probadas.
— atribuida a la práctica común de SRE, inspirada en la disciplina SRE.
Guía de diagnóstico rápido (primero/segundo/tercero)
Primero: ¿es CPU, memoria o I/O?
- Revisa carga y saturación: carga alta no significa que la CPU sea el problema. Puede ser espera de I/O o caos en la cola de runnable.
- Busca espera de I/O: si el disco está saturado, optimizar consultas no ayudará hasta que detengas la hemorragia (limitar, matar a los culpables, pausar jobs, añadir capacidad, reducir picos de checkpoint/flush).
- Revisa presión de memoria: hacer swap en un host de base de datos es una caída en cámara lenta.
Segundo: ¿estamos bloqueados por locks o esperando almacenamiento?
- PostgreSQL: encuentra PIDs bloqueantes, transacciones largas y autovacuum detenido por locks. Si hay replicación, revisa WAL sender/receiver y backlog de slots.
- Percona Server: revisa transacciones activas, deadlocks, esperas por locks de filas InnoDB y el estado de los hilos de replicación. Valida que estés escribiendo al primario previsto.
Tercero: ¿el sistema está divergiendo (replicación, corrupción o fallo parcial)?
- El retraso de replicación cambia tu respuesta al incidente. Si tu réplica tiene 30 minutos de retraso, la conmutación puede implicar pérdida de datos.
- Revisa los logs de error por errores CRC, fallos de fsync, disco lleno o fallos de archivado de redo/WAL. Estos no son problemas “para después”.
- Confirma que las copias de seguridad son viables antes de hacer cambios destructivos. En ambos ecosistemas, es demasiado fácil asumir lo contrario.
Tareas prácticas de operaciones con comandos (y qué decidir según la salida)
Estos son el tipo de comandos que realmente ejecutas cuando el mundo está en llamas. Cada tarea incluye:
el comando, qué significa la salida y qué decisión tomar a continuación.
Task 1 (host): confirmar espera de I/O vs saturación de CPU
cr0x@server:~$ iostat -xz 1 3
Linux 6.5.0 (db01) 12/30/2025 _x86_64_ (16 CPU)
avg-cpu: %user %nice %system %iowait %steal %idle
12.31 0.00 4.88 35.22 0.00 47.59
Device r/s w/s rkB/s wkB/s await aqu-sz %util
nvme0n1 220.0 780.0 8800.0 54000.0 18.4 7.12 98.7
Significado: %iowait es alto y el dispositivo está cerca del 100% de utilización. Estás limitado por I/O, no por CPU.
Decisión: deja de generar más I/O: mata las consultas más pesadas, pausa jobs por lotes, reduce la presión de checkpoints (Postgres) o de flush (InnoDB), y revisa si hay disco lleno y errores de RAID/NVMe.
Task 2 (host): detectar swap y presión de memoria
cr0x@server:~$ free -m
total used free shared buff/cache available
Mem: 64000 54000 1200 600 8800 6200
Swap: 8192 3900 4292
Significado: se está usando swap. En un host de base de datos, eso suele ser daño autoinfligido.
Decisión: reduce ahora los consumidores de memoria (picos de conexiones, work_mem/sort buffers gigantes, buffer pool demasiado grande). Si no puedes, mueve carga fuera del nodo o añade RAM. Swap junto con alta espera de I/O es un cóctel clásico de outage.
Task 3 (PostgreSQL): ver qué está ejecutando y qué está esperando
cr0x@server:~$ psql -XAtc "select pid, usename, state, wait_event_type, wait_event, now()-query_start as age, left(query,80) from pg_stat_activity where state <> 'idle' order by age desc limit 10;"
9231|app|active|Lock|transactionid|00:12:33.18291|update orders set status='paid' where id=$1
8120|app|active|IO|DataFileRead|00:09:10.09121|select * from order_items where order_id=$1
...
Significado: tienes una espera por lock (transactionid) y esperas de I/O (DataFileRead). La consulta más antigua probablemente está bloqueando a las demás.
Decisión: encuentra al bloqueador y decide si cancelarlo/terminarlo. También verifica si la espera de I/O es sistémica (ver iostat) o una sola consulta/índice malo.
Task 4 (PostgreSQL): encontrar bloqueadores rápido
cr0x@server:~$ psql -XAtc "select blocked.pid as blocked_pid, blocker.pid as blocker_pid, now()-blocker.query_start as blocker_age, left(blocker.query,80) as blocker_query from pg_locks blocked join pg_locks blocker on blocker.locktype=blocked.locktype and blocker.database is not distinct from blocked.database and blocker.relation is not distinct from blocked.relation and blocker.page is not distinct from blocked.page and blocker.tuple is not distinct from blocked.tuple and blocker.virtualxid is not distinct from blocked.virtualxid and blocker.transactionid is not distinct from blocked.transactionid and blocker.classid is not distinct from blocked.classid and blocker.objid is not distinct from blocked.objid and blocker.objsubid is not distinct from blocked.objsubid and blocker.pid <> blocked.pid join pg_stat_activity blocked_act on blocked_act.pid=blocked.pid join pg_stat_activity blocker on blocker.pid=blocker.pid where not blocked.granted and blocker.granted limit 5;"
9231|7011|00:48:02.01123|alter table orders add column foo text
Significado: un ALTER TABLE ha estado reteniendo locks durante 48 minutos. Eso puede congelar tu aplicación.
Decisión: termina el DDL si no es seguro, luego establece una política: cambios de esquema online, timeouts de lock y ventanas para DDL.
Task 5 (PostgreSQL): comprobar retraso de replicación y backlog de WAL
cr0x@server:~$ psql -XAtc "select application_name, state, sync_state, pg_size_pretty(pg_wal_lsn_diff(sent_lsn, replay_lsn)) as byte_lag from pg_stat_replication;"
replica01|streaming|async|512 MB
Significado: tu réplica está atrasada por ~512MB de WAL. Eso puede ser segundos o minutos según la tasa de escrituras.
Decisión: no hagas failover a ciegas. Comprueba si el retraso está disminuyendo. Si está creciendo, tienes un problema downstream (red, disco, replay).
Task 6 (PostgreSQL): revisar replication slots por retención de WAL descontrolada
cr0x@server:~$ psql -XAtc "select slot_name, active, pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) as retained from pg_replication_slots;"
logical_slot_1|f|97 GB
Significado: un slot lógico inactivo está reteniendo 97GB de WAL. Disco lleno es ahora un incidente programado.
Decisión: vuelve a activar el consumidor o elimina el slot si está realmente muerto. Luego añade monitorización/alertas para la retención de slots.
Task 7 (PostgreSQL): comprobar presión de checkpoints
cr0x@server:~$ psql -XAtc "select checkpoints_timed, checkpoints_req, round(checkpoint_write_time/1000.0) as write_s, round(checkpoint_sync_time/1000.0) as sync_s from pg_stat_bgwriter;"
120|980|8420|3110
Significado: los checkpoints solicitados (980) eclipsan a los temporizados (120). El sistema está forzando checkpoints frecuentes, usualmente por volumen de WAL y un max_wal_size pequeño.
Decisión: aumenta max_wal_size y ajusta checkpoint_completion_target; luego verifica que el almacenamiento pueda manejar escrituras continuas. No “arregles” esto apagando fsync a menos que disfrutes desarrollarte profesionalmente en el mercado laboral.
Task 8 (Percona Server): identificar el primario y salud de replicación
cr0x@server:~$ mysql -NBe "SHOW SLAVE STATUS\G" | egrep "Slave_IO_Running|Slave_SQL_Running|Seconds_Behind_Master|Last_SQL_Error"
Slave_IO_Running: Yes
Slave_SQL_Running: No
Seconds_Behind_Master: NULL
Last_SQL_Error: Error 'Duplicate entry' on query. Default database: 'app'. Query: 'INSERT INTO ...'
Significado: el hilo IO está corriendo, el hilo SQL está parado, el retraso es desconocido. La réplica está rota, no sólo atrasada.
Decisión: no promuevas esta réplica. Repara la replicación (saltar/ reparar con máxima precaución) o reconstruye. También diagnostica por qué ocurrió un duplicado—a menudo una multi-escritura mal configurada o sentencias no determinísticas.
Task 9 (Percona Server): confirmar modo GTID y seguridad de failover
cr0x@server:~$ mysql -NBe "SHOW VARIABLES LIKE 'gtid_mode'; SHOW VARIABLES LIKE 'enforce_gtid_consistency';"
gtid_mode ON
enforce_gtid_consistency ON
Significado: GTID está habilitado y la consistencia está forzada. Eso hace que la herramienta de failover y el re-pointing de réplicas sea más limpio.
Decisión: si haces failover planificado/no planificado, prefiere topologías con GTID habilitado. Si está desactivado, espera posiciones de binlog manuales y más riesgo a las 3AM.
Task 10 (Percona Server): leer el estado del motor InnoDB por locks y purge
cr0x@server:~$ mysql -NBe "SHOW ENGINE INNODB STATUS\G" | sed -n '1,120p'
...
TRANSACTIONS
------------
Trx id counter 123456789
Purge done for trx's n:o < 123450000 undo n:o < 0 state: running but idle
History list length 987654
LIST OF TRANSACTIONS FOR EACH SESSION:
---TRANSACTION 123456111, ACTIVE 1865 sec
...
Significado: la longitud de la lista de historial es enorme; el purge no puede mantenerse al día, a menudo por transacciones de larga duración.
Decisión: encuentra y termina transacciones largas (o corrige el comportamiento de la app). De lo contrario, el undo crece, el rendimiento se degrada y acabarás “optimizando” con un reinicio—es decir, apagar y encender con pasos extra.
Task 11 (Percona Server): detectar deadlocks y decidir qué matar
cr0x@server:~$ mysql -NBe "SHOW ENGINE INNODB STATUS\G" | egrep -n "LATEST DETECTED DEADLOCK|TRANSACTION|WAITING FOR THIS LOCK"
2345:LATEST DETECTED DEADLOCK
2361:*** (1) TRANSACTION:
2388:*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
2410:*** (2) TRANSACTION:
Significado: están ocurriendo deadlocks. InnoDB suele resolver soltando una transacción.
Decisión: si los deadlocks aumentan, busca nuevos caminos de código o índices faltantes. Matar hilos al azar rara vez es la solución; corregir patrones de acceso sí lo es.
Task 12 (MySQL/Percona): confirmar presión del buffer pool y tasa de aciertos
cr0x@server:~$ mysql -NBe "SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_reads'; SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read_requests';"
Innodb_buffer_pool_reads 9876543
Innodb_buffer_pool_read_requests 1234567890
Significado: las lecturas desde disco en el buffer pool son significativas; compara tasas de crecimiento a lo largo del tiempo. Si las lecturas desde disco suben rápidamente, tu caché no está reteniendo el working set.
Decisión: aumenta el buffer pool (si la RAM lo permite), reduce el working set (índices, arreglos de consultas) o deja de fingir que los discos giratorios “están bien porque son principalmente lecturas.”
Task 13 (PostgreSQL): encontrar las consultas principales por tiempo total (pg_stat_statements)
cr0x@server:~$ psql -XAtc "select round(total_exec_time) as total_ms, calls, round(mean_exec_time,2) as mean_ms, left(query,90) from pg_stat_statements order by total_exec_time desc limit 5;"
983412|1203|817.33|select * from events where user_id=$1 order by created_at desc limit 50
...
Significado: la consulta con mayor tiempo total es frecuente y relativamente lenta. Ese es tu ROI.
Decisión: añade el índice correcto, arregla ordenación/paginación o ajusta la forma de la consulta. Luego vuelve a comprobar. No ajustes parámetros del kernel antes de arreglar la consulta obvia.
Task 14 (PostgreSQL): comprobar señales de bloat (tuplas muertas) y autovacuum
cr0x@server:~$ psql -XAtc "select relname, n_live_tup, n_dead_tup, round(100.0*n_dead_tup/nullif(n_live_tup+n_dead_tup,0),2) as dead_pct from pg_stat_user_tables order by n_dead_tup desc limit 5;"
orders|12000000|4800000|28.57
events|90000000|11000000|10.89
Significado: grandes cuentas de tuplas muertas. Si autovacuum no puede mantenerse al día, el rendimiento de consultas decaerá y los índices también se hincharán.
Decisión: ajusta autovacuum para tablas calientes, considera VACUUM manual (o VACUUM FULL en ventanas controladas) y corrige patrones de transacción que impiden que las tuplas sean limpiadas (transacciones largas).
Task 15 (Percona Server): ver consultas actualmente en ejecución y su tiempo
cr0x@server:~$ mysql -NBe "SHOW FULL PROCESSLIST;" | head
12345 app 10.0.2.15:44210 appdb Query 35 Sending data SELECT ... FROM big_table ...
12346 app 10.0.2.16:55122 appdb Sleep 1200 NULL
Significado: una consulta ha estado ejecutándose durante 35 segundos y está “Sending data” (a menudo escaneando u ordenando). También hay conexiones en sleep largas.
Decisión: mata u optimiza a los culpables, limita max connections o usa pooling, e investiga por qué las conexiones quedan en sleep (fugas de la app, configuración de pool incorrecta).
Task 16 (host): confirmar disco lleno antes de perseguir fantasmas
cr0x@server:~$ df -h /var/lib
Filesystem Size Used Avail Use% Mounted on
/dev/nvme0n1p3 900G 890G 10G 99% /var/lib
Significado: prácticamente te quedó sin disco. Tanto PostgreSQL como InnoDB se comportan mal aquí, solo en diferentes dialectos de dolor.
Decisión: libera espacio de inmediato (logs antiguos, backups rotados, WAL/binlogs rotados con precaución), expande el volumen y luego realiza un postmortem sobre las políticas de retención.
Copias de seguridad y recuperación: la verdad a las 3AM
PostgreSQL: las copias son sencillas, las restauraciones son donde te ganas la confianza
PostgreSQL te ofrece dos modos de backup principales:
lógico (pg_dump) y físico (base backup + WAL). Las copias lógicas son portables,
lentas a escala y excelentes para migraciones. Las copias físicas son rápidas de restaurar y buenas para recuperación ante desastres,
pero necesitan archivado de WAL (o streaming continuo) para permitir recuperación a un punto en el tiempo.
La simplicidad operativa en Postgres es conceptual: si entiendes WAL, entiendes recuperación tras crash,
replicación y PITR. Es un modelo mental con distintas herramientas.
Los peligros operativos también son consistentes: si el archivado de WAL falla, PITR falla. Si los replication slots retienen WAL,
los discos se llenan. Si no prácticas la restauración, tu “backup” es una carpeta reconfortante de mentiras.
Percona Server: XtraBackup es excelente, pero debes respetar la tubería de restauración
Los operadores de Percona Server suelen usar:
volcados lógicos (mysqldump/mysqlpump) y copias físicas en caliente (XtraBackup).
XtraBackup es rápido y usualmente la respuesta correcta para conjuntos de datos grandes. También introduce una realidad operativa:
una copia física no es “un solo archivo”. Es un árbol de directorios, metadatos y un paso de prepare/apply-log.
Si tu ruta de restauración no está automatizada y ensayada, te traicionará en el momento que la necesites.
El PITR en MySQL/Percona a menudo depende de binlogs más una copia base. Eso es poderoso pero operativamente más delicado:
debes rastrear retención de binlogs, formato de binlog, modo GTID y la secuencia de aplicación.
¿Quién es más sencillo a las 3AM para recuperación?
Si tu organización es disciplinada con el archivado de WAL y pruebas de restauración, la recuperación en PostgreSQL suele ser más sencilla y predecible.
Si tu organización ya está profundamente en el mundo MySQL y tienes automatización endurecida para restaurar XtraBackup, Percona puede ser extremadamente fluido.
El factor decisivo no es la disponibilidad de herramientas. Es si tienes un runbook practicado y automatizado de restauración y ejercicios de staging.
Replicación y conmutación: qué se rompe y qué tan ruidoso es
Replicación de PostgreSQL: menos modos, menos sorpresas raras
La replicación por streaming de PostgreSQL es física: las réplicas reproducen WAL. Eso la hace consistente con cómo el primario
confirma cambios. La replicación lógica existe, pero la mayoría de topologías de conmutación dependen de replicación física.
Operativamente, esto es buena noticia: menos formatos de replicación, menos problemas de “esta sentencia no es determinística”.
Aun así debes gestionar retraso de replicación, particiones de red y herramientas de promoción. Pero el modo de fallo típico a las 3AM
es obvio: “la réplica está atrasada” o “la réplica no puede seguir porque el disco es lento”.
Replicación de Percona Server: flexible, madura y fácil de malinterpretar
La replicación de MySQL ha mejorado enormemente con los años: GTID, replicación basada en filas, opciones semi-sync,
aplicadores multi-hilo. Percona Server sigue este ecosistema y a menudo expone más instrumentación.
El peligro a las 3AM es humano: la gente asume que la replicación asíncrona es “básicamente síncrona”, o asume consistencia read-after-write
en réplicas, o asume que la conmutación es solo un cambio de DNS. Entonces descubren que el retraso de replicación no es una sugerencia; es física.
Si ejecutas Percona Server, sé religioso con:
formato de binlog basado en filas, GTID en todas partes y conmutación automatizada que también ponga vallas (fencing) al primario antiguo.
La parte de fencing es cómo evitas escrituras en split-brain—una de las clases de incidente más caras.
Ajuste de rendimiento: cambios seguros vs arrepentimiento
Ajustes de PostgreSQL que realmente ayudan a las 3AM
Los incidentes de rendimiento en PostgreSQL suelen estar relacionados con planes de consulta y I/O. Palancas seguras:
ajustar el número de conexiones (pooling), corregir índices faltantes y ajustar checkpoints/WAL para evitar tormentas periódicas de escritura.
Ajustes de memoria como work_mem pueden ayudar pero también son una fuente común de autosabotaje: es por operación, por hash, por sesión.
Los cambios más “seguros a las 3AM” son los que reducen la carga sin cambiar la corrección: cancelar una consulta, añadir un índice faltante
(con cuidado, quizá concurrentemente), ajustar timeouts de sentencia y reducir la concurrencia.
Ajustes de Percona Server que realmente ayudan a las 3AM
InnoDB suele ser el centro. Palancas seguras:
asegurar que el buffer pool tenga un tamaño sensato, mantener innodb_flush_log_at_trx_commit y sync_binlog en ajustes durables a menos que tengas una aceptación formal del riesgo,
y asegurarte de que no te estés asfixiando con demasiadas conexiones o costo de scheduling de hilos.
Muchas “ganancias” de rendimiento en MySQL son en realidad intercambios de durabilidad. Se ven bien hasta que ocurre un evento de energía o un kernel panic.
Si debes cambiar configuraciones de durabilidad, hazlo como una decisión de negocio consciente, no como un experimento a medianoche.
Segundo chiste corto, como corresponde: Apagar fsync es como quitar el detector de humo porque hace ruido—brevemente pacífico, luego educativo.
Realidades de almacenamiento y sistemas de archivos: I/O, durabilidad y sorpresas
Ambas bases de datos son motores de almacenamiento con opiniones. Si tu almacenamiento miente, ellos persistirán fielmente esa mentira.
La pregunta de simplicidad a las 3AM a menudo se reduce a “¿cómo se comporta esta base de datos bajo dolor I/O?”
PostgreSQL bajo dolor de I/O
- Picos de checkpoints pueden crear tormentas periódicas de latencia si están mal ajustados.
- Autovacuum puede convertirse en héroe (previniendo bloat) o villano (si compite con la carga en discos lentos).
- Archivado de WAL es una dependencia fuerte para PITR; un archivo roto es un incidente, no una advertencia.
InnoDB bajo dolor de I/O
- Presión del redo log y comportamiento de flushing pueden causar bloqueos si el subsistema de logs está limitado.
- Transacciones largas impiden el purge y causan bloat/undo, que a su vez generan más I/O.
- Flushing de páginas sucias y checkpointing pueden colapsar el throughput cuando los discos se saturan.
Consejo para operadores: elige tus batallas de almacenamiento
Si estás en almacenamiento de bloques en cloud, prueba tus peores IOPS, no el promedio. Si estás en NVMe local,
planifica la falla de dispositivo y el comportamiento de reconstrucción. Y en cualquier caso: monitoriza latencia de disco, profundidad de cola y capacidad del sistema de archivos.
Incidentes por disco lleno son vergonzosamente comunes porque los problemas de almacenamiento son silenciosos hasta que no lo son.
Tres mini-historias corporativas (realistas, anonimizadas, técnicamente exactas)
1) Incidente causado por una suposición errónea: “las lecturas en réplicas siempre están actualizadas”
Una compañía SaaS mediana ejecutaba Percona Server con un primario y dos réplicas. El documento de arquitectura decía:
“Lecturas van a réplicas, escrituras al primario.” El balanceador obedeció esa regla.
Nadie escribió la segunda regla: “Algunas lecturas deben ser consistentes con la escritura más reciente del usuario.”
Un lanzamiento de producto aumentó las escrituras. El retraso de replicación subió de “normalmente despreciable” a “notable”.
Empezaron a llegar tickets de soporte: los clientes guardaban ajustes, refrescaban y los ajustes volvían atrás. La app no estaba perdiendo datos.
Estaba leyendo datos antiguos desde las réplicas. Desde la perspectiva del usuario, eso es indistinguible de pérdida de datos.
A las 3AM el on-call intentó las maniobras habituales: reiniciar pods de la app, aumentar conexiones DB, escalar réplicas.
Empeoró las cosas. Más conexiones incrementaron la contención de escrituras en el primario, lo que aumentó el lag y la inconsistencia.
El sistema se comportó exactamente como fue diseñado—simplemente no como se supuso.
La solución fue aburrida y efectiva. Enrutar las rutas “read-after-write” al primario (o usar stickiness de sesión),
añadir enrutamiento consciente del lag (no enviar lecturas a una réplica por encima de un umbral) y dejar expectativas claras en el código:
“consistencia eventual es aceptable aquí, no allí.” También implementaron fencing para failover basado en GTID,
porque el incidente reveló lo casual que se trataba la conmutación.
Lección operativa: la replicación de Percona puede ser sólida, pero no te rescatará de tratar la replicación asíncrona como magia.
Postgres tiene la misma física, pero los equipos con frecuencia diseñan en torno a ella antes porque el lag de streaming suele monitorizarse como “bytes de WAL detrás”, que se siente más concreto que “segundos detrás del maestro.”
2) Optimización que salió mal: “más memoria para ordenar más rápido”
Un equipo de plataforma de datos usaba PostgreSQL para cargas tipo analytics en un clúster compartido. Tenían una cola de consultas lentas
con grandes sorts y hashes. Alguien propuso aumentar significativamente work_mem. El entorno de pruebas mejoró.
Los gráficos sonrieron. El cambio se desplegó.
Dos horas después el primario empezó a hacer swap. La latencia explotó. Autovacuum se quedó atrás.
Luego apareció retraso de replicación porque el replay de WAL en la réplica se retrasó por la contención de disco.
El canal de incidentes se llenó con los sospechosos habituales: “¿red?”, “¿bug del kernel?”, “¿nos están DDoSeando?”
Lo que pasó fue simple: work_mem es por operación. Bajo concurrencia, un work_mem grande
se multiplica en consumo real de memoria. La base de datos no “usa más memoria para una consulta”. Usó más memoria para cientos.
Una vez en swap, el sistema pasó la noche thrashing, realizando costoso I/O para soportar el sobrecompromiso de memoria.
El rollback solucionó el dolor inmediato. La mejora real fue más matizada: añadieron los índices correctos,
redujeron la concurrencia para informes pesados y usaron timeouts de consulta y colas de recursos (a nivel del planificador de la app)
para que una carga ruidosa no pudiera secuestrar todo el nodo.
Lección operativa: cambios que parecen seguros en aislamiento pueden ser catastróficos bajo concurrencia. En Postgres, las configuraciones de memoria
son peligrosamente afiladas. En Percona/MySQL, el equivalente suele ser “hacer el buffer pool enorme”
olvidando la caché del sistema de archivos, la reserva de memoria del SO o las necesidades de memoria para backup/restore.
3) Práctica aburrida pero correcta que salvó el día: ejercicios de restauración y listas de promoción
Una compañía ligada a pagos ejecutaba PostgreSQL con una política estricta: ejercicios mensuales de restauración a un entorno de staging,
y trimestrales “promocionar una réplica” en días de juego. Era el tipo de práctica que nunca gana premios internos porque
no entrega features. También hace que los incidentes sean más cortos y menos dramáticos, lo cual es profundamente impopular entre quienes gustan del drama.
Una noche, un controlador de almacenamiento se volvió poco saludable y empezó a devolver errores de I/O intermitentes. PostgreSQL empezó a registrar
fallos de fsync. El equipo no debatió si los logs eran “reales”. Su runbook trató los fallos de fsync como un riesgo de “stop-the-world”.
Pusieron vallas al nodo, promovieron una réplica y movieron el tráfico.
El momento clave: ya sabían que la réplica podía promoverse limpiamente porque lo habían hecho repetidamente.
También sabían que su cadena PITR estaba intacta porque los ejercicios de restauración verificaron archivos WAL y backups base.
Cuando la dirección preguntó “¿estamos a salvo?” el on-call pudo responder con evidencia, no con optimismo.
Lección operativa: la práctica convierte un incidente a las 3AM en una lista de verificación. Las herramientas de PostgreSQL encajan bien con esta disciplina,
pero el mismo enfoque funciona para Percona también—especialmente si validas las restauraciones con XtraBackup y los procedimientos de aplicar binlogs regularmente.
Errores comunes: síntoma → causa raíz → solución
1) Síntoma: uso de disco que sube sin parar en PostgreSQL
Causa raíz: replication slot inactivo reteniendo WAL, o archivado de WAL fallando y acumulándose los WAL.
Solución: inspecciona pg_replication_slots, elimina slots no usados, repara el consumidor y alerta sobre tamaño de WAL retenido. Valida archive_command y permisos.
2) Síntoma: picos periódicos de latencia cada pocos minutos (PostgreSQL)
Causa raíz: checkpointing agresivo debido a un max_wal_size pequeño o ajustes de checkpoint mal afinados.
Solución: sube max_wal_size, pon checkpoint_completion_target más cerca de 0.9, asegura que el almacenamiento pueda sostener escrituras continuas y confirma que no estás saturando I/O.
3) Síntoma: consultas se vuelven más lentas con los días; los índices parecen “menos efectivos” (PostgreSQL)
Causa raíz: bloat de tablas e índices por vacuum insuficiente o transacciones largas que impiden limpieza.
Solución: ajusta autovacuum por tabla caliente, elimina transacciones largas y programa reindex/VACUUM apropiadamente.
4) Síntoma: la réplica muestra Seconds_Behind_Master: NULL (Percona/MySQL)
Causa raíz: el hilo SQL se detuvo por error (clave duplicada, tabla faltante, deriva de esquema).
Solución: revisa Last_SQL_Error, decide si reconstruir o reparar cuidadosamente. Prefiere GTID y despliegues de esquema consistentes para evitar deriva.
5) Síntoma: “aumentaron los deadlocks” tras un despliegue (Percona/MySQL)
Causa raíz: nuevos caminos de consulta cambiaron el orden de locks o un índice faltante causa locks más amplios y tiempos de retención más largos.
Solución: analiza los logs de deadlock, añade índices y aplica orden consistente de transacciones en el código de la aplicación.
6) Síntoma: InnoDB se queda estancado con alta longitud de history list (Percona/MySQL)
Causa raíz: transacciones largas impiden el purge; a veces grandes transacciones de lectura o transacciones inactivas abiertas.
Solución: identifica y termina transacciones largas, establece timeouts sensatos y corrige el manejo de conexiones de la app. Monitoriza la longitud de history list.
7) Síntoma: tras una conmutación, se escriben en dos nodos (cualquier ecosistema)
Causa raíz: no hay fencing; clientes siguen conectados al primario antiguo; riesgo de split-brain.
Solución: implementa fencing a nivel de red/load balancer; aplica escritor único vía automatización y health checks; bloquea que el primario antiguo acepte escrituras.
8) Síntoma: backups “exitosos”, restauraciones fallidas (cualquier ecosistema)
Causa raíz: backups no probados; WAL/binlogs faltantes; permisos/rutas incorrectas; claves de cifrado ausentes; pasos de restauración sin documentar.
Solución: programa ejercicios de restauración, automatiza la restauración, verifica checksums y trata los objetivos de tiempo de restauración como requisitos de producción.
Listas de verificación / plan paso a paso
Checklist A: triaje a las 3AM (funciona para ambos)
- Confirma alcance del impacto: ¿servicio único o todo? ¿Es latencia, errores o corrección de datos?
- Revisa saturación del host: CPU, memoria, espera de I/O, disco lleno.
- Revisa vivacidad de la base de datos: ¿puedes conectar? ¿Las consultas progresan o están atascadas?
- Identifica a los principales culpables: consultas más largas, cadenas de espera de locks, tormentas de conexiones.
- Revisa estado de replicación antes de cualquier failover: lag, hilos parados, retención de WAL, etc.
- Detén la hemorragia: mata consultas fuera de control, pausa jobs por lotes, limita tráfico, reduce carga.
- Solo entonces afina: cambios cautelosos de configuración que reduzcan presión; evita intercambios de corrección/durabilidad.
- Toma una decisión de recuperación: permanecer y estabilizar vs conmutar vs restaurar.
Checklist B: pasos de “estabilización segura” para PostgreSQL
- Encuentra bloqueadores y termina si es necesario (especialmente DDL largos reteniendo locks).
- Cancela consultas fuera de control para reducir I/O y contención de locks.
- Revisa archivado de WAL y retención de slots para prevenir cascadas por disco lleno.
- Verifica salud de autovacuum en las tablas más calientes; ajusta por tabla si es necesario.
- Confirma presión de checkpoints; ajusta WAL/checkpoint en una ventana tranquila, no en medio del pánico, salvo que la alternativa sea outage.
Checklist C: pasos de “estabilización segura” para Percona Server
- Confirma qué nodo es el primario escribible; verifica que la app esté escribiendo ahí.
- Revisa hilos de replicación; no promuevas una réplica rota.
- Inspecciona estado de InnoDB: transacciones largas, presión de purge, deadlocks.
- Reduce tormentas de conexiones; considera tope temporal de conexiones o arreglos de pooling.
- Valida ajustes de durabilidad antes de cambiarlos; si los cambias, documenta el riesgo y el plan de reversión.
Paso a paso: practicar restauraciones (lo que hace que las 3AM sean sobrevivibles)
- Elige un conjunto de backups de producción y restáuralo en un entorno aislado.
- Para PostgreSQL: restaura base backup, reproduce WAL hasta un timestamp objetivo, ejecuta chequeos de integridad (consultas + pruebas de humo de la app).
- Para Percona/XtraBackup: restaura el directorio, aplica logs (prepare), inicia servidor, aplica binlogs/GTID según sea necesario para PITR, valida con pruebas de humo.
- Mide el tiempo de restauración y documentalo como tu RTO real.
- Repite hasta que puedas hacerlo desde un runbook sin improvisación.
Preguntas frecuentes
1) Si solo me importan las operaciones a las 3AM, ¿debería elegir PostgreSQL?
Si comienzas desde cero y valoras semánticas de recuperación predecibles, sí, PostgreSQL suele ser la apuesta más segura.
Pero si tu equipo ya tiene runbooks maduros y tooling MySQL/Percona, la madurez operativa vence a la simplicidad teórica.
2) ¿Es Percona Server más difícil de operar que “MySQL vanilla”?
Generalmente no. Percona Server suele elegirse porque mejora la visibilidad del operador y la compatibilidad con herramientas de rendimiento.
La complejidad viene de las decisiones de replicación y topología de MySQL, no del branding Percona.
3) ¿Cuál es más probable que me sorprenda con uso de disco inesperado?
PostgreSQL sorprende con retención de WAL (especialmente replication slots) y bloat si se descuida el vacuum.
Percona/MySQL sorprende con retención de binlogs, crecimiento de undo/history y backups acumulados.
En cualquier caso: el disco es un SLO de primera clase.
4) ¿Cuál es el enfoque de copia de seguridad más simple y fiable para cada uno?
PostgreSQL: backups físicos base más archivado de WAL para PITR, con ejercicios regulares de restauración.
Percona: estrategia full/incremental con XtraBackup más retención de binlogs para PITR, con drills regulares de restauración y validación GTID.
5) ¿Cuál es más fácil de conmutar de forma segura?
La conmutación en PostgreSQL es conceptualmente limpia (promocionar una réplica física), pero debes gestionar enrutamiento de clientes y fencing.
La conmutación en Percona/MySQL puede ser fluida con GTID y automatización, pero el split-brain y la rotura de replicación son modos de fallo más comunes.
6) ¿Cuál es el “self-own” más común a las 3AM en PostgreSQL?
Dejar acumular transacciones largas y problemas de vacuum hasta que el bloat y la contención se conviertan en outage.
El segundo más común es una mala configuración de archivado de WAL o replication slots olvidados que llenan el disco.
7) ¿Cuál es el “self-own” más común a las 3AM en Percona Server?
Tratar la replicación asíncrona como síncrona y promover la réplica equivocada, o conmutar sin fencear al primario antiguo.
También: “optimización” mediante intercambios de durabilidad que luego provocan pérdida de datos durante un crash.
8) ¿Puedo hacer que cualquiera sea “simple a las 3AM” sin importar la elección?
Sí. La receta es aburrida: monitorización consistente, backups probados, procedimientos de failover estandarizados, propiedad clara de cambios de esquema
y un playbook de incidentes que comience por saturación y comprobaciones de corrección.
9) ¿Cuál es más fácil de depurar rendimiento de consultas bajo presión?
PostgreSQL con pg_stat_statements y eventos de espera claros es excelente para diagnóstico dirigido.
Percona/MySQL también tiene buena instrumentación (performance schema, estado InnoDB), pero los equipos a menudo la subutilizan.
El sistema más fácil es el que tu equipo practica mensualmente, no el que admiras trimestralmente.
Próximos pasos prácticos
Si estás eligiendo entre PostgreSQL y Percona Server basándote en la “simplicidad a las 3AM”, toma la decisión según la realidad operativa:
las habilidades de tu equipo, tu automatización y tu tolerancia a modos de fallo específicos.
- Elige una topología “camino dorado” (un primario, réplicas definidas, método de conmutación definido) y prohíbe variaciones ad-hoc.
- Escribe el runbook de 3AM ahora: comprobaciones de saturación, comprobaciones de locks, comprobaciones de replicación, procedimiento en disco lleno y una lista de “no hacer”.
- Practica restauración hasta que sea una tarea rutinaria. Si no se practica, no es una copia de seguridad.
- Instrumenta los riesgos reales: WAL/slots/vacuum en Postgres; hilos de replicación/GTID/retención de binlog/purge de InnoDB en Percona.
- Fencea tus conmutaciones para que no puedas escribir a dos primarios. Esa es la diferencia entre un incidente y una catástrofe.
Finalmente, sé honesto sobre lo que quieres: si quieres menos piezas móviles y semánticas más coherentes, PostgreSQL tiende a ser más tranquilo a las 3AM.
Si quieres un ecosistema MySQL con buen tooling y tu organización ya lo ejecuta bien, Percona Server puede ser igual de calmado—si respetas la replicación y la durabilidad.