MySQL vs CockroachDB: SQL distribuido en servidores pequeños — el impuesto de latencia que nadie menciona

¿Te fue útil?

Compraste “tres servidores pequeños” porque a los presupuestos les encantan los tres. Luego desplegaste una base de datos SQL distribuida porque al marketing le encanta la palabra “resiliente”.
Ahora la latencia p95 de tu panel parece que tomó la ruta panorámica, y cada revisión de incidentes incluye la frase “pero funcionó bien en local”.

Este es el impuesto de latencia que nadie pone en la diapositiva: la corrección distribuida tiene un coste por consulta muy real, y en servidores pequeños ese coste se vuelve ruidoso.
No catastrófico. Lo suficientemente molesto para arruinarte el sueño.

El impuesto de latencia: lo que pagas por el consenso

Si recuerdas una cosa, que sea esta: la ruta OLTP por defecto de MySQL es “hacer la escritura localmente y luego replicar”.
La ruta OLTP por defecto de CockroachDB es “coordinar la escritura con un quórum y luego confirmar”.

En una red decente y con discos sanos, las escrituras por quórum están bien. En servidores pequeños con almacenamiento commodity y redes de 1Gbps (o peor: virtualizadas “best effort”),
las escrituras por quórum se convierten en un amplificador de latencia. No siempre en la latencia media. En la latencia de cola. Esa que los usuarios notan y los SREs heredan.

La diferencia operacional no es sutil:

  • MySQL: Un único primario puede ser extremadamente rápido. También es un punto único donde la física es amable y la corrección es local. Las réplicas sirven para escalar lecturas y failover. La consistencia es una elección de política.
  • CockroachDB: Cada escritura participa en el consenso para el rango que toca. La consistencia es el valor por defecto, no un añadido. La latencia es la tarifa.

Un encuadre práctico: si tu aplicación es sensible a la latencia p99 y estás ejecutando nodos pequeños, SQL distribuido puede ser como contratar a un comité para aprobar cada correo que envías.

Una cita que sobrevive a muchas revisiones de incidentes: Todo falla, todo el tiempo. — Werner Vogels.
No hablaba específicamente de tu clúster de tres nodos, pero podría haberlo hecho.

Dónde aparece el impuesto (y por qué sorprende a la gente)

La sorpresa no es que un sistema distribuido tenga sobrecarga. La sorpresa es dónde se manifiesta.
Mirarás la CPU y verás mucho margen. Mirarás el tiempo medio de consulta y te sentirás bien. Entonces abrirás el histograma y verás p95/p99 dispararse.

Lugares comunes donde aparece el impuesto:

  • Escrituras por quórum: un commit necesita acuses de recibo de la mayoría de réplicas del rango. Eso es al menos un viaje de ida y vuelta, a veces más.
  • Consistencia de lecturas: las lecturas fuertemente consistentes pueden necesitar coordinación (la ubicación del leaseholder importa). Las lecturas obsoletas pueden ser más rápidas, pero debes optar por ellas y entender las consecuencias.
  • Divisiones y reequilibrios de rangos: el trabajo en segundo plano no es “gratis”. En cajas pequeñas compite directamente con la latencia de primer plano.
  • Compacciones: los motores basados en LSM intercambian amplificación de escritura por eficiencia de lectura. Esa amplificación se convierte en un impuesto de disco y CPU en momentos inoportunos.

Dos números que deberían perseguir tu hoja de dimensionamiento

No puedes “optimizar” la física, solo rodearla.

  • RTT de red entre nodos: cada milisegundo importa cuando tu ruta de commit incluye coordinación entre nodos. Un RTT de 1–2ms suena bien hasta que necesitas múltiples saltos bajo carga.
  • Latencia de fsync en almacenamiento: si tu capa de almacenamiento ocasionalmente convierte fsync en una aventura de 20–100ms, el consenso distribuido incorporará fielmente esa aventura en la latencia visible al usuario.

Broma #1: Un clúster de tres nodos en VMs pequeñas es como una carrera de relevos de tres personas donde todos insisten en atarse los zapatos entre los traspasos.

Hechos e historia que puedes usar en discusiones

Esto no es trivia para un quiz en el bar. Explican por qué los sistemas se comportan como lo hacen.

  1. MySQL se lanzó a mediados de los 90 y se convirtió en la opción por defecto para OLTP web porque era fácil de desplegar, fácil de replicar y rápido en una sola máquina.
  2. InnoDB se convirtió en el motor de almacenamiento por defecto de MySQL (después de años de predominio de MyISAM), aportando recuperación ante fallos, bloqueo a nivel de fila y transacciones reales a despliegues comunes.
  3. La mayoría de patrones HA de MySQL históricamente favorecieron la replicación asincrónica porque es simple y mantiene baja la latencia del primario; la compensación es la posible pérdida de datos en failover.
  4. Algoritmos de consenso como Raft fueron diseñados para hacer el consenso distribuido más fácil de entender e implementar que Paxos, no gratuitos en latencia.
  5. CockroachDB se construyó con Raft desde el principio, buscando semánticas SQL tipo PostgreSQL con un diseño shared-nothing y geo-distribuido.
  6. Google Spanner popularizó “SQL distribuido con fuerte consistencia” usando TrueTime; CockroachDB persigue objetivos similares sin hardware de reloj especializado, lo que afecta cómo aparecen la incertidumbre y los reintentos de transacción.
  7. Los motores de almacenamiento LSM-tree se volvieron comunes en bases de datos distribuidas porque manejan bien alto throughput de escritura, pero requieren compacción—es decir, I/O en segundo plano que puede morder la latencia de cola.
  8. La era del teorema CAP convirtió “consistencia vs disponibilidad” en conversación de junta; los sistemas SQL distribuidos típicamente eligen consistencia y tolerancia a particiones, forzando compromisos de disponibilidad durante particiones.
  9. La latencia p99 se volvió obsesión SRE porque la experiencia de usuario está dominada por el comportamiento de cola, no por los promedios; la coordinación distribuida es un generador clásico de latencia en cola.

