PostgreSQL vs CockroachDB: alta disponibilidad sin drama — o con nuevos tipos de dolor

¿Te fue útil?

No compras “alta disponibilidad” con una diapositiva. La ejecutas. A las 2 a. m. Cuando una bandeja de racks se cae, un router se reinicia,
un despliegue sale mal y tu teléfono de guardia empieza a hacer cardio.

PostgreSQL puede ser aburridamente fiable en producción—si aceptas que la HA es sobre todo un sistema que construyes alrededor suyo.
CockroachDB ofrece HA como una característica de primera clase—si aceptas que los sistemas distribuidos no eliminan el dolor, lo reubican.

La pregunta real: ¿qué tipo de fallo quieres manejar?

El error más común al elegir una base de datos para HA es pensar que la pregunta es “¿Qué base de datos es más disponible?”
La disponibilidad no es un atributo de marca. Es una propiedad de extremo a extremo que incluye tus drivers clientes, DNS, balanceadores de carga,
hábitos de migración de esquema, disciplina de backups, observabilidad y el tipo de personas que contratas.

La pregunta real es: ¿qué fallo estás dispuesto a diagnosticar bajo estrés?

  • Con PostgreSQL, la historia de HA suele implicar replicación por streaming, un gestor de conmutación por error y clara separación
    entre “el nodo escribible” y “réplicas que pueden ir retrasadas”. Tu dolor suele girar alrededor de la orquestación de failover,
    el enrutamiento lectura/escritura, la latencia de replicación y bordes operativos afilados.
  • Con CockroachDB, la historia de HA está incorporada: replicación por consenso, reequilibrio automático y SQL que parece familiar.
    Tu dolor se traslada al debugging distribuido: contención, splits de rangos, hotspots, amplificación de latencia entre regiones
    y aprender el modelo de estado interno del clúster.

Elige el dolor que puedas pagar. No el dolor que se ve mejor en un diagrama.

Posicionamiento rápido: cuándo cada base de datos es el martillo adecuado

Elige PostgreSQL HA cuando puedas centralizar escrituras y controlar la complejidad

PostgreSQL es un caballo de batalla. Obtienes semánticas SQL maduras, un ecosistema enorme, características de rendimiento previsibles
y un modelo de fallo que la mayoría de equipos SRE pueden razonar. En la práctica, la HA en PostgreSQL suele ser:
un primario, una o más réplicas, además de un orquestador (Patroni, repmgr, Pacemaker/Corosync),
y una capa de enrutamiento (HAProxy, PgBouncer, balanceador en la nube o lógica a nivel de aplicación).

PostgreSQL HA es la elección correcta cuando:

  • Tu carga de escritura es intensa y sensible a la latencia, y puedes mantener el primario en una región/zona.
  • Quieres la profundidad completa de funcionalidades de Postgres (extensiones, índices avanzados, herramientas ricas) sin matices de “casi compatible”.
  • Puedes tolerar que la HA entre regiones suele ser recuperación ante desastres (DR), no “escrituras activas-activas”.
  • Dispones de un equipo que pueda tratar la conmutación por error como un subsistema ingenieril con simulacros.

Elige CockroachDB cuando necesites que las escrituras sobrevivan a la pérdida de nodos sin una pila HA a medida

CockroachDB es SQL distribuido: un almacén key-value replicado por consenso con SQL encima. El argumento comercial es atractivo:
“Sigue funcionando cuando las máquinas mueren.” A menudo cierto. Pero pagas con nuevos modos de fallo y más piezas móviles por consulta.
Tu postura operativa cambia de “proteger el primario” a “mantener el clúster balanceado y enfriar los hotspots.”

CockroachDB es la elección correcta cuando:

  • Necesitas failover automático de escrituras sin promover una réplica.
  • Tienes presencia multi-zona y quieres que la base de datos gestione colocación de réplicas y quórums automáticamente.
  • Tu organización está lista para aprender perfilado de rendimiento distribuido y aceptar mayor latencia base por seguridad.
  • Puedes diseñar la localidad de datos (particionamiento/geo) intencionadamente, no por deseo.

Broma seca #1: Construir HA en Postgres es como montar muebles de IKEA—estable y útil, a menos que “improvises” un tornillo que falta.

Historia y datos interesantes que puedes usar

