La mayoría de las discusiones sobre bases de datos empiezan como filosofía y acaban como un informe de incidentes. Tu aplicación funciona—hasta que necesitas dos cosas a la vez: semántica Postgres y disponibilidad global. Entonces aparece la factura. A veces es una factura de rendimiento. A veces es una factura operativa. A veces es una factura del tipo «¿por qué la página de pago se queda colgada solo en São Paulo?».
Si estás decidiendo entre “un Postgres único, bien gestionado” y “Postgres distribuido estilo Raft, por todas partes”, esta es la visión basada en producción: qué falla, qué es caro, qué es brillante y qué es excesivo.
Lo que realmente estás eligiendo (no son las funciones)
Sobre el papel, PostgreSQL y YSQL de YugabyteDB pueden parecer ambos “SQL con tablas, índices, transacciones, JSON y un planificador de consultas”. Esa no es la decisión. La decisión es:
- ¿Quieres escalar mayormente mejorando una única instancia de base de datos (máquina más grande, discos más rápidos, consultas ajustadas, réplicas de lectura, particionado), o
- ¿Quieres escalar distribuyendo el almacenamiento y el consenso de la base de datos entre nodos, aceptando una complejidad base mayor para obtener tolerancia a fallos y crecimiento horizontal?
PostgreSQL es el estándar de oro para “simple que puede ser sofisticado”. Te permite ejecutar un sistema de producción limpio donde el trabajo de rendimiento es sobre índices, vacuum, memoria y no hacer tonterías con picos de conexiones.
YugabyteDB es una base de datos SQL distribuida con una API compatible con Postgres (YSQL) construida sobre una capa de almacenamiento distribuido (DocDB) y consenso Raft. Quiere ser la respuesta cuando tus requisitos suenan como una nota de rescate: “debe sobrevivir a la pérdida de nodos, debe escalar escrituras, debe ejecutarse en zonas/regiones, debe ser tipo Postgres, debe seguir siendo ACID”.
La arista afilada: una base de datos distribuida cambia la física de tu aplicación. Muchas consultas se vuelven “un poco en red”. Algunas se vuelven “muy en red”. También empezarás a preocuparte por el emplazamiento, líderes, divisiones de tablets y la diferencia entre transacciones locales y globales. Puedes conservar tu SQL, pero estás cambiando un tipo de pericia por otro.
Regla empírica que defenderé: Si tu mayor dolor es “no podemos mantener Postgres arriba” o “siempre golpeamos CPU en un único escritor”, no empieces con SQL distribuido. Empieza con Postgres aburrido bien hecho. Si tu mayor dolor es “nuestro negocio exige activo-activo entre zonas/regiones con bajo RPO y sin cuello de botella de un único escritor”, YugabyteDB (u otro similar) se convierte en una opción real.
Historia rápida y hechos que importan en producción
Un poco de contexto ayuda a cortar el marketing. Aquí hay hechos concretos que cambian cómo operas estos sistemas:
- Los orígenes de PostgreSQL se remontan a POSTGRES (1986), mucho antes de que “cloud native” fuera un destello en la presentación de un VC. El proyecto PostgreSQL moderno lleva décadas lanzando versiones fiables y la cultura valora la corrección por encima de la novedad.
- MVCC en Postgres (control de concurrencia multi-versión) es por lo que los lectores en su mayoría no bloquean a los escritores. También es la razón por la que existe vacuum y por la que los “tuples muertos” son un tema operativo real.
- La replicación por streaming (basada en WAL) se volvió común en la era de Postgres 9.x, habilitando standbys calientes prácticos. Esto moldeó el patrón HA por defecto: un primario, réplicas y orquestación de failover.
- La replicación física de Postgres es todo o nada a nivel de instancia. Es excelente para failover de clúster completo, no para fragmentar la carga de escritura entre muchos primarios sin un particionado cuidadoso a nivel de aplicación.
- Raft se convirtió en el motor de consenso de los sistemas distribuidos modernos en los años 2010. Es popular porque es más fácil de razonar que Paxos, no porque sea gratis. Cada escritura paga cierto impuesto de coordinación.
- YugabyteDB usa un motor de almacenamiento distribuido (DocDB) inspirado en diseños tipo LSM. Eso significa compactions, SSTables y comportamientos de E/S diferentes al almacenamiento en heap de Postgres.
- “Distributed SQL” es un segundo intento a una idea antigua. Bases de datos shared-nothing y transacciones distribuidas no son nuevas; lo nuevo es mejor automatización, mejor red y la voluntad de gastar más CPU para simplificar el diseño de la aplicación.
- Google Spanner (2012) normalizó la idea de ACID distribuido globalmente con consistencia fuerte, al precio de una ingeniería cuidadosa de tiempo/consenso. Muchos sistemas (incluida la categoría de mercado) tomaron la ambición prestada, aunque difieren en implementación.
- Las extensiones de Postgres son una superpotencia (PostGIS, pg_stat_statements, etc.). En sistemas “compatibles con Postgres”, el soporte de extensiones suele ser parcial, y eso cambia lo que puedes instrumentar y cómo resuelves problemas.
Broma #1: Una base de datos distribuida es simplemente una base de datos normal que ha descubierto que la teletransportación es poco fiable y ahora guarda rencor por ello.
Diferencias de arquitectura que cambian tu vida con el pager
PostgreSQL: una realidad primaria, más réplicas
En la producción clásica de Postgres, tienes un primario escribible. Las réplicas siguen mediante WAL. El failover promociona una réplica. Las lecturas pueden descargarse, pero las escrituras son de un solo nodo.
Esto no es una debilidad; es un diseño que mantiene el núcleo más sencillo y predecible. Con buenos discos, diseño de esquema sensato y pool de conexiones, Postgres puede hacer mucho. Y los modos de fallo están bien entendidos: saturación de disco, lag de replicación, bloat, mal comportamiento del autovacuum, picos de checkpoints, consultas malas y picos de conexiones.
La victoria operativa es que normalmente puedes responder “¿dónde está la verdad?” con “en el primario”. Eso parece pequeño hasta que estás depurando un heisenbug a las 3 a.m.
YugabyteDB: almacenamiento distribuido, líderes de tablet y consenso por todos lados
YugabyteDB divide los datos en tablets (shards). Cada tablet se replica (típicamente RF=3). Una tablet tiene un líder y seguidores. Las escrituras pasan por Raft para confirmarse. Las lecturas pueden servirse desde líderes o seguidores según la configuración de consistencia y las rutas de consulta.
El resultado: puedes perder nodos y seguir atendiendo tráfico. Puedes escalar almacenamiento y rendimiento de escritura añadiendo nodos. Pero también has introducido:
- Problemas de colocación de líderes (la latencia depende de dónde estén los líderes).
- Problemas de tablets calientes (un shard se ve golpeado y se convierte en el cuello de botella).
- Trabajo en segundo plano (compactions, división de tablets, reequilibrado) que compiten con el tráfico de primer plano.
- Más maneras de estar “up” pero lento (existe quórum, pero la latencia en la cola puede ser fea).
El modelo operativo cambia. No gestionas solo un proceso de base de datos; gestionas un pequeño sistema distribuido con su propio plano de control y semánticas de fallo.
Diferencias del motor de almacenamiento: heap+WAL vs realidad tipo LSM
Postgres almacena filas en archivos heap, usa WAL para durabilidad y depende de vacuum para recuperar espacio porque MVCC mantiene versiones antiguas hasta que es seguro eliminarlas.
El almacenamiento subyacente de YugabyteDB se comporta más como un motor LSM: las escrituras son estructuradas en log y luego compactadas. Eso a menudo significa amplificación de escritura y deuda de compactación que pueden volverse problemas de rendimiento. No es “peor”, es diferente. Tus discos y tu monitorización deben ajustarse a la diferencia.
Transacciones: local vs distribuido es donde vive tu latencia
Las transacciones de Postgres son locales a la instancia. El bloqueo y las reglas de visibilidad son todo en proceso. La red no forma parte de la latencia de commit.
En un sistema SQL distribuido, algunas transacciones tocan múltiples tablets. Eso implica coordinación extra. Si esas tablets están en distintas zonas/regiones, ahora tu camino de commit incluye latencia WAN. La base de datos aún puede ser correcta. Tus usuarios seguirán molestos.
Implicación práctica: En YugabyteDB, el modelado de datos y las decisiones de localidad (tablespaces/placement, claves de partición, patrones de acceso) pueden marcar la diferencia entre “lo suficientemente rápido” y “¿por qué cada petición es 80 ms más lenta que ayer?”.
Latencia, consistencia y coste: el triángulo del que no te escapas
Hablemos de las restricciones con las que no puedes negociar:
Latencia: la línea base importa más que las medias
En Postgres, una búsqueda indexada simple puede ocupar unos cientos de microsegundos hasta un par de milisegundos en hardware decente, asumiendo aciertos de caché y un sistema tranquilo. La latencia en la cola suele venir de paradas de I/O, contención de locks o trabajo basura como vacuum retrasado.
En sistemas distribuidos, la latencia base incluye coordinación. Incluso si la mediana parece bien, tu p95/p99 puede saltar cuando se mueven líderes, cuando un nodo tiene un pico de GC, cuando las compactions disparan I/O o cuando una transacción entre tablets cruza al participante más lento.
Consistencia: lo que tu gente de producto entiende vs lo que hace tu código
Postgres te da consistencia fuerte dentro de la instancia, y la replicación suele ser asíncrona a menos que configures replicación síncrona. Eso significa que eliges entre “sin pérdida de datos ante fallo del primario” y “no añadir latencia a cada commit”. Puedes usar síncrono, pero es una decisión de negocio disfrazada de archivo de configuración.
YugabyteDB suele usar replicación síncrona a través del grupo Raft de una tablet para las escrituras. Esa es una postura de durabilidad por defecto más fuerte, pero la latencia depende de la colocación de réplicas. También puedes configurar compensaciones de consistencia de lectura. Tu sistema te permitirá hacerlo rápido; también te permitirá hacerlo raro.
Coste: pagas la resiliencia con máquinas y trabajo extra
Un clúster Postgres bien gestionado podría ser: un primario potente, dos réplicas, un pooler de conexiones y buenas copias de seguridad. Tu palanca de escalado es “primario más grande” hasta que deja de serlo.
Un clúster YugabyteDB quiere múltiples nodos incluso para cargas pequeñas, porque la redundancia es parte del diseño. Con factor de replicación 3, tu coste bruto de almacenamiento es aproximadamente 3x (más overhead), y necesitas holgura para reequilibrado y compactions. Estás comprando suavidad operativa bajo fallo, pero pagas en nodos, red y complejidad.
Broma #2: Lo agradable de los algoritmos de consenso es que garantizan acuerdo—más o menos sobre cuánto va a durar tu despliegue.
Una idea parafraseada sobre fiabilidad (una cita, mantenida honesta)
Idea parafraseada, atribuida a John Allspaw: “El comportamiento real de tu sistema es lo que hace bajo estrés y fallo, no lo que prometía el diagrama de arquitectura.”
Compatibilidad con Postgres: lo que “estilo Postgres” te da—y lo que no
YSQL de YugabyteDB habla el protocolo wire de Postgres y aspira a las semánticas de Postgres. Eso es valioso: drivers existentes, ORMs y mucha transferencia de conocimiento SQL.
Pero “compatible” no es “idéntico”, y los sistemas de producción viven en las diferencias. Aquí están los puntos en los que los equipos tropiezan:
- Extensiones: En Postgres, las extensiones resuelven problemas reales (observabilidad, tipos, indexación). En YugabyteDB, el soporte de extensiones es más limitado. Si dependes de una extensión específica, podrías estar rediseñando en lugar de migrando.
- Planificador y características de rendimiento: Aunque una consulta sea SQL válido, los costes de ejecución difieren. Los escaneos distribuidos y los joins distribuidos pueden comportarse como “bien en staging, caros en prod” cuando el tamaño de los datos cruza un umbral.
- Matices de aislamiento y bloqueo: La meta es comportamiento tipo Postgres, pero el control de concurrencia distribuido tiene restricciones. Lee la letra pequeña sobre niveles de aislamiento y reintentos de transacción.
- Herramientas operativas: Postgres tiene un banco amplio de herramientas. YugabyteDB tiene sus propias herramientas, métricas y flujos operativos. Obtendrás buena visibilidad, pero tendrás que reaprender algunos instintos.
Si tu aplicación usa SQL estándar, índices estándar y tu mayor problema es escalar más allá de un solo primario, YugabyteDB puede sentirse como magia. Si tu aplicación depende mucho de características y extensiones específicas de Postgres, el Postgres distribuido estilo puede sentirse como mudarte de apartamento y descubrir que tu sofá no cabe en el nuevo ascensor.
Tareas prácticas: comandos, salidas, decisiones (12+)
Estos son los chequeos reales que ejecuto (o pido que ejecuten) al decidir si Postgres es suficiente, si un clúster YugabyteDB está sano o por qué algo está lento. Cada uno incluye: comando, qué significa la salida y qué decisión tomar.
Task 1 — Postgres: confirmar estado de replicación y lag
cr0x@server:~$ psql -h pg-primary -U postgres -d postgres -c "select application_name, state, sync_state, write_lag, flush_lag, replay_lag from pg_stat_replication;"
application_name | state | sync_state | write_lag | flush_lag | replay_lag
------------------+-----------+------------+-----------+-----------+------------
pg-replica-1 | streaming | async | 00:00:00 | 00:00:00 | 00:00:01
pg-replica-2 | streaming | async | 00:00:00 | 00:00:00 | 00:00:02
(2 rows)
Significado: Las réplicas están en streaming; el replay lag es de segundos bajos. Decisión: El escalado de lecturas y la postura de failover parecen normales. Si el lag crece, investiga red/disco en réplicas o consultas de larga duración que retrasen el replay.
Task 2 — Postgres: encontrar las peores consultas por tiempo total
cr0x@server:~$ psql -h pg-primary -U postgres -d appdb -c "select queryid, calls, total_time, mean_time, rows, left(query, 80) as q from pg_stat_statements order by total_time desc limit 5;"
queryid | calls | total_time | mean_time | rows | q
----------+-------+------------+-----------+-------+--------------------------------------------------------------------------------
98122311 | 12000 | 955432.12 | 79.61 | 12000 | select * from orders where customer_id = $1 order by created_at desc limit 50
11220091 | 1500 | 440010.55 | 293.34 | 1500 | update inventory set qty = qty - $1 where sku = $2
(2 rows)
Significado: Una consulta domina el tiempo total; otra tiene alta latencia media. Decisión: Optimiza/indexa la primera si está caliente; investiga contención o índice faltante para el update. Si esto ya es “suficiente”, puede que no necesites SQL distribuido.
Task 3 — Postgres: revisar presión de bloat y salud del vacuum
cr0x@server:~$ psql -h pg-primary -U postgres -d appdb -c "select relname, n_dead_tup, n_live_tup, last_vacuum, last_autovacuum from pg_stat_user_tables order by n_dead_tup desc limit 5;"
relname | n_dead_tup | n_live_tup | last_vacuum | last_autovacuum
----------------+------------+------------+----------------------+----------------------
order_events | 8200000 | 51000000 | | 2025-12-30 09:01:12
sessions | 1900000 | 12000000 | 2025-12-29 03:11:55 | 2025-12-30 08:57:48
(2 rows)
Significado: Tuples muertos altos; autovacuum está corriendo pero puede que vaya por detrás. Decisión: Ajusta umbrales de autovacuum por tabla, añade índices apropiados para ayudar al vacuum y revisa transacciones de larga duración. No “resuelvas” esto migrando bases de datos.
Task 4 — Postgres: ver locks activos y quién está bloqueando
cr0x@server:~$ psql -h pg-primary -U postgres -d appdb -c "select blocked.pid as blocked_pid, blocker.pid as blocker_pid, blocked.query as blocked_query, blocker.query as blocker_query from pg_locks blocked join pg_stat_activity blocked_sa on blocked_sa.pid = blocked.pid 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.pid != blocked.pid join pg_stat_activity blocker_sa on blocker_sa.pid = blocker.pid where not blocked.granted;"
blocked_pid | blocker_pid | blocked_query | blocker_query
------------+-------------+-----------------------------------+--------------------------------
29411 | 28703 | update inventory set qty = qty-1 | vacuum (verbose, analyze) inventory
(1 row)
Significado: Vacuum está bloqueando un update (o viceversa). Decisión: Ajusta los settings de costo de vacuum, programa ventanas de mantenimiento o rediseña patrones de transacción. Si tu app es sensible a locks, lo distribuido no eliminará magia la contención.
Task 5 — Postgres: confirmar presión de checkpoints
cr0x@server:~$ psql -h pg-primary -U postgres -d postgres -c "select checkpoints_timed, checkpoints_req, checkpoint_write_time, checkpoint_sync_time from pg_stat_bgwriter;"
checkpoints_timed | checkpoints_req | checkpoint_write_time | checkpoint_sync_time
------------------+-----------------+-----------------------+----------------------
1021 | 980 | 7123456 | 502311
(1 row)
Significado: Muchos checkpoints solicitados; tiempos de escritura/sincronización altos. Decisión: Ajusta shared_buffers, checkpoint_timeout, checkpoint_completion_target y asegúrate de que el disco puede manejar ráfagas de escritura. Otra vez: arregla los fundamentos antes de buscar una nueva base de datos.
Task 6 — Postgres: comprobar tormentas de conexiones y necesidad de pooler
cr0x@server:~$ psql -h pg-primary -U postgres -d postgres -c "select state, count(*) from pg_stat_activity group by 1 order by 2 desc;"
state | count
-----------+-------
idle | 420
active | 65
idle in transaction | 12
(3 rows)
Significado: Cientos de sesiones idle; algunas “idle in transaction” (malo). Decisión: Añade/confirma pool de conexiones (PgBouncer), corrige manejo de transacciones en la app y establece timeouts de statement/idle. Muchas quejas de “Postgres lento” son en realidad “demasiadas conexiones”.
Task 7 — YugabyteDB: comprobar estado general del clúster
cr0x@server:~$ yb-admin -master_addresses yb-master-1:7100,yb-master-2:7100,yb-master-3:7100 list_all_tablet_servers
UUID RPC Host/Port State
3d6b1f7a-2c3b-4f5f-ae3b-7d4d7bbad001 yb-tserver-1:9100 ALIVE
c4f05c1e-9d2b-43d4-a822-ef6e8f9a0002 yb-tserver-2:9100 ALIVE
a9e7f2c0-8b7f-4b32-a1a9-1d8d1dbb0003 yb-tserver-3:9100 ALIVE
(3 rows)
Significado: Todos los tablet servers están vivos. Decisión: Si falta uno o aparece DEAD, detente aquí y estabiliza el clúster antes de analizar rendimiento; “lento” a menudo significa “replicación degradada”.
Task 8 — YugabyteDB: inspeccionar líderes de tablets y salud de replicación
cr0x@server:~$ yb-admin -master_addresses yb-master-1:7100,yb-master-2:7100,yb-master-3:7100 list_tablets appdb.orders
Tablet-UUID Range Leader-UUID RF
b8c9d1110c3b4f9ea0d0000000000001 [hash 0x00, 0x55) 3d6b1f7a-2c3b-4f5f-ae3b-7d4d7bbad001 3
b8c9d1110c3b4f9ea0d0000000000002 [hash 0x55, 0xaa) c4f05c1e-9d2b-43d4-a822-ef6e8f9a0002 3
b8c9d1110c3b4f9ea0d0000000000003 [hash 0xaa, 0xff) a9e7f2c0-8b7f-4b32-a1a9-1d8d1dbb0003 3
Significado: El liderazgo está balanceado entre tservers. Decisión: Si los líderes se concentran en un nodo, espera hotspots y CPU desigual. Rebalancea líderes o revisa placement/partitioning.
Task 9 — YugabyteDB: comprobar tablets sub-replicadas
cr0x@server:~$ yb-admin -master_addresses yb-master-1:7100,yb-master-2:7100,yb-master-3:7100 list_under_replicated_tablets
Tablet-UUID Table
(0 rows)
Significado: No hay tablets sub-replicadas. Decisión: Si esto muestra entradas, arregla la replicación primero (disco, red, salud de nodos). La sub-replicación infla latencia y riesgo.
Task 10 — YugabyteDB (YSQL): encontrar consultas lentas a nivel SQL
cr0x@server:~$ ysqlsh -h yb-tserver-1 -U yugabyte -d appdb -c "select queryid, calls, total_time, mean_time, left(query, 80) from pg_stat_statements order by mean_time desc limit 5;"
queryid | calls | total_time | mean_time | left
----------+-------+------------+-----------+--------------------------------------------------------------------------------
77190012 | 3300 | 222000.11 | 67.27 | select * from orders where customer_id = $1 order by created_at desc limit 50
(1 row)
Significado: La misma historia que en Postgres: las rutas calientes de tu app son visibles. Decisión: Antes de culpar a lo “distribuido”, revisa índices y forma de las consultas. Los sistemas distribuidos castigan más los full scans.
Task 11 — YugabyteDB: validar si una consulta es local o distribuida (explain)
cr0x@server:~$ ysqlsh -h yb-tserver-1 -U yugabyte -d appdb -c "explain (analyze, dist, costs off) select * from orders where customer_id = 42 order by created_at desc limit 50;"
QUERY PLAN
------------------------------------------------------------------------
Limit (actual time=8.211..8.227 rows=50 loops=1)
-> Index Scan using orders_customer_created_idx on orders (actual time=8.210..8.221 rows=50 loops=1)
Storage Read Requests: 3
Storage Write Requests: 0
Storage Execution Time: 6.902 ms
Planning Time: 0.312 ms
Execution Time: 8.411 ms
(8 rows)
Significado: “Storage Read Requests: 3” sugiere lecturas en múltiples tablets. Decisión: Si este número crece con el tamaño de datos, diseña para localidad (clave de partición hash, colocaliza tablas relacionadas o evita joins entre particiones).
Task 12 — YugabyteDB: comprobar presión de compaction/LSM vía disco y síntomas de I/O
cr0x@server:~$ iostat -xz 1 3
avg-cpu: %user %nice %system %iowait %steal %idle
18.22 0.00 6.11 21.40 0.00 54.27
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 120.0 8200.0 0.0 0.0 4.10 68.3 950.0 64200.0 0.0 0.0 24.80 67.6 9.10 98.5
Significado: Disco al máximo; write await alto. En un motor tipo LSM, las compactions pueden generar escrituras sostenidas. Decisión: Añade capacidad de I/O, ajusta compactions, limita trabajo de fondo o añade nodos para repartir la carga. Si tu presupuesto no permite discos rápidos, el almacenamiento distribuido será un mal rato.
Task 13 — Comprobación de red: latencia entre zonas/regiones
cr0x@server:~$ ping -c 5 yb-tserver-3
PING yb-tserver-3 (10.10.3.21) 56(84) bytes of data.
64 bytes from 10.10.3.21: icmp_seq=1 ttl=62 time=18.9 ms
64 bytes from 10.10.3.21: icmp_seq=2 ttl=62 time=19.4 ms
64 bytes from 10.10.3.21: icmp_seq=3 ttl=62 time=18.7 ms
64 bytes from 10.10.3.21: icmp_seq=4 ttl=62 time=20.1 ms
64 bytes from 10.10.3.21: icmp_seq=5 ttl=62 time=19.2 ms
--- yb-tserver-3 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4006ms
rtt min/avg/max/mdev = 18.7/19.2/20.1/0.5 ms
Significado: ~19 ms RTT está bien para humanos, caro para consenso. Decisión: Si los quorums Raft abarcan estos enlaces, la latencia de commit la heredará. Mantén quorums dentro de un dominio de baja latencia cuando sea posible; usa estrategias de geo-partitioning en vez de pretender que la física no existe.
Task 14 — Postgres: verificar que tus backups son reales (no aspiracionales)
cr0x@server:~$ pgbackrest --stanza=appdb check
stanza: appdb
status: ok
cipher: none
db (current)
wal archive min/max (0000000100002A9F000000D1/0000000100002A9F000000F4) ok
database size 145.3GB, backup size 52.1GB
Significado: WAL archiving funciona; los backups son consistentes. Decisión: Si esto falla, arregla backups antes de escalar nada. La fiabilidad no es una característica de base de datos; es un hábito operativo.
Task 15 — YugabyteDB: conteo básico de tablets y comprobación de skew
cr0x@server:~$ yb-admin -master_addresses yb-master-1:7100,yb-master-2:7100,yb-master-3:7100 list_tablet_servers
UUID Host Tablets Leaders
3d6b1f7a-2c3b-4f5f-ae3b-7d4d7bbad001 yb-tserver-1 620 240
c4f05c1e-9d2b-43d4-a822-ef6e8f9a0002 yb-tserver-2 610 210
a9e7f2c0-8b7f-4b32-a1a9-1d8d1dbb0003 yb-tserver-3 590 170
Significado: La distribución de tablets y líderes es desigual. Decisión: Investiga ajustes del rebalancer y movimiento de líderes. El skew suele mapear directamente a CPU sesgada y latencia en cola.
Guion de diagnóstico rápido: encuentra el cuello de botella sin leer hojas de té
Cuando algo está “lento”, necesitas un orden de triaje. No un runbook de diez páginas. Un filtro de tres pasadas que estrecha el problema rápido.
Primera: ¿el sistema está degradado o solo ocupado?
- Postgres: revisa salud de replicación y saturación de disco. Si el primario está ligado por I/O, todo lo demás es ruido descendente.
- YugabyteDB: revisa vivacidad de nodos y tablets sub-replicadas. Un clúster puede estar “up” mientras grupos Raft están enfermos, y entonces la latencia se vuelve feroz.
Segunda: ¿es una consulta/ruta o todo?
- Mira pg_stat_statements (ambos sistemas en la capa YSQL) para encontrar los mayores consumidores por tiempo medio y total.
- Si una consulta domina, probablemente tienes un problema de esquema/índice/consulta.
- Si todo está lento, probablemente tienes un cuello de botella de recursos (CPU, IO, red) o un comportamiento sistémico (vacuum/compaction, tormentas de conexiones, desequilibrio de líderes).
Tercera: ¿la latencia viene de cómputo, I/O o red?
- Cómputo: CPU alta, cola de ejecución; busca líderes calientes (YB) o planes malos (PG).
- I/O: iowait alto, await alto, utilización del dispositivo cerca del 100%.
- Red: RTT entre zonas, pérdida de paquetes o un diseño que fuerza quórums a través de distancia.
Atajo de decisión: Si puedes atribuir la latencia p95 al RTT de red y a los caminos de commit distribuidos, necesitas cambios de localidad de datos—no “más nodos”. Si lo atribuyes a unas pocas consultas, necesitas índices y mejores patrones de acceso—no una nueva base de datos.
Tres micro-historias corporativas desde el frente
1) Incidente causado por una suposición errónea: “las réplicas de lectura nos salvarán”
Una empresa SaaS mediana ejecutaba Postgres con dos réplicas de lectura. La app era mayormente de lecturas, así que el equipo movió “endpoints pesados” a réplicas y se sintió satisfecho. Durante un tiempo todo fue bien. Luego lanzaron una funcionalidad que escribía una fila de auditoría en cada lectura (los de compliance estaban encantados).
La suposición fue que el endpoint era “de solo lectura”, así que siguió dirigido a réplicas. Pero el ORM escribía eventos de auditoría en la misma ruta de solicitud. En las réplicas, esas escrituras fallaban, se reintentaban y caían de regreso al primario. El primario de repente vio una oleada de escrituras, más una oleada de churn de conexiones por reintentos. La CPU se disparó; aparecieron esperas de locks; el p95 se convirtió en p999.
El incidente fue ruidoso porque nada estaba “caído”. El primario estaba vivo. La replicación estaba viva. La experiencia de usuario era simplemente miserable. Los logs de la app estaban llenos de advertencias engañosas “no se pudo ejecutar la sentencia” que parecían problemas transitorios de red.
La solución fue vergonzosamente sencilla: dirigir todos los endpoints que puedan escribir—incluso “una pequeña escritura”—al primario, y separar la ingesta de auditoría en una canalización asíncrona con buffer. También añadieron una barrera: en la app, cualquier conexión DB marcada como solo-lectura rechazaba escrituras a nivel de driver.
Conclusión: Escalar lecturas con réplicas es real, pero “solo lectura” debe aplicarse. Y si tu roadmap de producto cambia silenciosamente rutas de lectura a rutas de escritura, tu arquitectura debe notarlo.
2) Optimización que salió mal: “aumentemos el paralelismo y hagamos todo en batch”
Una plataforma de e-commerce tuvo lentitud en checkout y decidió “optimizar la base de datos”. Aumentaron tamaños de batch para actualizaciones de inventario y ejecutaron más workers en paralelo. La idea: menos transacciones, más throughput, menos overhead.
En Postgres, las transacciones más grandes mantuvieron locks más tiempo. Aumentaron los deadlocks. El autovacuum empezó a retrasarse porque las transacciones de larga duración impedían la limpieza. El bloat apareció; los índices crecieron; la tasa de aciertos de caché cayó; el I/O subió. El sistema se volvió más lento para todos, no solo para checkout.
Más tarde probaron una opción SQL distribuida (YugabyteDB) y aplicaron el mismo instinto: batches más grandes, más workers paralelos. Esta vez el revés fue distinto. Se formaron shards calientes alrededor de SKUs populares. Unas pocas tablets se convirtieron en líderes para las claves más concurridas, y esos líderes fueron machacados. Las compactions aumentaron en los nodos calientes. La latencia se volvió muy espasmódica: “generalmente bien, ocasionalmente terrible”.
Se recuperaron haciendo lo contrario de lo que parecía “eficiente”: transacciones más pequeñas, distribución de claves más fina y controles de contención por SKU. También introdujeron idempotencia y políticas de reintento sensatas porque las transacciones distribuidas pedirán reintentos cuando ocurran conflictos.
Conclusión: “Aumentar batches” no es un truco universal de rendimiento. Cambia la duración de locks en Postgres y la intensidad de hotspots en sistemas distribuidos. A veces la optimización solo mueve el dolor a una forma más cara.
3) Práctica aburrida pero correcta que salvó el día: restoraciones ensayadas
Una empresa relacionada con pagos usaba Postgres para datos de libro mayor y experimentaba con YugabyteDB para un servicio de perfiles de usuario globalmente disponible. No fueron glamorosos con las operaciones. Fueron estrictos.
Cada mes ejecutaban un drill de restauración. No teórico. Un “restaurar a un entorno nuevo y ejecutar un conjunto de verificaciones” real. Validaban continuidad de WAL, contaban filas y ejecutaban un conjunto de invariantes: balances suman correctamente, claves foráneas coinciden, las escrituras recientes existen.
Un día, un bug del subsistema de almacenamiento corrompió un subconjunto de bloques en una réplica. El primario seguía bien, pero el candidato a failover era ahora cuestionable. Como practicaban, el equipo no adivinó. Inmediatamente retiraron la réplica sospechosa de promoción y restauraron una réplica limpia desde backup mientras mantenían el primario estable.
Dos meses después, cuando otro incidente requirió promover una réplica durante una ventana de mantenimiento que salió mal, supieron exactamente qué nodos eran seguros y cuánto tardaría reconstruir. Nadie improvisó en datos de producción. Nadie fue creativo con `rm -rf`.
Conclusión: Ensaya restauraciones. Es poco sexy, consume tiempo y previene el tipo de outage “perdimos datos y también nuestra dignidad” que acaba carreras.
Errores comunes: síntoma → causa raíz → solución
1) Síntoma: p95 de Postgres sube cada pocos minutos
Causa raíz: Ráfagas de checkpoints y saturación de I/O (a menudo combinado con checkpoint_timeout pequeño y throughput de disco insuficiente).
Solución: Aumenta checkpoint_timeout, establece checkpoint_completion_target, asegúrate de que WAL y datos estén en almacenamiento rápido y vigila pg_stat_bgwriter. Confirma que el SO no está haciendo swap.
2) Síntoma: tablas de Postgres siguen creciendo, rendimiento degrada en días
Causa raíz: Vacuum no puede mantenerse (umbrales autovacuum muy altos, transacciones largas o maintenance_work_mem insuficiente).
Solución: Ajusta autovacuum por tabla caliente, acorta la vida de las transacciones, añade índices que ayuden al vacuum y monitorea n_dead_tup.
3) Síntoma: “Añadimos réplicas de lectura pero las escrituras se hicieron más lentas”
Causa raíz: Explosión de conexiones y mala configuración del pool; las réplicas no redujeron trabajo del primario porque la app aún golpea al primario para escrituras y muchas lecturas.
Solución: Usa un pooler (PgBouncer), reduce max connections y mueve cargas realmente solo-lectura. Valida midiendo CPU/IO del primario antes y después.
4) Síntoma: YugabyteDB está “up” pero la latencia es terrible tras pérdida de un nodo
Causa raíz: Tablets sub-replicadas o re-replicación en curso; los grupos Raft trabajan más y los líderes pueden haberse movido.
Solución: Revisa tablets sub-replicadas, estabiliza discos/red, permite que el reequilibrado termine y evita grandes cambios de esquema durante la recuperación.
5) Síntoma: YugabyteDB tiene un nodo con CPU alta y el resto parece aburrido
Causa raíz: Concentración de líderes o tablets hotspot provocadas por patrones de acceso con sesgo en claves.
Solución: Rebalancea líderes, aumenta apropiadamente el número de tablets y rediseña claves de partición para repartir escrituras. Considera colocalización o geo-partitioning para localidad.
6) Síntoma: escrituras multi-región en YugabyteDB se sienten “aleatoriamente lentas”
Causa raíz: El quórum se extiende por enlaces de alta latencia; el camino de commit incluye RTT WAN y la colocación del líder puede ser subóptima.
Solución: Mantén quorums Raft dentro de una región/zona cuando sea posible, usa lecturas desde seguidores o geo-partitioning para lecturas y fija expectativas: escrituras globales fuertes cuestan latencia.
7) Síntoma: la aplicación ve reintentos de transacción frecuentes en YugabyteDB
Causa raíz: Claves de alta contención, filas calientes o transacciones largas que aumentan la probabilidad de conflicto.
Solución: Reduce contención (shard counters, evita “fila secuencia global”), acorta transacciones, implementa reintento con jitter y asegura idempotencia.
8) Síntoma: “Migramos y ahora las consultas analíticas son peores”
Causa raíz: Escaneos y joins distribuidos son caros; la localidad de datos y los índices no están alineados con patrones analíticos.
Solución: Separa OLTP de analytics, usa vistas materializadas/ETL o mantiene analytics en Postgres/warehouse. OLTP distribuido no es un motor analítico gratis.
Listas de verificación / plan paso a paso
Paso a paso: decidir si Postgres es suficiente
- Mide, no vibes: habilita pg_stat_statements y captura 7–14 días de forma de carga (top consultas por tiempo total/medio, recuentos).
- Arregla lo obvio: índices faltantes, patrones malos de ORM, consultas N+1, “idle in transaction” y mala configuración del pool de conexiones.
- Pon el vacuum bajo control: ajuste autovacuum por tabla para tablas con churn; confirma que transacciones largas no bloquean la limpieza.
- Confirma holgura de disco: iostat y utilización del sistema de ficheros; revisa presión de checkpoints y saturación de escritura WAL.
- Implementa HA de forma aburrida: al menos una réplica, failover automatizado, backups con drills de restauración y un pooler.
- Escala lecturas apropiadamente: réplicas para solo-lectura, cache donde sea seguro y reduce viajes de ida y vuelta.
- Escala escrituras honestamente: si un primario es el límite, evalúa particionado/sharding a nivel de app o considera SQL distribuido.
Paso a paso: decidir si YugabyteDB está justificado
- Escribe los no negociables: “sobrevivir pérdida de zona”, “activo-activo”, “escalado de escrituras”, “RPO/RTO”. Si no puedes articularlos, estás comprando dopamina.
- Modela el presupuesto de latencia: qué p95 necesitas y qué RTT existe entre emplazamientos. Si tu camino de commit abarca 20–50 ms RTT, lo notarás.
- Prototipa la ruta caliente: no un benchmark genérico. Tus 5 consultas top reales, tus formas de transacción reales, tus índices reales.
- Diseña para localidad: elige claves de partición y placement para mantener la mayoría de transacciones locales. Trata las transacciones distribuidas cross-region como caso especial.
- Planifica reintentos: implementa idempotencia y manejo de reintentos de transacción en la app. Los sistemas distribuidos no piden; ordenan.
- Presupuesta ops: monitorización para skew de líderes, splits de tablets, compaction debt y salud de nodos. Asigna propiedad; “el vendor lo manejará” no es un plan de on-call.
Checklist de migración (si debes hacerlo)
- Inventario de funciones de Postgres usadas: extensiones, tipos personalizados, triggers, replicación lógica, procedimientos almacenados.
- Identifica brechas de compatibilidad temprano; reescribe las partes riesgosas primero.
- Configura escrituras duales o CDC en un entorno staging; valida corrección con invariantes.
- Ejecuta pruebas de carga que incluyan fallos: mata un nodo, añade latencia, llena discos.
- Define criterios de rollback y ensáyalos. Si el rollback es “ya veremos”, no funcionarás.
Preguntas frecuentes
1) ¿YugabyteDB es “solo Postgres pero distribuido”?
No. YSQL aspira a compatibilidad con Postgres en SQL y layers de protocolo, pero el modelo de almacenamiento y replicación es distinto. Eso cambia rendimiento y modos de fallo.
2) ¿Puedo tener activo-activo con Postgres?
No en el sentido directo de “multi-writer con consistencia fuerte”. Puedes usar replicación lógica, patrones multi-primary o sharding a nivel de app, pero cada opción trae aristas afiladas y manejo de conflictos. Para escrituras distribuidas verdaderas sin shard a nivel de app, entras en territorio de SQL distribuido.
3) ¿YugabyteDB será automáticamente más rápido que Postgres?
No. Para muchas cargas OLTP que caben en un buen primario Postgres, Postgres será más rápido y más simple. YugabyteDB te compra resiliencia y escalado horizontal, no reducción de latencia gratuita.
4) ¿Cuál es la razón más común por la que fallan migraciones a YugabyteDB?
Asumir que “compatible con Postgres” significa “reemplazo drop-in”, y luego descubrir que una extensión, patrón de consulta o expectativa operativa no se traduce limpiamente. La compatibilidad es real, pero no mágica.
5) ¿Cuándo es SQL distribuido la elección correcta?
Cuando los requisitos del negocio exigen alta disponibilidad a través de dominios de fallo, escalar escrituras más allá de un nodo y no puedes o no quieres shardear en la aplicación. También cuando el coste del downtime es mayor que el de nodos extra.
6) ¿Cómo cambian los reintentos el diseño de la aplicación en sistemas distribuidos?
Necesitas idempotencia para operaciones de escritura y lógica de reintento estructurada con jitter y intentos limitados. Si no, los reintentos convierten conflictos transitorios en una estampida.
7) ¿Multi-región siempre significa “usuarios en todas partes con baja latencia”?
No. Multi-región puede significar “disponible en todas partes”, no “rápido en todas partes”. Las escrituras fuertemente consistentes entre regiones pagan latencia WAN. Para baja latencia en todas partes necesitas estrategias de localidad y a veces consistencia relajada para lecturas.
8) ¿Puedo usar YugabyteDB también para analytics?
Puedes ejecutar consultas analíticas, pero los motores OLTP distribuidos suelen encarecer escaneos y joins grandes. Muchos equipos mantienen OLTP en Postgres o YugabyteDB y mueven analytics a un sistema dedicado.
9) ¿Qué debo monitorizar que sea diferente entre ambos?
Postgres: progreso de vacuum, bloat, locks, checkpoints, lag de replicación, conteos de conexiones. YugabyteDB: salud de tablets, distribución de líderes, tablets sub-replicadas, compaction debt, reequilibrado y latencia entre nodos.
10) Si soy pequeño hoy, ¿debería “prepararme para el futuro” con YugabyteDB?
Por lo general no. Ejecuta Postgres bien primero. El mejor “prepararse para el futuro” es un diseño de esquema limpio, disciplina de consultas e higiene operativa. Las migraciones siempre son más complicadas de lo que la gente presupone.
Siguientes pasos prácticos
Si quieres una decisión que resista el estrés de producción, haz esto en orden:
- Haz tu Postgres actual aburridamente excelente: pg_stat_statements, pool de conexiones, ajuste de vacuum, timeouts sensatos, backups verificados, restores ensayados.
- Cuantifica el muro de escalado: ¿es CPU, I/O, contención de locks o throughput de un único escritor? Trae gráficos, no opiniones.
- Escribe el requerimiento de disponibilidad en términos operativos: qué dominios de fallo debes sobrevivir, qué RPO/RTO y qué presupuesto de latencia para el usuario.
- Si realmente necesitas escrituras distribuidas y resiliencia multi-dominio: prototipa YugabyteDB con tu carga real, mide latencia en cola y practica fallos (pérdida de nodo, presión de disco, inyección de latencia).
- Elige lo que puedas operar: la mejor base de datos es la que tu equipo puede mantener rápida, correcta y tranquila a las 2 a.m.
Postgres sigue siendo la opción por defecto porque es predecible y ampliamente comprendido. YugabyteDB es atractivo cuando el problema es genuinamente distribuido. Si no estás seguro de qué problema tienes, probablemente tienes un problema de Postgres.