Por qué los servidores pequeños empeoran la percepción del SQL distribuido

“Servidores pequeños” suele significar alguna combinación de: menos núcleos, menos RAM, NVMe más lento (o peor: adjunto por red), vecinos menos predecibles y una red que fue dimensionada para tráfico “normal”.
SQL distribuido no odia los servidores pequeños. Simplemente se niega a pretender que son servidores grandes.

Los nodos pequeños no solo reducen capacidad; reducen margen

El margen es el colchón invisible que mantiene la latencia de cola estable.
En una máquina grande, compactions, flushes o una ligera variación de red pueden ser absorbidos.
En un nodo pequeño, el mismo trabajo en segundo plano compite con el tráfico de primer plano y gana más a menudo de lo que te gustaría.

Los clústeres de tres nodos son seductores operacionalmente (y mecánicamente duros)

Tres nodos es la forma mínima que parece “un clúster”. También es la forma mínima en la que cualquier perturbación puede hacerse visible para el usuario.
Pierde un nodo y entras en modo “por favor, no más fallos”.

  • MySQL en tres nodos típicamente significa 1 primario + 2 réplicas. El primario se mantiene caliente; las réplicas son principalmente pasajeras hasta el failover.
  • CockroachDB en tres nodos significa que los tres están en la ruta de escritura (quórum). La salud de cada nodo importa cada minuto.

El asesino silencioso: la desigualdad

Las flotas pequeñas rara vez son homogéneas. Un nodo está en un hipervisor un poco más ruidoso.
Un disco es ligeramente más lento.
Una NIC comparte interrupciones con algo descortés.
Los sistemas distribuidos tienden a correr al ritmo de su peor participante—especialmente en p99.

MySQL: la referencia de baja latencia con bordes afilados

El superpoder de MySQL es aburrido: una máquina, un registro de commits, una jerarquía de caché.
Si tu carga encaja en un primario, MySQL puede ofrecer latencias muy bajas con comportamiento predecible.
Puedes ejecutar MySQL mal, pero tienes que esforzarte para lograrlo.

En qué destaca MySQL (en servidores pequeños)

  • Escrituras de fila única de baja latencia cuando tu esquema e índices son sensatos.
  • p95/p99 estable cuando el almacenamiento es decente y evitas puntos calientes de contención.
  • Simplicidad operacional para equipos que toleran un primario único y tienen un plan claro de failover.

Los modos de fallo de MySQL no son sutiles

  • Saturación del primario: no obtienes degradación elegante; obtienes una cola.
  • Retraso en la replicación: el escalado de lecturas y la seguridad en failover dependen de un stream async rezagado a menos que pagues el coste sync.
  • Corrección en failover: el split brain es evitable, pero no automático; necesitas herramientas y disciplina.

Chequeo de realidad de MySQL

Si necesitas consistencia fuerte entre varios escritores en múltiples ubicaciones, MySQL no se convertirá mágicamente en una base de datos distribuida.
Puedes construir eso con semi-sync replication, group replication o coordinación externa—pero te ganarás cada pager.

CockroachDB: corrección a escala, incluso cuando no necesitabas escala

El argumento de CockroachDB es simple: mantener SQL, perder la fragilidad del primario único.
Las escrituras se replican. Las lecturas pueden servirse desde el “sitio correcto”. Los failovers están automatizados y son rápidos.
Es ingeniería real, no polvo de hadas.

En qué destaca CockroachDB (incluso en servidores pequeños)

  • Sobrevivir fallos de nodo sin rituales manuales de failover.
  • Escalar lecturas y escrituras añadiendo nodos (dentro de lo razonable y prestando atención a la localidad).
  • Semánticas consistentes que mantienen la lógica de la aplicación más simple que la replicación hecha a mano.

Lo que CockroachDB te exige

  • Respeto por la latencia: estás coordinando, así que mide red y disco como si importara.
  • Comprensión de la contención: los puntos calientes no desaparecen en un clúster; se convierten en puntos calientes distribuidos con reintentos.
  • Decisiones de localidad cuidadosas: leaseholders y réplicas deben colocarse intencionalmente si te importa la latencia.

Reintentos de transacción: la “característica” que se siente como un bug

CockroachDB reintentará transacciones bajo contención o incertidumbre. Eso es parte de cómo preserva la corrección.
El síntoma son errores a nivel de aplicación (o reintentos del driver) y picos de latencia que parecen aleatorios hasta que los correlacionas con métricas de contención.

Broma #2: SQL distribuido es el único producto que puede venderte “sin punto único de fallo” y aun así fallar tu consulta porque dos relojes discreparon educadamente.

Por cargas: quién gana y por qué

OLTP simple (lecturas/escrituras de fila única, SLO estricto en p99)