El contexto importa porque el diseño de bases de datos es, en gran medida, el registro fósil de viejas interrupciones.
Aquí hay hechos que realmente te ayudan a tomar decisiones:

  1. PostgreSQL desciende de POSTGRES (UC Berkeley, mediados de los 80), diseñado cuando la corrección importaba más que las palabras de moda en la nube.
    Ese ADN se nota en su comportamiento conservador y predecible.
  2. Write-ahead logging (WAL) es más antiguo que tu sistema CI. Postgres depende del WAL para garantizar durabilidad y habilitar replicación.
    Si no respetas WAL y los checkpoints, eventualmente tendrás un incidente en cámara lenta.
  3. La replicación por streaming en Postgres llegó en la 9.0 (2010), lo que hizo la replicación física “real” común y rápida.
    Antes de eso, la HA era más torpe y manual.
  4. Hot standby de Postgres (réplicas legibles) aterrizó en la 9.0. Eso habilitó un patrón estándar: escalar lecturas, proteger el primario y planear fallovers.
  5. CockroachDB se inspiró en Google Spanner (descrito públicamente en 2012). La tesis de Spanner: SQL a escala global con consistencia fuerte,
    a costa de coordinación.
  6. CockroachDB usa consenso Raft para replicación. Raft está diseñado para ser comprensible (en relación con Paxos), pero “comprensible”
    aún involucra términos como quórums, leases y transferencias de liderazgo.
  7. “Distributed SQL” se convirtió en una categoría porque NoSQL no podía fingir para siempre. Muchos equipos querían transacciones SQL de vuelta
    después de aprender que la consistencia eventual es un estilo de vida, no un ajuste.
  8. CAP no es una herramienta para comparar productos. En sistemas reales, la tolerancia a particiones no es opcional, y “consistencia” tiene múltiples significados.
    La pregunta útil es: ¿qué compromiso hace el sistema durante particiones y cuán visible es eso para tu app?
  9. Las extensiones de Postgres son una superpotencia y una trampa. Te permiten construir sistemas especializados rápido (PostGIS, pgcrypto, herramientas tipo Timescale),
    pero te atan a las semánticas de Postgres en formas que sistemas “compatibles” quizá no repliquen.

Cómo funciona realmente la HA: replicación de Postgres vs consenso de Cockroach

PostgreSQL HA: un único escritor con réplicas, más pegamento de orquestación

La arquitectura central de PostgreSQL es de un único escritor. Puedes escalar lecturas con réplicas y puedes hacer failover promoviendo una réplica.
Pero “Postgres HA” no es una única característica; es una coreografía:

  • Streaming replication copia WAL del primario a las réplicas.
  • Synchronous replication puede asegurar que los commits esperen la confirmación de réplicas (menor RPO, mayor latencia).
  • Failover management decide qué nodo se convierte en primario y reconfigura clientes.
  • Prevención de split-brain es tu trabajo. Necesitas fencing, quórum o un lock distribuido fiable.

La ventaja de Postgres es la claridad: hay un primario. Si las escrituras fallan, buscas el primario o promueves uno.
El riesgo es que tu pila de HA sea “baterías no incluidas.” Esa pila puede ser excelente, pero debe ser diseñada.

CockroachDB HA: replicación por consenso en todas partes, con SQL como copiloto

CockroachDB distribuye datos en ranges (shards). Cada range se replica (a menudo 3 réplicas por defecto),
y una réplica es el líder Raft que maneja escrituras para ese range. Las transacciones coordinan entre ranges.
El failover es interno: si un nodo muere, otra réplica se convierte en líder para los ranges afectados, siempre que el quórum permanezca.

La ventaja operacional de Cockroach es que no promueves manualmente un nuevo primario. El sistema se autocura dentro de las limitaciones de quórum.
El riesgo es la previsibilidad de rendimiento: una transacción simple puede implicar múltiples ranges y múltiples grupos de consenso,
especialmente a medida que tu dataset y esquema evolucionan.

RPO/RTO en términos reales

Si compras HA, compras RPO y RTO, no sensaciones.

  • Postgres con replicación asíncrona: RPO puede ser no cero (puedes perder los últimos segundos de commits en failover),
    RTO depende de la orquestación y del enrutamiento de clientes.
  • Postgres con replicación síncrona: RPO puede acercarse a cero si está bien configurado, pero la latencia de commit aumenta y puedes estancarte
    si los standbys sincronizados no están disponibles.
  • CockroachDB: RPO suele ser cercano a cero dentro del quórum; RTO suele ser rápido ante fallos de nodo, pero particiones y
    clústeres sobrecargados pueden llevar a comportamiento “disponible pero triste”: timeouts, reintentos y rendimiento degradado.

Una cita para mantenerte honesto, parafraseando a Werner Vogels: “Todo falla, todo el tiempo.” Si quieres la redacción exacta, trátalo como una idea parafraseada.

Latencia, latencia extrema y por qué “multi-región” es una palabra trampa

Las conversaciones de HA se vuelven románticas sobre geografía. “Activo-activo entre regiones.” “Escrituras globales.”
Entonces la física aparece, sin invitación, como el auditor al que olvidaste CC.

Con Postgres, la ruta de escritura es local al primario. Si pones el primario en una región y ejecutas servidores de aplicación en otra,
tu latencia p99 hará una burla de tu hoja de ruta. Eso no es culpa de Postgres; es tuya.
La HA en Postgres suele significar “mantén el primario cerca de los escritores; mantén réplicas cerca de los lectores; haz failover cuando sea necesario.”

Con CockroachDB, multi-región es parte del diseño, pero no es magia. Una escritura fuertemente consistente necesita un quórum.
Si tus réplicas están repartidas por regiones distantes, el tiempo de ida y vuelta del quórum se convierte en la latencia base de escritura.
El sistema puede colocar líderes y réplicas para optimizar localidad, pero aún debes elegir tus restricciones:
latencia vs supervivencia vs modelo de consistencia.

Broma seca #2: La consistencia fuerte multi-región es la única forma de convertir “velocidad de la luz” en una factura mensual recurrente.

La latencia extrema es donde las bases de datos van a morir

La latencia mediana está bien para dashboards. La latencia extrema es lo que experimentan los usuarios.
Los sistemas distribuidos tienden a ensanchar la cola porque más componentes participan en cada operación.
CockroachDB puede ser excelente, pero si tu transacción toca múltiples ranges distribuidos en nodos,
tienes más oportunidades para colas, contención y réplicas lentas.

Postgres tiene sus propios problemas de cola—autovacuum atascado, colas de locks, picos de checkpoints—pero el alcance suele ser más fácil de acotar:
los límites de recursos de un primario y un único flujo WAL.

Modos de fallo: lo que te despierta

Modos de fallo de PostgreSQL (y cómo se sienten)

  • Split brain: dos nodos creen ser primario. Síntomas: líneas de tiempo divergentes, escrituras en conflicto, “no se puede conectar” intermitente.
    Causas raíz: fencing malo, gestor de failover mal configurado, particiones de red, scripts demasiado ingeniosos.
  • Replicación atrasada: réplicas se quedan atrás. Síntomas: lecturas obsoletas, surprises read-after-write, failover perdería datos.
    Causas raíz: I/O lento, limitación de red, transacciones largas, ráfagas de WAL durante jobs por lotes, réplicas infra-dimensionadas.
  • Presión de checkpoints / WAL: picos de latencia. Síntomas: estancamientos periódicos de escritura, saturación de I/O, aumento de tiempos de fsync.
    Causas raíz: shared_buffers demasiado pequeño con alta rotación, parámetros de checkpoint agresivos, almacenamiento lento, demasiados buffers sucios.
  • Deuda de autovacuum: todo se vuelve más lento y luego cae en picada. Síntomas: bloat, aumento de tiempos de consulta, advertencias de wraparound.
    Causas raíz: umbrales de vacuum no ajustados para alto churn, transacciones largas, workers de autovacuum insuficientes.
  • Failover con estado incompatible: la réplica promovida carece de extensiones, configuraciones o tiene parámetros distintos.
    Síntomas: errores de la aplicación tras el failover, planes de consulta inesperados, roles faltantes.
    Causa raíz: servidores únicos (“snowflake”) y deriva de configuración.

Modos de fallo de CockroachDB (y cómo se sienten)

  • Ranges calientes / hotspots: un espacio de claves se vuelve un imán de tráfico. Síntomas: alta latencia para un subconjunto de operaciones, picos de CPU en pocos nodos.
    Causas raíz: claves monotonamente crecientes, contención en una sola fila, diseño de esquema pobre para distribución.
  • Reintentos de transacción: la app ve errores de serialización o fallos reintentables. Síntomas: latencia incrementada, timeouts intermitentes, reintentos en alza.
    Causas raíz: alta contención, transacciones largas, escrituras en conflicto, backoff o lógica de reintento insuficiente en clientes.
  • Sub-replicación / pérdida de quórum: un range no alcanza quórum. Síntomas: indisponibilidad de parte de los datos, escrituras estancadas.
    Causas raíz: demasiadas fallas de nodo, clúster mal dimensionado, mantenimiento realizado como si fuera demolición.
  • Latencia de quórum entre regiones: escrituras lentas globalmente. Síntomas: saltos en p95/p99 correlacionados con RTT entre regiones.
    Causa raíz: políticas de colocación de réplicas que forzan quórums entre regiones distantes.
  • Interferencia de reequilibrado en segundo plano: el clúster se “cura” mientras intentas servir tráfico.
    Síntomas: disco y red sostenidos, mayor latencia, más variación.
    Causas raíz: descomisionar nodos bajo carga, cambios repentinos de topología, margen insuficiente.

Observa el tema: los fallos de Postgres suelen ser sobre orquestación y techos de recursos de un único nodo.
Los fallos de Cockroach suelen ser sobre coordinación y efectos secundarios de la distribución.

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

A continuación hay tareas operativas reales con comandos que puedes ejecutar, qué significa la salida y la decisión que tomas.
Están divididas entre Postgres y CockroachDB, con algunas comprobaciones a nivel de SO. Sin relleno; esto es lo que realmente hace la guardia.

PostgreSQL: identificar el primario y la salud de la replicación

Task 1: ¿Este nodo es primario o standby?

cr0x@server:~$ psql -XAtc "select pg_is_in_recovery();"
f

Significado: f significa que este nodo no está en recovery: está actuando como primario. t significaría standby.
Decisión: Si esperabas un standby pero obtuviste f, detente y revisa el estado de failover; podrías estar en territorio de split-brain.

Task 2: Comprobar el retraso de replicación en bytes en el primario

cr0x@server:~$ psql -Xc "select application_name, client_addr, state, sent_lsn, write_lsn, flush_lsn, replay_lsn, pg_wal_lsn_diff(sent_lsn,replay_lsn) as byte_lag from pg_stat_replication;"
 application_name | client_addr |  state  | sent_lsn  | write_lsn | flush_lsn | replay_lsn |  byte_lag
------------------+-------------+---------+-----------+-----------+-----------+------------+-----------
 replica1         | 10.0.1.21   | streaming | 3/9A4F2C8 | 3/9A4F2C8 | 3/9A4F2C8 | 3/9A4F2C8  |         0
 replica2         | 10.0.2.37   | streaming | 3/9A4F2C8 | 3/9A4F2C8 | 3/9A4F2C8 | 3/9A4E100  |     54024