En servidores pequeños, MySQL suele ganar en latencia pura. El commit es local. La caché es local. La ruta de código es madura y corta.

CockroachDB puede cumplir SLOs razonables aquí, pero sentirás más los costes del quórum y del motor de almacenamiento, especialmente si tus nodos están poco potentes o tu red está nerviosa.
Si tu producto vive o muere por p99 de 5–20ms para transacciones pequeñas, no sustituyas casualmente una base de datos basada en consenso esperando que “se ajuste”.

Alta disponibilidad con failover automático

CockroachDB gana en comportamiento operacional. Si no tienes personal para ejecutar ensayos de failover de MySQL con rigor, acabarás fallando el failover.
CockroachDB hace lo “aburrido” (pérdida de nodo) aburrido.

MySQL puede ser altamente disponible, pero debes implementarlo y ensayarlo. La tecnología existe. La consistencia organizacional a menudo no.

Escalado de escrituras (múltiples escritores, crecimiento horizontal)

El escalado horizontal de escrituras no es la zona de confort nativa de MySQL.
Puedes fragmentar (shard), pero entonces tu app se convierte en una capa de enrutamiento. Es una arquitectura legítima, pero es un compromiso.

CockroachDB está diseñado para escalar escrituras dividiendo rangos y distribuyéndolos. En nodos pequeños aún puedes estar limitado por disco y CPU por nodo,
pero al menos el objetivo de diseño coincide con el requisito.

Escalado de lecturas

Las réplicas de lectura de MySQL son toscas pero efectivas. También son operativamente indulgentes: si una réplica se retrasa, enrutas alrededor.
CockroachDB también puede distribuir lecturas, pero debes entender leaseholders y localidad; de lo contrario las lecturas rebotan por el clúster y pagan saltos extra.

Latencia multi-región

Si eres realmente multi-región, la velocidad de la luz se convierte en tu product manager. La consistencia fuerte a través de océanos es cara.
CockroachDB te da herramientas para colocar datos y controlar la localidad de lecturas/escrituras, pero si exiges milisegundos de un solo dígito globalmente, tu demanda está equivocada.

MySQL en multi-región a menudo se convierte en un primario en una región como verdad con réplicas asincrónicas en otras. Eso es rápido localmente y “eventualmente correcto” remotamente.
A veces eso es exactamente lo que quieres.

Evolución de esquema y fricción operacional

Ambos tienen escollos en cambios de esquema. MySQL cuenta con décadas de experiencia operativa para cambios de esquema en línea.
CockroachDB soporta cambios de esquema en línea pero aún puede sorprenderte con backfills y su impacto en clústeres pequeños.

Tareas prácticas: comandos, salidas y la decisión que orientan

No solucionas la latencia distribuida con buenas intenciones. La arreglas con mediciones que señalan la parte más lenta de la ruta de commit:
red, disco, contención o topología.

Abajo hay tareas concretas. Cada una incluye (1) un comando, (2) salida representativa, (3) qué significa y (4) la decisión que tomas.
Ejecútalas en tus nodos reales. Preferiblemente durante una prueba de carga que se parezca a producción, no en un martes tranquilo.

Tarea 1: Confirmar RTT de red entre nodos de base de datos

cr0x@server:~$ ping -c 5 db-node-2
PING db-node-2 (10.0.0.12) 56(84) bytes of data.
64 bytes from 10.0.0.12: icmp_seq=1 ttl=64 time=0.78 ms
64 bytes from 10.0.0.12: icmp_seq=2 ttl=64 time=0.91 ms
64 bytes from 10.0.0.12: icmp_seq=3 ttl=64 time=1.02 ms
64 bytes from 10.0.0.12: icmp_seq=4 ttl=64 time=0.85 ms
64 bytes from 10.0.0.12: icmp_seq=5 ttl=64 time=0.80 ms

--- db-node-2 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4092ms
rtt min/avg/max/mdev = 0.78/0.87/1.02/0.09 ms

Significado: RTT submilisegundo a ~1ms es aceptable para un clúster pequeño. Si ves 3–10ms dentro de un “centro de datos”, espera dolor en p99 bajo escrituras por quórum.

Decisión: Si el RTT es alto o volátil, arregla la colocación de red (mismo rack/zona), ajustes de offload de la NIC y problemas de vecinos ruidosos antes de culpar a la base de datos.

Tarea 2: Medir latencia TCP y retransmisiones bajo carga

cr0x@server:~$ ss -ti dst 10.0.0.12:26257 | head -n 12
ESTAB 0 0 10.0.0.11:52144 10.0.0.12:26257
	 cubic wscale:7,7 rto:204 rtt:1.3/0.4 ato:40 mss:1448 pmtu:1500 rcvmss:1448 advmss:1448 cwnd:10 bytes_acked:812345 segs_out:12034 segs_in:11877 send 89.2Mbps lastsnd:12 lastrcv:12 lastack:12 pacing_rate 178Mbps retrans:3/12034

Significado: rtt muestra el RTT observado y su variación. retrans indica pérdida de paquetes o congestión.

Decisión: Si las retransmisiones suben durante picos, tu latencia de cola no es “tunning de base de datos”. Es congestión. Arregla QoS, ancho de banda, problemas MTU o tráfico este-oeste ruidoso.

Tarea 3: Comprobar la distribución de latencia de disco (el verdadero villano)