Significado: byte_lag muestra cuánto se queda atrás cada réplica respecto a lo que el primario ha enviado.
Un retraso pequeño es normal; crecimiento persistente significa que la réplica no puede seguir el ritmo.
Decisión: Si el retraso crece, evita promover la réplica atrasada; investiga I/O, CPU y red de la réplica.

Task 3: En un standby, medir el retraso de replique en términos de tiempo

cr0x@server:~$ psql -Xc "select now() - pg_last_xact_replay_timestamp() as replay_delay;"
 replay_delay
--------------
 00:00:02.184

Significado: Si esto crece a minutos bajo carga normal, tu “escalado de lecturas” está produciendo lecturas obsoletas.
Decisión: O bien deja de enviar lecturas sensibles a latencia aquí, o arregla el cuello de botella (a menudo disco).

Task 4: Comprobar transacciones de larga duración que bloquean vacuum y control de bloat

cr0x@server:~$ psql -Xc "select pid, usename, state, now()-xact_start as xact_age, left(query,80) as query from pg_stat_activity where xact_start is not null order by xact_age desc limit 5;"
 pid  | usename | state  |  xact_age  |                                      query
------+--------+--------+------------+--------------------------------------------------------------------------------
 8421 | app    | active | 00:42:11   | update orders set status='paid' where id=$1 returning id
 9173 | app    | idle in transaction | 01:13:02 | select * from customers where id=$1

Significado: “idle in transaction” durante una hora es una señal de alerta; puede impedir la limpieza y causar bloat.
Decisión: Corrige el manejo de conexiones de la app; considera idle_in_transaction_session_timeout y mata a los culpables durante el incidente.

Task 5: Encontrar contención de locks rápidamente

cr0x@server:~$ psql -Xc "select a.pid, now()-a.query_start as age, a.state, left(a.query,70) as query, l.mode, l.granted from pg_locks l join pg_stat_activity a on a.pid=l.pid where a.datname=current_database() order by l.granted, age desc limit 12;"
 pid  |   age    | state  |                               query                               |        mode         | granted
------+----------+--------+-------------------------------------------------------------------+---------------------+---------
 5211 | 00:01:12 | active | alter table invoices add column tax_region text                   | AccessExclusiveLock | f
 4182 | 00:01:08 | active | select * from invoices where account_id=$1 order by created_at    | AccessShareLock     | t

Significado: Un AccessExclusiveLock en espera bloquea básicamente todo en esa tabla.
Decisión: Aborta el DDL si es inseguro; reprograma con opciones CONCURRENTLY o patrones de migración en línea.

Task 6: Comprobar presión de checkpoints y comportamiento de buffers

cr0x@server:~$ psql -Xc "select checkpoints_timed, checkpoints_req, buffers_checkpoint, buffers_backend, maxwritten_clean, checkpoint_write_time, checkpoint_sync_time from pg_stat_bgwriter;"
 checkpoints_timed | checkpoints_req | buffers_checkpoint | buffers_backend | maxwritten_clean | checkpoint_write_time | checkpoint_sync_time
-------------------+-----------------+--------------------+-----------------+------------------+-----------------------+----------------------
               102 |              47 |            9231142 |          312991 |            25013 |               1882210 |                402113

Significado: Un checkpoints_req alto relativo a los checkpoints temporizados sugiere presión de WAL forzando checkpoints extra.
Decisión: Ajusta parámetros de checkpoint y evalúa throughput de almacenamiento; considera distribuir ráfagas de escritura y aumentar max_wal_size.

Task 7: Validar que el archivado de WAL funciona (para PITR)

cr0x@server:~$ psql -Xc "select archived_count, failed_count, last_archived_wal, last_archived_time, last_failed_wal, last_failed_time from pg_stat_archiver;"
 archived_count | failed_count | last_archived_wal |     last_archived_time     | last_failed_wal | last_failed_time
---------------+--------------+-------------------+----------------------------+-----------------+-----------------
         91822 |            0 | 00000001000000030000009A | 2025-12-30 02:18:12.104+00 |                 |

Significado: failed_count=0 es lo que quieres. El archivado fallido significa que tu historia de recuperación es ficción.
Decisión: Si hay fallos, deja de fingir que tienes PITR; arregla permisos, almacenamiento o el comando del archiver antes del próximo incidente.

PostgreSQL: postura de failover y enrutamiento de clientes

Task 8: Comprobar configuración de replicación síncrona

cr0x@server:~$ psql -Xc "show synchronous_commit; show synchronous_standby_names;"
 synchronous_commit
--------------------
 on

 synchronous_standby_names
--------------------------
 FIRST 1 (replica1,replica2)

Significado: Los commits esperan a un standby. Si ambos standbys están poco saludables, las escrituras pueden detenerse.
Decisión: Si estás en un outage y necesitas que las escrituras avancen, puedes relajar temporalmente la sincronía—pero documéntalo y revierte.

Task 9: Confirmar que tu app apunta al endpoint correcto (ejemplo PgBouncer)

cr0x@server:~$ psql -h 127.0.0.1 -p 6432 -U pgbouncer -d pgbouncer -Xc "show clients;"
 type | user | database | state  | addr       | port  | local_addr | local_port | connect_time
------+------|----------|--------|------------|-------|------------|------------|----------------------------
 C    | app  | prod     | active | 10.4.7.19  | 49212 | 10.4.2.10  | 6432       | 2025-12-30 02:18:55.911+00

Significado: Ves clientes activos y sus direcciones fuente. Útil durante failover: ¿los clientes llegan al proxy?
Decisión: Si los clientes no se conectan, la BD puede estar bien y la capa de enrutamiento estar rota (o bloqueada por firewall/DNS).

CockroachDB: salud del clúster y saneamiento de distribución

Task 10: Comprobar estado de nodos y vivacidad

cr0x@server:~$ cockroach node status --host localhost:26257
  id |    address     |     build     |  started_at           | is_live | replicas |  cpu | mem |  ssd | version
-----+----------------+---------------+-----------------------+---------+----------+------+-----+------+---------
   1 | 10.0.1.10:26257| v24.1.3       | 2025-12-30 00:11:02   | true    |     2210 | 0.42 | 64G |  30% | 24.1
   2 | 10.0.2.10:26257| v24.1.3       | 2025-12-30 00:11:08   | true    |     2198 | 0.55 | 64G |  29% | 24.1
   3 | 10.0.3.10:26257| v24.1.3       | 2025-12-30 00:10:59   | false   |     2175 | 0.00 | 64G |  31% | 24.1

Significado: El nodo 3 no está vivo. Con una configuración de 3 réplicas, perder uno suele ser tolerable, pero ahora estás a una falla de quedar en peligro.
Decisión: Pausa cualquier mantenimiento; restaura la salud del nodo o añade capacidad antes de hacer algo “ingenioso.”

Task 11: Verificar salud de replicación rápidamente

cr0x@server:~$ cockroach node status --ranges --host localhost:26257
  id | ranges | underreplicated | unavailable | leader_ranges
-----+--------+-----------------+-------------+--------------
   1 |   5600 |              12 |           0 |         1850
   2 |   5578 |              15 |           0 |         1902
   3 |   5489 |             301 |           2 |            0

Significado: Ranges sub-replicados y unavailable indican problemas de colocación de datos y posible indisponibilidad.
Decisión: Si unavailable es distinto de cero, trátalo como un incidente visible al usuario. Restaura el quórum (trae el nodo, o reconfigura).

Task 12: Identificar sentencias costosas y reintentos (introspección SQL)

cr0x@server:~$ cockroach sql --host localhost:26257 -e "select app_name, query, count(*) as execs, sum(retries) as retries from crdb_internal.statement_statistics where aggregated_ts > now() - interval '10 minutes' group by app_name, query order by retries desc limit 5;"
  app_name |                    query                    | execs | retries
----------+---------------------------------------------+-------+---------
  api     | update accounts set balance = balance + $1  |  1200 |     340

Significado: Reintentos altos para un update sugieren contención o claves calientes.
Decisión: Reduce la contención: rediseña el patrón de escrituras (shard counters, evita hotspots de fila única), acorta transacciones, añade backoff en reintentos.

Task 13: Comprobar distribución de ranges y posibles hotspots

cr0x@server:~$ cockroach sql --host localhost:26257 -e "select range_id, replicas, lease_holder, queries_per_second from crdb_internal.ranges_no_leases order by queries_per_second desc limit 5;"
  range_id | replicas | lease_holder | queries_per_second
----------+----------+--------------+-------------------
    90211 | {1,2}    |            1 |              892.2
    90197 | {1,2,3}  |            2 |              521.7

Significado: Un range con QPS muy alto puede volverse cuello de botella; también observa ranges con solo dos réplicas.
Decisión: Si los hotspots persisten, revisa el esquema y la distribución de claves; si el conteo de réplicas es bajo, aborda la pérdida de nodos y la re-replicación.

Comprobaciones de SO y almacenamiento que afectan a ambos sistemas

Task 14: Comprobar latencia y saturación de disco (iostat)

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.33    0.00    5.41    9.88    0.00   72.38

Device            r/s     w/s   rkB/s   wkB/s  await  svctm  %util
nvme0n1         120.0   980.0  6400.0 51200.0  18.40   0.78  92.10

Significado: %util cerca de 100% junto con alto await significa que el disco es un cuello de botella.
Decisión: Deja de culpar “a la base de datos.” Reduce presión de escritura, añade IOPS, muévete a almacenamiento más rápido, o añade nodos (Cockroach) / ajusta checkpoints (Postgres).

Task 15: Confirmar espacio en sistema de archivos y salud de inodos

cr0x@server:~$ df -h /var/lib/postgresql /var/lib/cockroach
Filesystem      Size  Used Avail Use% Mounted on
/dev/nvme0n1p2  900G  812G   43G  95% /var/lib/postgresql
/dev/nvme1n1p1  900G  701G  153G  83% /var/lib/cockroach

Significado: 95% de uso coquetea con el desastre; a Postgres en especial no le gusta quedarse sin disco en medio del WAL.
Decisión: Si está por encima de ~85–90% sostenido, trátalo como urgente: expande el volumen, purga o mueve datos antes de que se convierta en downtime.

Guion de diagnóstico rápido

Cuando la base de datos “está lenta”, no tienes tiempo para filosofía. Necesitas un triaje rápido y repetible que reduzca el cuello de botella.
Aquí hay una secuencia pragmática primer/segundo/tercer paso para ambos sistemas.