cr0x@server:~$ iostat -x 1 5
Linux 6.5.0 (db-node-1) 	12/30/2025 	_x86_64_	(8 CPU)

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          18.20    0.00    6.40    3.60    0.00   71.80

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         210.0   8120.0     0.0    0.0    1.20    38.7     480.0   24576.0     0.0    0.0   14.80    51.2   7.40  92.0

Significado: w_await en ~15ms con %util ~92% significa que las escrituras están encolándose. Ahí es donde p99 va a morir.

Decisión: Si la utilización del disco es consistentemente alta o los await hacen picos, muévete a almacenamiento más rápido, separa dispositivos WAL, reduce la amplificación de escritura o baja la presión de compacción antes de cambiar parámetros SQL.

Tarea 4: Validar opciones del sistema de archivos y montaje

cr0x@server:~$ findmnt -no TARGET,SOURCE,FSTYPE,OPTIONS /var/lib
/var/lib /dev/nvme0n1p2 ext4 rw,relatime,discard,data=ordered

Significado: Opciones como discard pueden añadir latencia en algunas configuraciones; los valores por defecto de ext4 suelen estar bien, pero los detalles importan.

Decisión: Si ves sistemas de archivos en red, thin-provisioning extraño o opciones inseguras, arregla el almacenamiento primero. El consenso distribuido no perdona discos inestables.

Tarea 5: Comprobar tiempo de steal de CPU (impuesto de virtualización)

cr0x@server:~$ mpstat 1 5 | tail -n 7
12:22:11 PM  all   15.40    0.00    6.90    2.10    0.00    4.80    0.00   70.80
12:22:12 PM  all   17.20    0.00    8.10    2.40    0.00    6.20    0.00   66.10
12:22:13 PM  all   14.80    0.00    6.30    1.90    0.00    7.60    0.00   69.40

Significado: Si ves %steal no trivial (no mostrado aquí; es 0.00), el hipervisor está tomando tiempo de tu CPU. Eso infla la latencia de coordinación.

Decisión: Steal alto → muévete a instancias dedicadas, fija vCPUs o acepta que tu p99 ahora es un recurso compartido con los jobs por lotes de tus vecinos.

Tarea 6: Medir comportamiento de fsync rápidamente con fio

cr0x@server:~$ fio --name=fsync-test --directory=/var/lib/dbtest --size=1G --bs=4k --rw=write --ioengine=sync --fdatasync=1 --runtime=30 --time_based --direct=1
fsync-test: (g=0): rw=write, bs=(R) 4096B-4096B, (W) 4096B-4096B, (T) 4096B-4096B, ioengine=sync, iodepth=1
fio-3.33
Starting 1 process
fsync-test: (groupid=0, jobs=1): err= 0: pid=22811: Tue Dec 30 12:23:40 2025
  write: IOPS=4200, BW=16.4MiB/s (17.2MB/s)(492MiB/30001msec); 0 zone resets
    clat (usec): min=70, max=24500, avg=220, stdev=410
    clat percentiles (usec):
     |  95.00th=[  310],  99.00th=[  950],  99.90th=[ 8500]

Significado: El 99.90th en 8.5ms es aceptable; si ves 50–200ms en el 99.9th, tu base de datos periódicamente “se congelará” en p99.

Decisión: Cola de fsync mala → cambia la clase de almacenamiento, desactiva las mentiras del caché de escritura, asegura protección contra pérdida de energía o mueve WAL/pebble/redo logs a mejor medio.

Tarea 7: MySQL—comprobar política de flush de InnoDB

cr0x@server:~$ mysql -e "SHOW VARIABLES LIKE 'innodb_flush_log_at_trx_commit';"
+--------------------------------+-------+
| Variable_name                  | Value |
+--------------------------------+-------+
| innodb_flush_log_at_trx_commit | 1     |
+--------------------------------+-------+

Significado: 1 es lo más seguro: flush en cada commit. 2 o 0 pueden reducir latencia pero arriesgan perder commits recientes tras un crash.

Decisión: Si comparas MySQL con CockroachDB, mantén esto en 1 por honestidad. Si lo cambias, escribe el riesgo en tu runbook de incidentes.

Tarea 8: MySQL—verificar que semi-sync esté realmente activado (o no)

cr0x@server:~$ mysql -e "SHOW STATUS LIKE 'Rpl_semi_sync_master_status';"
+-----------------------------+-------+
| Variable_name               | Value |
+-----------------------------+-------+
| Rpl_semi_sync_master_status | OFF   |
+-----------------------------+-------+

Significado: Si semi-sync está OFF, los commits del primario no esperan acuse de recibo de réplicas. Por eso MySQL es rápido, y también por eso el failover puede perder datos.

Decisión: Si el negocio requiere “sin pérdida de datos”, o activas semi-sync (pagando latencia) o dejas de fingir que la replicación async cumple ese requisito.

Tarea 9: MySQL—detectar lag de replicación y decidir qué significa “fresco”

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

Significado: 42 segundos de retraso no es “un poco.” Es otra base de datos.

Decisión: Enruta lecturas que necesitan frescura al primario, ajusta la replicación (aplicación paralela, arreglos de esquema/índices) o acepta explícitamente la consistencia eventual en el comportamiento de la aplicación.

Tarea 10: CockroachDB—comprobar salud y liveness de nodos

cr0x@server:~$ cockroach node status --insecure
  id |            address            |  sql_address  |  build  |            started_at            |            updated_at            | is_available | is_live