Primero: ¿es la base de datos o el camino hacia ella?

  • Revisa errores/timeouts de conexión en logs de la app. Si es DNS/LB/firewall, la BD puede estar inocente.
  • Verifica que el endpoint resuelva a los nodos esperados. Tráfico mal enroutado es clásico tras un failover.
  • Comprueba salud básica del SO: CPU steal, disco lleno, latencia de disco.

Segundo: ¿está el sistema bloqueado (locks/contención) o saturado (I/O/CPU)?

  • Postgres: mira locks, transacciones largas y espera de I/O; revisa lag de replicación si las lecturas están obsoletas.
  • CockroachDB: mira reintentos, hotspots (ranges) y sub-replicación; revisa vivacidad de nodos y disponibilidad de ranges.

Tercero: ¿es esto un evento de topología/HA?

  • Postgres: confirma exactamente un primario, confirma que las réplicas están conectadas, confirma el estado del gestor de failover.
  • CockroachDB: confirma salud de quórum, sub-replicación y si el reequilibrado está combatiendo tu carga.

La disciplina es evitar perseguir planes de consulta antes de confirmar que el clúster no está simplemente hambriento de disco o atascado detrás de un lock.
La mayoría de incidentes de “rendimiento de DB” son en realidad “incidentes de coordinación y almacenamiento con máscara SQL.”

Tres mini-historias corporativas desde el frente

Mini-historia 1: El incidente causado por una suposición equivocada (failover Postgres)

Una SaaS mediana ejecutaba Postgres con un primario en una zona y una réplica por streaming en otra.
Tenían un gestor de failover y un VIP. Todos se sentían seguros. Incluso escribieron “RPO cercano a cero” en un documento interno, que es como tentar al destino.

Una tarde, el almacenamiento del primario comenzó a tener timeouts. El gestor de failover promovió la réplica. El VIP se movió.
La app se recuperó rápido—hasta que soporte notó pedidos “faltantes”. No muchos, pero lo suficiente para importar.
El equipo asumió que la replicación síncrona estaba habilitada porque “lo configuramos hace meses.”

Resultó que los ajustes de sincronía se aplicaron en el primario antiguo pero nunca se desplegaron de forma consistente.
Peor, la app usaba réplicas para algunas “pantallas de confirmación”, y esas ahora mostraban una línea de tiempo que no coincidía con el nuevo primario.
El failover en sí fue correcto. La suposición sobre el RPO no lo fue.

La recuperación requirió un triaje doloroso: comparar IDs de pedidos y timestamps, reconciliar desde logs upstream y explicar al negocio por qué “alta disponibilidad”
no significó “sin pérdida de datos.” La solución real fue aburrida: gestión de configuración, paridad de parámetros forzada
y una política clara de qué consultas pueden ir a réplicas.

Tras el incidente, cambiaron su playbook: el failover no está “terminado” hasta verificar modo de replicación, lag de réplicas y enrutamiento de la aplicación.
También empezaron a escribir objetivos de RPO/RTO explícitos por función, no por base de datos.

Mini-historia 2: La optimización que salió mal (hotspot en CockroachDB)

Un equipo de e-commerce migró un servicio de carrito intensivo en escrituras a CockroachDB para failover automático entre tres zonas.
Los primeros resultados estuvieron bien. La latencia parecía estable. Luego llegó la temporada alta y el flujo de checkout empezó a producir reintentos y timeouts.

El equipo había “optimizado” las claves primarias para ser secuenciales buscando localidad de índice, razonando desde años de experiencia en Postgres.
En CockroachDB, eso creó un hotspot de escrituras: los inserts castigaban un pequeño conjunto de ranges porque las claves crecían monótonamente.
Los splits de rango ayudaron, pero el liderazgo de los ranges más calientes se concentró en un par de nodos que llegaron a saturarse de CPU.

La guardia vio que los nodos estaban vivos y la replicación saludaba. Aun así la latencia subió y la app reintentó agresivamente,
convirtiendo la contención en un Denegación de Servicio auto-infligida. El sistema estaba disponible, técnicamente. Los usuarios seguían sin poder pagar confiablemente.

La solución fue contraintuitiva para una mentalidad Postgres: cambiar la distribución de claves (usar prefijos aleatorios o hash-sharding),
y rediseñar “contadores de una sola fila” en contadores shardados. También implementaron backoff sensato en reintentos y limitaron concurrencia en los caminos de código más calientes.

Mantuvieron CockroachDB, pero dejaron de intentar que se comporte como una base de datos de nodo único. Esa fue la verdadera migración.

Mini-historia 3: La práctica aburrida pero correcta que salvó el día (PITR en Postgres)

Una plataforma B2B ejecutaba Postgres con replicación por streaming y un régimen estricto de PITR: backups base diarios,
archivado WAL validado cada día y simulacros de restauración trimestrales. A nadie le gustaban los simulacros. Ese era el punto.

Un ingeniero ejecutó una migración que eliminó una columna en el esquema equivocado. La app no falló de inmediato; falló gradualmente a medida que ciertos jobs corrían.
La réplica replicó fielmente el error, porque las réplicas son obedientes así.