-----+-------------------------------+---------------+---------+----------------------------------+----------------------------------+--------------+----------
   1 | db-node-1:26257               | db-node-1:26257 | v23.2.6 | 2025-12-30 11:02:17.123456+00:00 | 2025-12-30 12:25:01.123456+00:00 | true         | true
   2 | db-node-2:26257               | db-node-2:26257 | v23.2.6 | 2025-12-30 11:02:19.223456+00:00 | 2025-12-30 12:25:00.923456+00:00 | true         | true
   3 | db-node-3:26257               | db-node-3:26257 | v23.2.6 | 2025-12-30 11:02:21.323456+00:00 | 2025-12-30 12:25:01.003456+00:00 | true         | true

Significado: Todos los nodos están vivos y disponibles. Si uno pasa a no disponible de forma intermitente, espera picos de latencia de escritura y reintentos de transacción.

Decisión: Arregla la estabilidad del nodo antes de tunear SQL. “Flapea a veces” no es un detalle menor en sistemas de consenso.

Tarea 11: CockroachDB—inspeccionar distribución de rangos y sub-replicación

cr0x@server:~$ cockroach node ranges --insecure
  node_id | ranges | leaseholders | replicas | avg_range_size
----------+--------+--------------+----------+----------------
        1 |    412 |          190 |      824 |        64MiB
        2 |    398 |          145 |      796 |        66MiB
        3 |    421 |          205 |      842 |        63MiB

Significado: Balance razonable. Si un nodo tiene la mayoría de leaseholders, se convierte en el punto caliente de latencia para lecturas/escrituras de esos rangos.

Decisión: Si hay sesgo, ajusta restricciones de localidad, añade capacidad o investiga por qué el reequilibrio está atascado (a menudo disco o red).

Tarea 12: CockroachDB—buscar contención y reintentos en estadísticas SQL

cr0x@server:~$ cockroach sql --insecure -e "SELECT app_name, sum(retry_count) AS retries, sum(cnt) AS stmts FROM crdb_internal.statement_statistics GROUP BY app_name ORDER BY retries DESC LIMIT 5;"
  app_name  | retries | stmts
------------+---------+--------
  web-api   |    1842 |  98412
  worker    |     210 |  12001
  console   |       0 |   1422

Significado: Tu API está siendo reintentada mucho. Eso es tiempo que el usuario experimenta como latencia (aunque la transacción finalmente tenga éxito).

Decisión: Encuentra claves calientes, reduce la contención (shardear contadores, agrupar escrituras de otra forma) y asegúrate de que los reintentos del cliente estén bien configurados y acotados en vez de fallar de forma estruendosa.

Tarea 13: CockroachDB—identificar localidad del leaseholder para una tabla caliente

cr0x@server:~$ cockroach sql --insecure -e "SHOW RANGES FROM TABLE app.orders;"
  start_key | end_key | range_id | replicas | lease_holder
------------+---------+----------+----------+--------------
  NULL      | /1000   |    84    | {1,2,3}  |      2
  /1000     | /2000   |    91    | {1,2,3}  |      2
  /2000     | /3000   |    99    | {1,2,3}  |      1

Significado: Leaseholders concentrados en el nodo 2 significa que el nodo 2 maneja la “coordinación” para esos rangos. Si el nodo 2 es más lento, todos son más lentos.

Decisión: Fija leaseholders mediante zone configs/locality, o arregla el disco/red/CPU del nodo 2 para que deje de ser el termómetro del clúster.

Tarea 14: Comparar distribuciones de latencia de consultas desde el cliente

cr0x@server:~$ pt-query-digest --type slowlog /var/log/mysql/mysql-slow.log | head -n 12
# 2.1s user time, 20ms system time, 28.11M rss, 100.00% CPU
# Query_time distribution
#   1us
#  10us
# 100us  ################################################
#   1ms   ###############################################
#  10ms   ######
# 100ms   ##
#    1s   #

Significado: La distribución te dice si tienes un pequeño número de consultas patológicas lentas o una degradación amplia en la cola.

Decisión: Pocos patológicos → arreglos de índices/plan. Cola amplia → mira almacenamiento/red/contención de locks y trabajo en segundo plano.

Tarea 15: Comprobar scheduler de Linux y desequilibrio de IRQ (clásico en nodos pequeños)

cr0x@server:~$ cat /proc/interrupts | egrep "eth0|nvme" | head -n 6
  48:  18823401        0        0        0   IR-PCI-MSI 524288-edge      eth0-TxRx-0
  49:         0        0        0        0   IR-PCI-MSI 524289-edge      eth0-TxRx-1
  50:         0        0        0        0   IR-PCI-MSI 524290-edge      eth0-TxRx-2
  51:         0        0        0        0   IR-PCI-MSI 524291-edge      eth0-TxRx-3

Significado: Si todas las interrupciones caen en una CPU (como arriba), puedes obtener latencias raras bajo carga incluso con uso medio de CPU “bajo”.

Decisión: Activa irqbalance o fija colas correctamente; para nodos pequeños, esto puede ser la diferencia entre p99 estable y el caos.

Guía rápida de diagnóstico

Cuando la latencia sube, no tienes tiempo para filosofía. Necesitas una secuencia corta que encuentre el cuello de botella con alta probabilidad.
Aquí está el playbook que uso cuando alguien dice, “Cockroach está lento” o “MySQL de repente va con lag” en servidores pequeños.