El equipo enfrentó una bifurcación conocida: intentar parchear hacia adelante bajo presión, o restaurar a un punto conocido bueno.
Como tenían archivos WAL fiables y pasos ensayados, restauraron a un timestamp minutos antes de la migración y reprodujeron cambios seguros.

El outage se midió en decenas de minutos, no en horas. El postmortem fue comprensiblemente corto.
Los héroes no fueron los que escribieron scripts ingeniosos; fueron los que probaron restauraciones cuando no había incendio.

La HA redujo el downtime por fallos de nodo. PITR los salvó de sí mismos. Herramientas diferentes. Ambas obligatorias.

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

1) Síntoma: tras el failover, las escrituras tienen éxito pero las lecturas muestran datos antiguos

Causa raíz: la aplicación aún lee desde una réplica atrasada, o el enrutamiento lectura/escritura no se actualizó correctamente.

Solución: fuerza enrutamiento a través de un endpoint único con conocimiento de roles; añade reglas read-after-write; monitoriza el replay delay de réplicas.

2) Síntoma: el primario de Postgres se vuelve “lento cada pocos minutos”

Causa raíz: picos de checkpoint o presión de flush de WAL por límites de almacenamiento y configuración de checkpoints agresiva.

Solución: aumenta max_wal_size, ajusta parámetros de checkpoint y mueve WAL/datos a almacenamiento más rápido; verifica latencias de fsync.

3) Síntoma: las réplicas de Postgres se retrasan durante jobs por lotes

Causa raíz: ráfagas de WAL + cuello de botella de I/O en réplicas, a menudo agravado por transacciones largas que retrasan limpieza.

Solución: limita escrituras por lotes, aumenta recursos de réplicas y elimina transacciones largas; considera particionamiento lógico o programar jobs fuera de pico.

4) Síntoma: CockroachDB muestra “nodos saludables” pero la app hace timeout

Causa raíz: contención de transacciones y reintentos, frecuentemente por claves calientes o transacciones demasiado conversadoras.

Solución: rediseña hotspots (sharding de claves, aleatorización), acorta transacciones, implementa reintentos acotados con backoff y reduce patrones de contención.

5) Síntoma: CockroachDB se vuelve inestable durante mantenimiento de nodos

Causa raíz: margen insuficiente; reequilibrado y re-replicación compiten con la carga de producción.

Solución: añade capacidad, drena/descomisiona lentamente y evita bajar múltiples nodos; programa ventanas de mantenimiento con límites conscientes del tráfico.

6) Síntoma: HA de Postgres “funciona”, pero ocasionalmente tienes dos primarios

Causa raíz: split brain por elección de líder no fiable, falta de fencing o comportamiento de partición de red no modelado.

Solución: implementa elección basada en quórum/almacén de locks (etcd/consul), fencing fiable (STONITH donde aplique) y prueba escenarios de partición.

7) Síntoma: “No podemos restaurar rápido” a pesar de tener backups

Causa raíz: los backups nunca se restauraron en la práctica; archivos WAL faltantes; credenciales/permisos obsoletos.

Solución: programa simulacros de restauración, verifica el archivado diariamente y automatiza validaciones; trata las restauraciones como una característica de producción.

Listas de verificación / plan paso a paso

Lista de decisión: Postgres HA vs CockroachDB

  1. Define RPO/RTO por servicio. Si no lo puedes escribir, estás adivinando.
  2. Mide tus necesidades de localidad de escritura. ¿Dónde se hacen las escrituras? Si es “en todas partes”, demuéstralo con trazas.
  3. Clasifica la carga: OLTP de alta contención, append-heavy, mixto lectura/escritura, transacciones largas, analítica en background.
  4. Lista características imprescindibles: extensiones, comportamiento de aislamiento específico, objetos grandes, índices especializados, decodificación lógica, etc.
  5. Realidad de personal: ¿tienes operadores que pueden depurar consenso y contención, o un equipo experto en internals de Postgres?
  6. Simulacros de fallo: ¿puedes ensayar pérdida de región y demostrar recuperación en staging con carga parecida a producción?

Paso a paso: construir HA “aburrida” en PostgreSQL

  1. Elige una topología: primario + 2 réplicas entre zonas; decide cuáles son endpoints solo de lectura.
  2. Implementa replicación: streaming replication; decide async vs sync según presupuesto de latencia.
  3. Añade orquestación: un gestor real de failover; evita scripts DIY a menos que disfrutes la arqueología.
  4. Añade enrutamiento: endpoints estables para apps; separación lectura/escritura explícita si usas réplicas.
  5. Prevén split brain: elección de líder basada en quórum; estrategia de fencing; prueba particiones.
  6. Backups y PITR: backups base + archivado WAL; valida diariamente; practica restauraciones.
  7. Observabilidad: lag de replicación, esperas de lock, tasa de WAL, timing de checkpoints, latencia de disco.
  8. Runbooks: promover, degradar, rewind, reconstruir réplica; documenta “qué no hacer” durante un incidente.

Paso a paso: operar CockroachDB sin autolesionarse

  1. Comienza con 3+ nodos en 3 zonas (o más para margen). No ejecutes un clúster “justo suficiente” en producción.
  2. Define localidad y constraints antes de necesitarlos. Decide dónde deberían vivir leases/líderes para tablas críticas.
  3. Modela la contención temprano: identifica contadores, actualizaciones de una sola fila, claves secuenciales y patrones de “último registro”.
  4. Implementa reintentos correctamente en la aplicación con backoff acotado. Reintentos sin límite son una característica de denegación de servicio distribuida.
  5. Vigila salud de ranges: sub-replicación, ranges indisponibles, concentración de leaseholders.
  6. Planifica mantenimiento teniendo en cuenta el reequilibrado: descomisiona lentamente, mantén margen, evita drenas multi-nodo bajo carga.
  7. Benchmark con concurrencia realista, no con un script sintético amable que nunca compite.

Preguntas frecuentes

1) ¿Puede Postgres ser “activo-activo” para escrituras?

No en el modelo central de un único primario. Puedes aproximarlo con sharding, enrutamiento a nivel de aplicación o soluciones multi-master especializadas,
pero te apuntas a gestionar conflictos y complejidad operativa. Si realmente necesitas multi-writer con consistencia fuerte,
un sistema basado en consenso puede encajar mejor—si puedes manejar los tradeoffs.

2) ¿CockroachDB garantiza cero downtime?

Puede sobrevivir a muchas fallas de nodo sin promoción manual, pero “cero downtime” depende del quórum, margen de capacidad
y si tu carga provoca contención/reintentos. Es resistente, no invencible.

3) ¿Cuál es la configuración HA más simple de Postgres que no me odiará después?

Primario + dos réplicas entre zonas, un gestor de failover probado (no scripts cron), un endpoint estable para escritores
y PITR probado. Mantenlo aburrido, automatiza la paridad de configuración y ensaya los failovers.

4) ¿La replicación síncrona en Postgres es siempre mejor?

Reduce RPO, pero aumenta latencia y puede bloquear escrituras si los standbys síncronos no están disponibles.
Úsala cuando puedas permitir la latencia y tengas conectividad de standby fiable. Si no, usa async más PITR robusto.

5) ¿Por qué las aplicaciones con CockroachDB necesitan lógica de reintento?

Porque las transacciones serializables bajo contención pueden verse forzadas a reintentar para preservar corrección.
Si no gestionas los reintentos correctamente, convertirás contención transitoria en errores visibles para el usuario.

6) ¿Cuál es más fácil de depurar a las 2 a. m.?

Postgres suele ser más fácil si tu incidente es “un nodo está lento” o “lag de réplica”, porque las piezas móviles son menos.
CockroachDB puede ser más fácil para fallos sencillos de nodo, pero más difícil para patologías de rendimiento y contención.

7) ¿Puedo levantar y mover un esquema Postgres a CockroachDB?

Puedes migrar mucho SQL, pero no asumas comportamiento idéntico alrededor de locks, casos límite de aislamiento, patrones de sequences/serial,
y disponibilidad de extensiones. El mayor riesgo no es la sintaxis—es las características de carga como contención y distribución de claves.

8) ¿Y los backups—CockroachDB elimina la necesidad de pensar en PITR?

No. HA gestiona fallos de nodo; los backups gestionan fallos humanos y corrupción lógica. Aún necesitas procedimientos de restauración probados.
Base de datos distinta, misma realidad.

9) ¿Debo usar réplicas de lectura con Postgres si me importa la corrección?

Sí, pero sé explícito. Enruta solo tráfico de lectura seguro a réplicas, o implementa reglas de read-after-write.
Si tratas réplicas como totalmente consistentes, tarde o temprano introducirás un bug como característica.

10) ¿CockroachDB es siempre más lento porque es distribuido?

No siempre, pero la coordinación distribuida añade coste base y la latencia extrema puede aumentar bajo contención.
Para algunas cargas es excelente; para OLTP de baja latencia en una sola región con muchas escrituras, Postgres suele ganar en velocidad bruta.

Conclusión: próximos pasos que puedes ejecutar esta semana

La HA en PostgreSQL es un sistema que construyes; la HA en CockroachDB es un sistema al que te unes. Ambos pueden ejecutar cargas de producción de forma fiable.
Ambos pueden arruinarte el fin de semana si los tratas como magia.

Aquí tienes qué hacer a continuación, en orden:

  1. Escribe RPO/RTO por servicio y consigue aprobación del negocio. Quieres menos sorpresas que tus auditores.
  2. Realiza un simulacro de fallo: mata un nodo, particiona una ruta de red y mide tiempo de recuperación de extremo a extremo (incluyendo clientes).
  3. Implementa el guion de diagnóstico rápido como un runbook y ensáyalo. La velocidad viene de la repetición, no del genio.
  4. Elige un dolor para eliminar primero: prevención de split-brain en Postgres, o mitigación de contención/hotspots en Cockroach.
  5. Prueba restauraciones. No “tenemos backups”, sino “restauramos el backup del martes pasado en un entorno limpio y verificamos la corrección”.

Si quieres HA sin drama, elige la arquitectura que coincida con los hábitos de tu organización.
Si quieres HA con nuevos tipos de dolor, elige la que coincida con la curiosidad de tu organización.
En cualquier caso, mantenlo aburrido donde importa: backups, simulacros y propiedad clara.

← Anterior
Elección del planificador IO de ZFS: mq-deadline vs none para HDD, SSD y NVMe
Siguiente →
Chiplets de AMD: El truco que resucitó a Ryzen

Deja un comentario