Primero: ¿es red, disco o contención?

  1. Comprueba RTT y retransmisiones entre nodos (ping + ss).
    Si RTT/jitter o retransmisiones correlacionan con los picos de latencia, para y arregla la red.
  2. Comprueba await y utilización del disco (iostat).
    Si las escrituras se encolan (w_await elevado, %util alto), para y arregla almacenamiento o amplificación de escritura.
  3. Comprueba reintentos de transacción / esperas por locks (estadísticas SQL de Cockroach para reintentos; performance_schema o InnoDB status en MySQL para waits).
    Si los reintentos/esperas suben, tienes contención, no “hardware lento”.

Segundo: confirma que la topología y la localidad son sensatas

  1. CockroachDB: colocación de leaseholders para rangos calientes; balance de distribución de rangos; sub-replicación.
  2. MySQL: roles primario/replica, lag de replicación, enrutamiento de lecturas y si semi-sync está inesperadamente activado (o desactivado).

Tercero: identifica trabajo en segundo plano que te está robando recursos

  1. CockroachDB: compactions, reequilibrio de rangos, backfills de esquema. En nodos pequeños pueden ser ruidosos.
  2. MySQL: purge lag, presión de checkpoints, transacciones largas o un job de backup que hace I/O “educado” que en realidad no lo es.

Condiciones de parada (aka evita tuning aleatorio)

  • Si iostat muestra await alto, no toques ajustes SQL todavía.
  • Si las retransmisiones de paquetes suben, no rediseñes el esquema aún.
  • Si los reintentos/esperas dominan, no compres hardware nuevo antes de arreglar el hotspot.

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

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

Una empresa SaaS mediana migró un servicio de perfiles de usuario de MySQL a CockroachDB. El documento de arquitectura decía: “Necesitamos HA; un clúster de tres nodos nos lo da.”
El equipo era inteligente, el código estaba limpio y los ensayos de migración fueron bien—en un entorno de staging con baja carga y vecinos amigables.

La suposición que falló: “Si cada nodo está por debajo del 30% de CPU, tenemos mucho margen.”
La latencia en producción se disparó tras el lanzamiento, pero la CPU permanecía tranquila. El ingeniero on-call miró las gráficas de CPU como si fuesen a confesar.
Mientras tanto, los usuarios esperaban entre 200–600ms extra para actualizaciones de perfil en picos.

El verdadero cuello de botella fue la latencia de cola del almacenamiento en un nodo. Su SSD tenía pausas largas ocasionales en fsync.
MySQL había enmascarado esto antes porque el primario estaba en otra máquina y el lag de replicación no se hacía visible al usuario.
En CockroachDB, ese nodo formaba parte del quórum para una gran parte de los rangos, así que sus pausas se convirtieron en pausas de quórum.

La solución fue vergonzosamente no relacionada con la base de datos: reemplazar la unidad y reequilibrar leaseholders.
Además cambiaron reglas de compra: no “SSDs misteriosos” y no mezclar clases de almacenamiento dentro de un clúster de consenso.

La lección que quedó: con SQL distribuido, un nodo lento no es solo “un poco más lento”. Se convierte en la personalidad del clúster.

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

Una plataforma de e-commerce ejecutaba MySQL con replicación async. El checkout era rápido, pero la dirección quiso “consistencia más fuerte” durante fallos.
Alguien propuso activar semi-sync replication para asegurar que al menos una réplica confirmara escrituras antes del commit.
Sonaba como una mejora de seguridad gratis. No lo fue.

Activaron semi-sync durante una ventana de mantenimiento y vieron solo un aumento modesto en la latencia media.
Una semana después, la primera perturbación real de red llegó: un switch top-of-rack empezó a perder paquetes intermitentemente.
El primario comenzó a esperar acks de réplica que llegaban tarde o no llegaban.

La latencia no solo aumentó. Se volvió espasmódica. El tráfico de checkout se acumuló, los thread pools se saturaron y la capa de aplicación comenzó a agotar tiempos.
El incidente parecía “la base de datos está lenta”. La causa raíz era “la red es poco fiable y la pusimos en la ruta de commit”.

La optimización que salió mal no fue semi-sync en sí; fue activarlo sin validar la calidad de la red este-oeste y sin tiempos de espera ajustados a la realidad.
Tras el incidente, mantuvieron semi-sync—pero solo después de colocar réplicas en un segmento de red mejor y definir comportamiento claro para cuando semi-sync degrade.

La lección: cualquier cosa que añada un acuse de recibo a la ruta de commit convierte los problemas de red en dolor visible por el usuario. Esto es cierto en MySQL y es el valor por defecto en CockroachDB.

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

Un equipo de servicios financieros ejecutaba CockroachDB en nodos pequeños pero decentes. No tenían presupuesto infinito, así que apostaron por disciplina:
tipos de instancia estables, discos dedicados y un calendario estricto de cambios para migraciones de esquema.

Su práctica menos glamurosa fue también la más valiosa: antes de cada release ejecutaban una “prueba de aceptación de latencia de cola” fija que medía p95 y p99 en un conjunto de transacciones críticas,
mientras simultáneamente corrían un job de I/O tipo compaction en segundo plano para simular condiciones ruidosas.

Un día, la prueba falló. La latencia media se veía bien; p99 se duplicó. ¿El cambio? Un índice nuevo en una tabla de alta escritura más un backfill que aumentó la amplificación de escritura.
Los desarrolladores argumentaron que el código era correcto, y tenían razón. Pero la corrección en producción incluye física.

Porque tenían la prueba, lo detectaron antes que los clientes. Reprogramaron el backfill a horas valle, ajustaron restricciones para mantener leaseholders cerca de la capa API,
y añadieron throttling al job de migración.

El día se salvó gracias a una rutina aburrida y a la negativa de tratar p99 como “la métrica de otro”.

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

Estos son los patrones que aparecen en canales de incidentes. Los síntomas son repetitivos; las causas raíz suelen ser aburridas.
Las soluciones son específicas, no inspiradoras.

CockroachDB: p99 picos de latencia de escritura cada pocos minutos

  • Síntomas: breves acantilados de latencia, reintentos de transacción, nodos “sanos” a primera vista.
  • Causa raíz: compacciones o flushes de almacenamiento compitiendo con escrituras de primer plano; o un nodo con cola larga de fsync.
  • Solución: valida cola de fsync con fio, reduce la saturación de disco, usa mejor almacenamiento y asegura homogeneidad de nodos. Considera separar cargas y dimensionar para margen de compacción.

CockroachDB: lecturas inesperadamente lentas en un clúster “de una sola región”

  • Síntomas: latencia de lectura más alta de lo esperado, incluso para SELECTs simples.
  • Causa raíz: leaseholders no ubicados cerca de la pasarela SQL/capa de la aplicación; consultas rebotando entre nodos.
  • Solución: inspecciona distribución de leaseholders, configura localidad/zone constraints y enruta conexiones SQL apropiadamente.

CockroachDB: la aplicación ve fallos de serialización esporádicos

  • Síntomas: errores reintentables, mayor tiempo de solicitud end-to-end, fallos “aleatorios” bajo carga.
  • Causa raíz: contención en filas/rangos calientes; transacciones largas; actualizaciones de tipo contador.
  • Solución: rediseña puntos calientes (contadores shardedeados, batching), acorta transacciones, añade índices apropiados y asegura que la lógica de reintentos del cliente sea correcta y acotada.

MySQL: réplicas se retrasan en picos y nunca se ponen al día

  • Síntomas: aumento de Seconds_Behind_Source, lecturas retrasadas, failovers riesgosos.
  • Causa raíz: consultas lentas en réplica, insuficiente paralelismo de apply, DDL pesado o saturación de I/O.
  • Solución: optimiza consultas calientes, activa/ajusta replicación paralela, aísla réplicas para lecturas y programa DDL cuidadosamente.

MySQL: aumento repentino de latencia tras “hacerlo más seguro”

  • Síntomas: commits más lentos, timeouts en la app, CPU normal.
  • Causa raíz: activación de semi-sync o ajustes de durabilidad más estrictos sin verificar latencia de cola en red/disco.
  • Solución: mide RTT y retransmisiones, fija timeouts sensatos, coloca réplicas mejor y decide explícitamente qué semántica de fallo quieres.

Ambos: la latencia media está bien, los usuarios se quejan

  • Síntomas: los paneles muestran “verde”, los tickets de soporte dicen “a veces lento”.
  • Causa raíz: latencia de cola por contención, GC/compaction, trabajos en segundo plano o jitter de red.
  • Solución: instrumenta y alerta sobre p95/p99, correlaciona con métricas de disco/red y haz de la latencia de cola parte de los criterios de lanzamiento.

Listas de verificación / plan paso a paso

Checklist A: elegir MySQL vs CockroachDB para servidores pequeños

  1. Si necesitas p99 de un solo dígito ms para OLTP en una ubicación: empieza con MySQL en un primario potente. Añade réplicas para lecturas y backups. No te disculpes.
  2. Si necesitas failover automático con mínimo esfuerzo operativo y puedes tolerar una prima de latencia: CockroachDB es una opción razonable, pero dimensiona discos y red como si importara (porque importa).
  3. Si necesitas multi-writer sin shardear la app: CockroachDB encaja. MySQL te obligará a shardear o a patrones de coordinación cuidadosos.
  4. Si tus nodos son “muy pequeños” y el almacenamiento es mediocre: no elijas SQL distribuido por rendimiento. Elígelo por disponibilidad y comportamiento operacional, y acepta el impuesto.
  5. Si el negocio exige “sin pérdida de datos”: asegúrate de no depender de semánticas async en MySQL, y asegúrate de que tu clúster CockroachDB pueda mantener quórum bajo fallo.

Checklist B: hacer que CockroachDB se comporte en nodos pequeños

  1. Mantén nodos lo más idénticos posible (CPU, RAM, tipo de disco, ajustes de kernel).
  2. Mide y controla la cola de fsync; no adivines.
  3. Vigila reintentos de transacción y contención; arregla puntos calientes en esquema y patrones de acceso.
  4. Chequea la localidad de leaseholders para rangos calientes; no asumas que la base de datos “conoce” la topología de tu app.
  5. Programa cambios de esquema y backfills; limítalos; trátalos como eventos de producción.
  6. Deja margen para compactions y reequilibrios. “80% disco ocupado” no es margen.

Checklist C: hacer MySQL seguro sin destruir la latencia

  1. Mantén innodb_flush_log_at_trx_commit=1 a menos que puedas explicar el riesgo en una frase a un no-técnico.
  2. Define qué consistencia necesitas para lecturas; enruta en consecuencia (primario vs réplicas).
  3. Ensaya el failover. Un runbook que nunca ejecutaste es ficción.
  4. Monitoriza lag de replicación y pon umbrales que desencadenen acción operativa, no solo paneles.
  5. Mantén backups y restores aburridos y probados; la replicación no es un backup.
  6. Haz cambios de esquema online con herramientas probadas y un plan de rollback.

Preguntas frecuentes

1) ¿CockroachDB es “más lento” que MySQL?

En servidores pequeños para OLTP de baja latencia, a menudo sí—especialmente en p95/p99—porque la replicación por quórum y las compactions LSM añaden trabajo inevitable.
En cargas que necesitan escalar escrituras o failover automático, “más lento por transacción” puede seguir siendo “más rápido para el negocio” porque las interrupciones cuestan más que milisegundos.

2) ¿Por qué la latencia de CockroachDB se ve bien en promedio pero mal en p99?

Porque la latencia de cola es donde aparece la coordinación y el trabajo de almacenamiento en segundo plano: fsync lento ocasional, ráfagas de compaction, hiccups de réplicas, jitter de red y reintentos.
Las medianas los ocultan; los usuarios no.

3) ¿Puedo hacer CockroachDB tan rápido como un primario MySQL de un solo nodo?

Puedes reducir la brecha con buen hardware, configuración consciente de localidad y arreglos de contención. No puedes eliminar el quórum y seguir reclamando las mismas semánticas de fallo.
Si necesitas latencia de un nodo, ejecuta una base de datos de nodo único para ese camino.

4) ¿Es suficiente un clúster CockroachDB de tres nodos?

Es el mínimo para HA basada en quórum y funciona. También es frágil en el sentido de que cualquier mantenimiento o inestabilidad de nodo consume tu margen de seguridad inmediatamente.
Si puedes permitírtelo, cinco nodos compran respiro operacional y a menudo mejor comportamiento de cola.

5) ¿Cuál es el equivalente en MySQL a la consistencia fuerte de CockroachDB?

MySQL puede aproximar durabilidad/consistencia más fuerte usando semi-sync o group replication, pero cambias la ruta de commit y sufres una pérdida de latencia.
La diferencia clave es que CockroachDB está diseñado alrededor de ese modelo; MySQL puede hacerlo, pero es más fácil configurarlo mal y más difícil razonar sobre fallos.

6) ¿Cuál es más fácil de operar con un equipo pequeño?

Si tu carga cabe en un primario, MySQL suele ser más simple en el día a día.
Si necesitas failover automático y no puedes permitirte operaciones HA manuales, CockroachDB puede reducir el trabajo—siempre que inviertas en medir disco/red y entender la contención.

7) ¿CockroachDB requiere NVMe rápido?

No lo requiere para funcionar. Lo requiere para sentirse bien bajo carga, especialmente en nodos pequeños.
El almacenamiento lento o con jitter aparece como latencia de commit por quórum y estancamientos de compaction.

8) ¿Puedo usar CockroachDB en redes de 1Gbps?

Sí, pero debes vigilar retransmisiones y congestión este-oeste, y mantener los nodos cerca (bajo RTT, bajo jitter).
Si tu red es compartida y con ráfagas, tu p99 reflejará esa realidad.

9) ¿Por qué los clústeres pequeños parecen “bien” hasta que el tráfico crece un poco?

Porque lo primero que agotas es el margen, no la CPU. El trabajo en segundo plano y los efectos de cola suben de forma no lineal.
A pequeña escala, el sistema tiene tiempo ocioso para ocultar pecados; a escala moderada, empieza a cobrar intereses.

10) Si ya estoy en MySQL, ¿cuándo debería moverme a CockroachDB?

Muévete cuando tu dolor sea principalmente failover, crecimiento multi-writer, fragilidad operacional o la complejidad de shardear y asegurar correctness.
No te muevas porque la base de datos está de moda. Si tu principal dolor es latencia de consultas, un sistema distribuido es una forma cara de comprar más latencia.

Conclusión: qué hacer la próxima semana

Si estás en servidores pequeños y debatiendo MySQL vs CockroachDB, trátalo como una decisión de ingeniería, no como una decisión de marca.
MySQL es la referencia de latencia. CockroachDB es la referencia de disponibilidad. Puedes afinar ambos, pero no puedes negociar sus valores por defecto.

Pasos prácticos que realmente cambian resultados:

  1. Mide RTT y cola de fsync en tus nodos reales. Si alguno es feo, arregla eso antes de cambiar de base de datos.
  2. Elige tu modelo de verdad: replicación asincrónica (rápida, más arriesgada) vs quórum/consistencia fuerte (más segura, más lenta). Escríbelo.
  3. Ejecuta una prueba de carga enfocada en p99 con I/O en segundo plano corriendo. Si no pruebas colas, las descubrirás en producción.
  4. Si eliges CockroachDB en nodos pequeños: invierte en hardware homogéneo, conciencia de localidad/leaseholder y diagnóstico de contención desde el día uno.
  5. Si eliges MySQL: invierte en failover disciplinado, monitorización de lag de replicación y backups probados. No dejes que “simple” se vuelva “descuidado”.

El impuesto de latencia no es una tarifa oculta. Está en el recibo. Solo tienes que mirar p99 para verlo.

← Anterior
GeForce FX “Leaf Blower”: la GPU famosa por su ruido
Siguiente →
Anchura del bus de memoria (128/256/384 bits): cuándo importa realmente

Deja un comentario