La propuesta suele ocurrir en una reunión donde nadie ha recibido una paginación a las 3 a.m. recientemente. «Simplemente usa NoSQL», dice alguien, como si elegir una base de datos fuera como elegir una tipografía. Unos meses después, estás mirando pedidos duplicados, entradas de contabilidad que faltan y un trabajo de conciliación «temporal» que se ha convertido en tu sistema más fiable.
El problema no es que NoSQL sea malo. El problema es que «simplemente usa NoSQL» es una respuesta perezosa a una pregunta difícil: ¿cómo almacenas la verdad bajo restricciones reales de producción—latencia, coste, concurrencia, fallos, error humano y un negocio que cambia de opinión cada dos sprints?
Por qué «simplemente usa NoSQL» quema a los equipos
«Simplemente usa NoSQL» suele ser un eufemismo de una de estas creencias:
- SQL no escala (traducción: una vez tuvimos una consulta lenta y culpamos a la base de datos).
- Los esquemas ralentizan a los equipos (traducción: no queremos discutir el modelo de datos hoy).
- Necesitamos flexibilidad (traducción: los requisitos del producto son inestables, así que codificaremos esa inestabilidad en el almacenamiento).
- Los joins son caros (traducción: una vez vimos un join en un plan de consulta lento).
- Queremos «escala web» (traducción: queremos sentirnos seguros sin hacer planificación de capacidad).
Esas creencias no siempre están equivocadas. Pero el consejo es incompleto—peligrosamente incompleto—porque omite la parte en la que defines las invariantes y decides dónde aplicarlas.
Las invariantes son el producto
Si almacenas dinero, inventario, permisos, derechos de facturación, recuentos de asientos, o «solo una sesión activa por usuario», estás almacenando invariantes. Eso no es un detalle de implementación; es el negocio. La base de datos es donde las invariantes viven en paz o mueren ruidosamente.
Las bases de datos relacionales no son mágicas porque hablen SQL. Son poderosas porque ofrecen un conjunto maduro e integrado de herramientas para hacer cumplir la verdad:
- Transacciones con niveles de aislamiento bien comprendidos
- Restricciones (claves foráneas, restricciones de unicidad, check constraints)
- Índices declarativos y planificación de consultas
- Registro de escritura duradero (write-ahead logging) y recuperación predecible
- Observabilidad operacional afinada por décadas de dolor
Los sistemas NoSQL pueden ser absolutamente correctos, fiables y rápidos. Pero muchos de ellos elevan la corrección hacia la capa de aplicación: a procesos en segundo plano, a transacciones compensadoras, a «lo limpiamos después». Ese «después» se convierte en un impuesto permanente, pagado en agotamiento de on-call.
Una regla práctica: si los datos deben ser correctos hoy, haz cumplir la corrección donde se escriben. Si puedes tolerar ser «eventualmente correcto», aún necesitas definir qué significa «eventualmente» y cómo detectas cuando no ocurrió.
Broma corta #1: La consistencia eventual es como una disculpa en un chat grupal: llega después, y no siempre arregla lo que se rompió.
NoSQL no es una sola cosa
Cuando alguien dice «NoSQL», pregunta a cuál categoría se refiere, porque el perfil operativo y la historia de corrección difieren:
- Almacenes de documentos (p.ej., tipo Mongo): excelentes para documentos anidados, pueden ser una trampa para invariantes entre documentos.
- Clave-valor (p.ej., tipo Redis): fantásticos para caché y estado efímero, malos como fuente de verdad a menos que estén cuidadosamente restringidos.
- Wide-column (p.ej., tipo Cassandra): excelentes para alto rendimiento de escritura con patrones de acceso conocidos; particiones calientes arruinarán tu fin de semana.
- Índices de búsqueda (p.ej., tipo Elasticsearch): no son una base de datos; son un índice con opiniones.
- Bases de datos gráficas: nicho pero útiles cuando las relaciones son la consulta.
«Simplemente usa NoSQL» ignora la parte en la que emparejas la forma de los datos, los patrones de acceso y la tolerancia a fallos con las garantías reales del sistema.
Ocho hechos rápidos y un poco de historia
Un poco de contexto ayuda, porque la industria sigue reaprendiendo las mismas lecciones con diferente marca.
- Las bases de datos relacionales obtuvieron su teoría en 1970 cuando E. F. Codd publicó el modelo relacional. Eso no fue «SQL»; fue un modelo de corrección.
- La estandarización de SQL comenzó en los años 80. Las partes aburridas—transacciones, restricciones, optimización de consultas—son donde vive la mayor parte del valor.
- «NoSQL» como término se popularizó alrededor de 2009, en gran parte como bandera para sistemas diseñados para abordar problemas de escala y distribución de la época.
- El teorema CAP se formalizó a principios de los 2000. La gente todavía lo malinterpreta, sobre todo para justificar comportamientos rotos como un «tradeoff».
- El artículo Dynamo de Amazon (mediados de los 2000) influyó en una generación de diseños de clave-valor y wide-column centrados en disponibilidad y tolerancia a particiones.
- El artículo Bigtable de Google (mediados de los 2000) moldeó las wide-column y motores de almacenamiento con LSM-tree optimizados para rendimiento de escritura.
- El commit de dos fases (two-phase commit) existe desde antes de la mayoría de las plataformas en la nube. No es nuevo; es caro en entornos distribuidos y doloroso de operar.
- «NewSQL» no fue un reemplazo mágico; fue un intento de llevar semánticas SQL a sistemas distribuidos, a veces con éxito, a menudo con nuevas restricciones operativas.
La historia no elige tu base de datos. Pero sí explica por qué existen ciertos tradeoffs y por qué tu «esquema flexible» sigue convirtiéndose en una serie de backfills a medida.
La alternativa real: diseñar desde las invariantes y luego elegir la tecnología
La alternativa real a «simplemente usa NoSQL» no es «simplemente usa PostgreSQL». Es un proceso de decisión:
- Escribe las invariantes. No características. Invariantes. «Un total de factura debe ser igual a la suma de las líneas.» «Un usuario no puede tener dos suscripciones activas.» «El inventario no puede ser negativo.»
- Define el contrato de consistencia. Para cada invariante, responde: ¿debe ser verdadera en el momento de la escritura o puede reconciliarse después?
- Modelo los patrones de acceso. Rutas de lectura, rutas de escritura, cardinalidad, picos, fan-outs.
- Elige dónde vive la verdad. Un sistema es el sistema de registro. Todo lo demás es derivado, cacheado, indexado o desnormalizado.
- Elige el conjunto mínimo de bases de datos. Cada datastore adicional es un modo de fallo adicional, un runbook de on-call, una historia de backups y un plan de migración de datos.
«Usar una base de datos» no es dogma; es un modelo de costes
A los equipos les encanta la persistencia poliglota hasta que tienen que restaurarla. «Tenemos Postgres para transacciones, Redis para caché, Elasticsearch para búsqueda, Kafka para eventos y un almacén de documentos para cosas flexibles.» Eso puede ser correcto. También puede ser una pesadilla de fiabilidad.
Un valor por defecto práctico para muchas empresas:
- PostgreSQL (u otra BD relacional) como sistema de registro.
- Redis para caché y limitación de tasa, no para estado canónico a menos que aceptes los riesgos.
- Un índice de búsqueda para búsqueda de texto, alimentado desde el sistema de registro.
- Un bus de logs/eventos si necesitas integración asíncrona y capacidad de replay.
Comienza con la arquitectura más simple que pueda ser correcta. Escálala con técnicas aburridas—índices, afinamiento de consultas, particionado, réplicas de lectura—antes de pasar a «necesitamos una base de datos distribuida». Los criterios para graduarse deberían ser carga que puedas medir, no miedo que puedas imaginar.
Una cita, porque sigue siendo cierta
Idea parafraseada (Werner Vogels): «Lo construyes, lo operas» significa que los equipos son responsables de los resultados operativos, no solo de fusiones de código.
Las decisiones sobre bases de datos son resultados operativos. Si tu base de datos es «problema de otra persona», tu cola de incidentes te enseñará lo contrario.
Modos de fallo de NoSQL que realmente encontrarás
1) «Esquema flexible» se convierte en «corrupción silenciosa»
Los almacenes de documentos facilitan escribir datos con campos faltantes, tipos erróneos o formas sutilmente inconsistentes. Sin restricciones, tu aplicación se convierte en la que impone el esquema. Y las aplicaciones cambian. Los ingenieros rotan. Las migraciones se posponen.
Patrón de fallo: un servicio escribe price_cents como número, otro lo escribe como cadena, analytics lo convierte «útilmente» y ahora los ingresos son un error de redondeo en la revisión trimestral.
2) Invariantes entre entidades pasan a trabajos en segundo plano
¿Necesitas «solo una suscripción activa por cuenta»? En una BD relacional: restricción única más transacción. En muchos entornos NoSQL: comprobación en la app, carrera bajo concurrencia, arreglar con un job periódico de «dedupe» y añadir una herramienta manual para el equipo de soporte.
3) Particiones calientes y claves desiguales
Los sistemas NoSQL distribuidos adoran la distribución uniforme de claves. Los negocios reales adoran IDs secuenciales, inquilinos con tráfico enorme y prefijos con la «fecha de hoy». Puedes construir un clúster wide-column que maneje millones de escrituras… hasta que una clave de partición se convierta en un agujero negro.
4) Los índices secundarios no son gratuitos (y a veces no son reales)
Algunos sistemas tratan los índices secundarios como opcionales, eventualmente consistentes o costosos de mantener. Tu consulta funciona bien en staging y luego se desmorona bajo la cardinalidad de producción.
5) La complejidad operativa se convierte en el producto
Replicación, compactación, reparación, hinted handoff, lecturas por quórum, tombstones—esto no son «características avanzadas». Son el coste de entrada. Si tu equipo no tiene apetito por ese trabajo, no compres ese sistema.
6) Backup/restore y recuperación punto-en-tiempo se vuelven extraños
Una base de datos relacional con archivado de WAL te da una historia directa: backup completo + replay de WAL. En algunos sistemas NoSQL, los backups son «tomar snapshots por nodo y esperar que coincidan». Puedes hacerlo, pero necesitas practicarlo. Si no lo haces, el primer intento de restauración será durante un incidente. Eso no es un simulacro; es un momento de carrera profesional.
Cuándo NoSQL es la opción correcta (y cuándo no lo es)
Usa NoSQL cuando se cumplan estas condiciones
- Tus patrones de acceso son conocidos y estables. Puedes describir tus consultas por adelantado y no mutarán semanalmente.
- Necesitas un rendimiento masivo de escritura a través de particiones y puedes diseñar claves para evitar hotspots.
- Puedes tolerar semánticas transaccionales más débiles para el flujo principal, o tienes un diseño compensador que has probado.
- Estás construyendo vistas derivadas: cachés, modelos de lectura materializados, índices de búsqueda, capas de ingestión de series temporales.
- Tu equipo puede operarlo. No «alguien puede buscarlo en Google». Alguien lo tendrá bajo su responsabilidad.
Evita NoSQL como sistema de registro cuando se cumplan estas condiciones
- Necesitas invariantes multi-entidad en el momento de la escritura. Facturación, contabilidad, permisos, inventario, derechos.
- No conoces aún los patrones de acceso. «Esquema flexible» no te salvará de consultas desconocidas; solo demora la discusión.
- No puedes permitir trabajo de limpieza manual. La reconciliación en segundo plano es un plan de plantilla oculto.
- Necesitas auditoría sencilla. Quieres restricciones, logs y una historia clara de «quién cambió qué».
Broma corta #2: «Sin esquema» normalmente significa «esquema escrito en Slack y perdido en un canal que nadie revisa».
Tres mini-historias corporativas desde las trincheras
Mini-historia #1: El incidente causado por una suposición errónea (consistencia eventual por defecto)
Una compañía SaaS mediana reconstruyó su flujo de facturación para «moverse más rápido». El antiguo monolito usaba una base de datos relacional con transacciones. La nueva arquitectura separó «suscripciones», «facturas» y «pagos» en servicios separados, cada uno con su propio almacén de documentos. Los servicios se comunicaban por un bus de eventos.
La suposición errónea fue sutil: pensaron que «consistencia eventual» significaba «unos segundos». En realidad, significaba «cuando los consumidores estén sanos, al día y no re-procesando». Durante un despliegue, el servicio de facturas se retrasó respecto a los eventos de pago el tiempo suficiente como para que la interfaz mostrara «pago recibido» pero «factura impaga». Los tickets de soporte se dispararon, luego finanzas escaló porque el cobro automático se activó en cuentas pagadas.
On-call intentó parchearlo con reintentos y escalado de consumidores. Empeoró. Los replays reordenaron eventos. Algunos eventos se procesaron dos veces. Un pequeño número se perdió por un bug en las claves de idempotencia. Nadie tenía un único lugar para consultar «¿cuál es la verdad para la cuenta X ahora mismo?».
La solución no fue heroica. Hicieron que un servicio—el libro mayor de facturación—fuera autoritativo en una base de datos relacional. Otros servicios se convirtieron en proyecciones. Añadieron aplicación de idempotencia en la ruta de escritura del libro mayor y la UI leyó del libro mayor para el estado visible al cliente. El bus de eventos siguió existiendo, pero su papel fue integración, no verdad.
Mini-historia #2: La optimización que salió mal (desnormalización para evitar joins)
Una plataforma de comercio electrónico tenía un endpoint «detalles del pedido» lento. Alguien lo perfiló, vio múltiples joins y tomó una decisión segura: «Desnormalicemos. Los joins no escalan.» Movieron las líneas de pedido y el estado de envío a un solo documento por pedido en un almacén de documentos. Las lecturas se hicieron más rápidas inmediatamente. Todos celebraron.
Luego llegaron las contrapartidas según lo previsto. El procesamiento de devoluciones necesitaba actualizar líneas individuales preservando un historial de auditoría. Soporte necesitaba actualizaciones parciales sin sobrescribir cambios concurrentes. Un flujo antifraude añadió etiquetas y notas. Tres equipos empezaron a escribir en el mismo documento, desde servicios distintos, con diferentes suposiciones sobre concurrencia.
Los conflictos se convirtieron en pérdidas por «última escritura gana». Los ingenieros añadieron lógica de fusión por campo. Luego añadieron un campo de «versión del documento» y reintentaron actualizaciones. Luego añadieron un job de «reparación» en segundo plano para reconciliar campos faltantes. El documento de pedido se volvió territorio disputado. Los incidentes se volvieron políticos: «tu servicio sobrescribió nuestros campos».
El diseño estable eventual reintrodujo la normalización donde importaba la concurrencia. El encabezado del pedido quedó en una fila, las líneas en otra tabla y las anotaciones mutables en su propio almacén con propiedad explícita. La lectura rápida vino de vistas materializadas y caché—no de convertir un problema de corrección en un blob JSON.
Mini-historia #3: La práctica aburrida pero correcta que salvó el día (restauraciones probadas y PITR)
Una plataforma B2B ejecutaba Postgres como su almacén principal. Nada llamativo. Pero un SRE insistió en dos disciplinas aburridas: archivado de WAL para recuperación punto-en-tiempo y un simulacro mensual de restauración. No «tenemos backups», sino «restauramos en un clúster nuevo y ejecutamos chequeos de la aplicación».
Una tarde, un ingeniero ejecutó una migración que eliminó una columna y la recreó con el mismo nombre pero distinto tipo. La migración pasó en staging. En producción, la app empezó a escribir datos sin sentido en la nueva columna. El bug se detectó rápido, pero los datos ya estaban mal de una forma que no podía «arreglarse hacia adelante» sin un punto de referencia fiable.
Realizaron una recuperación punto-en-tiempo hasta minutos antes de la migración, extrajeron las tablas afectadas y reprodujeron las escrituras legítimas usando logs de la aplicación y runners idempotentes. El outage fue doloroso, pero acotado. No hubo un «perdimos un día de datos» en el correo. Finanzas no se involucró. Nadie tuvo que explicar «creemos que está correcto».
Ese incidente no dio para charla en una conferencia. Sí evitó un cuarto de caos en cámara lenta. Lo aburrido gana porque es repetible.
Guion de diagnóstico rápido: qué comprobar primero/segundo/tercero
Esta es la secuencia de on-call que encuentra el cuello de botella sin pasar una hora discutiendo con dashboards.
Primero: decide si es saturación, contención o corrección
- Saturación: CPU, IO, memoria o red al máximo. La latencia sube con la carga.
- Contención: locks, claves calientes, concurrencia limitada. El rendimiento se estanca; las colas crecen.
- Corrección: lag de replicación, lecturas inconsistentes, índices faltantes que causan timeouts que parecen outages.
Segundo: localiza el punto de estrangulamiento (app vs base de datos vs almacenamiento)
- Comprueba la latencia p95 de la app y la tasa de errores alrededor de las llamadas a la BD.
- Comprueba la salud de la BD: conexiones, esperas de locks, consultas lentas.
- Comprueba el almacenamiento: iowait, latencia de disco, saturación, errores de sistema de ficheros.
Tercero: elige la mitigación más rápida y segura
- Reduce la carga: limita tasas, rechaza tráfico no crítico, desactiva endpoints costosos.
- Mejora el plan de consulta: añade/ajusta índices, corrige patrones N+1, limita consultas sin tope.
- Arregla la contención: evita filas/particiones calientes, acorta transacciones, ajusta aislamiento, añade colas.
- Escala con seguridad: añade réplicas de lectura, añade caché, aumenta el tamaño de la instancia solo si entiendes el límite.
Tareas prácticas: comandos, salidas y la decisión que tomar
Estas son tareas reales que puedes ejecutar hoy. Cada una incluye un comando, qué te dice la salida y qué decisión tomar. Asume servidores Linux y una mezcla de PostgreSQL y componentes NoSQL comunes. Ajusta hostnames y credenciales según corresponda.
Tarea 1: Comprobar si la máquina está limitada por IO (Linux iostat)
cr0x@server:~$ iostat -x 1 3
Linux 6.1.0 (db01) 02/04/2026 _x86_64_ (16 CPU)
avg-cpu: %user %nice %system %iowait %steal %idle
12.31 0.00 4.22 31.88 0.00 51.59
Device r/s w/s rkB/s wkB/s await svctm %util
nvme0n1 85.0 220.0 5200.0 14800.0 18.2 0.9 92.4
Qué significa: Alto %iowait y alta %util de disco sugieren saturación de almacenamiento. await indica latencia por encolamiento.
Decisión: No empieces por «optimizar código». Reduce la amplificación de escrituras (índices, autovacuum, compactación), mueve WAL a disco más rápido o escala almacenamiento/IOPS.
Tarea 2: Ver quién consume memoria y si hay swapping
cr0x@server:~$ free -h
total used free shared buff/cache available
Mem: 64Gi 52Gi 1.2Gi 1.1Gi 11Gi 8.5Gi
Swap: 8Gi 2.9Gi 5.1Gi
Qué significa: Uso de swap en un host de BD suele correlacionar con picos de latencia aleatorios.
Decisión: Reduce la presión de memoria (ajusta shared buffers / tamaños de caché, corrige fugas, dimensiona correctamente). Si sigue habiendo swapping, tu «problema de BD» es un problema de kernel.
Tarea 3: Confirmar espacio en sistema de ficheros e inodos
cr0x@server:~$ df -h /var/lib/postgresql
Filesystem Size Used Avail Use% Mounted on
/dev/nvme0n1p2 900G 812G 42G 96% /var/lib/postgresql
Qué significa: 96% lleno es un outage en cámara lenta (vacuum, compactación y archivos temporales necesitan espacio libre).
Decisión: Amplía almacenamiento o purga de forma segura. Añade alertas en 80/85/90%. No esperes al 100%.
Tarea 4: Comprobar latencia de disco directamente
cr0x@server:~$ nvme smart-log /dev/nvme0n1 | sed -n '1,12p'
Smart Log for NVME device:nvme0n1 namespace-id:ffffffff
critical_warning : 0x00
temperature : 41 C
available_spare : 100%
available_spare_threshold : 10%
percentage_used : 4%
data_units_read : 1,210,331
data_units_written : 2,980,552
host_read_commands : 18,220,114
host_write_commands : 55,991,202
Qué significa: No es una métrica de latencia, pero te indica si el dispositivo está sano. Las advertencias críticas importan.
Decisión: Si aparecen advertencias/errores SMART, deja de debatir planes de consulta y empieza a planificar reemplazo de disco o migración.
Tarea 5: Encontrar las consultas más lentas de PostgreSQL por tiempo total
cr0x@server:~$ psql -d appdb -c "SELECT query, calls, total_time, mean_time FROM pg_stat_statements ORDER BY total_time DESC LIMIT 5;"
query | calls | total_time | mean_time
----------------------------------------------------------------------+-------+------------+----------
SELECT * FROM orders WHERE account_id = $1 ORDER BY created_at DESC... | 9842 | 812345.12 | 82.54
UPDATE inventory SET available = available - $1 WHERE sku = $2 | 62111 | 420998.77 | 6.78
SELECT * FROM events WHERE tenant_id = $1 AND created_at > $2 | 2109 | 318120.33 | 150.86
Qué significa: total_time encuentra «muerte por mil cortes». mean_time encuentra cuchillos afilados.
Decisión: Arregla primero alto total_time si domina la carga; arregla alto mean_time si domina la latencia tail.
Tarea 6: EXPLAIN ANALYZE de una consulta lenta
cr0x@server:~$ psql -d appdb -c "EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM orders WHERE account_id = 42 ORDER BY created_at DESC LIMIT 50;"
Limit (cost=0.56..220.14 rows=50 width=312) (actual time=120.331..120.410 rows=50 loops=1)
Buffers: shared hit=120 read=845
-> Index Scan Backward using orders_created_at_idx on orders (cost=0.56..44120.12 rows=10024 width=312) (actual time=120.329..120.401 rows=50 loops=1)
Filter: (account_id = 42)
Rows Removed by Filter: 580000
Buffers: shared hit=120 read=845
Planning Time: 0.219 ms
Execution Time: 120.450 ms
Qué significa: El índice está en created_at, pero filtras por account_id. Está escaneando mucho y luego filtrando.
Decisión: Añade un índice compuesto como (account_id, created_at DESC) o reestructura la consulta. Esto es «SQL no escala» solo si te niegas a indexar correctamente.
Tarea 7: Comprobar contención de locks en PostgreSQL
cr0x@server:~$ psql -d appdb -c "SELECT blocked.pid AS blocked_pid, blocking.pid AS blocking_pid, blocked.query AS blocked_query FROM pg_stat_activity blocked JOIN pg_locks bl ON bl.pid = blocked.pid JOIN pg_locks kl ON kl.locktype = bl.locktype AND kl.database IS NOT DISTINCT FROM bl.database AND kl.relation IS NOT DISTINCT FROM bl.relation AND kl.page IS NOT DISTINCT FROM bl.page AND kl.tuple IS NOT DISTINCT FROM bl.tuple AND kl.virtualxid IS NOT DISTINCT FROM bl.virtualxid AND kl.transactionid IS NOT DISTINCT FROM bl.transactionid AND kl.classid IS NOT DISTINCT FROM bl.classid AND kl.objid IS NOT DISTINCT FROM bl.objid AND kl.objsubid IS NOT DISTINCT FROM bl.objsubid AND kl.pid != bl.pid JOIN pg_stat_activity blocking ON blocking.pid = kl.pid WHERE NOT bl.granted;"
blocked_pid | blocking_pid | blocked_query
------------+--------------+--------------------------------
21934 | 21811 | UPDATE inventory SET available =
Qué significa: Una transacción está bloqueando a otras. La BD está bien; tu modelo de concurrencia no.
Decisión: Acorta transacciones, añade índices adecuados para evitar escalado de locks o re-diseña el patrón de fila caliente (los contadores por SKU son notoriamente problemáticos).
Tarea 8: Comprobar lag de replicación (réplica streaming de Postgres)
cr0x@server:~$ psql -d appdb -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
------------------+---------+------------+-----------+-----------+------------
replica01 | streaming | async | 00:00:01 | 00:00:02 | 00:00:15
Qué significa: El replay lag indica que las lecturas desde la réplica pueden estar obsoletas por esa duración.
Decisión: Si tu app lee después de escribir desde réplicas, necesitas reglas de enrutamiento de lectura, afinidad de sesión, o replicación síncrona para flujos de trabajo específicos.
Tarea 9: Ver si autovacuum está al día (riesgo de bloat)
cr0x@server:~$ psql -d appdb -c "SELECT relname, n_dead_tup, last_autovacuum FROM pg_stat_user_tables ORDER BY n_dead_tup DESC LIMIT 5;"
relname | n_dead_tup | last_autovacuum
------------+------------+--------------------------
events | 81234567 | 2026-02-03 01:12:02+00
sessions | 21003444 | 2026-01-29 12:40:10+00
orders | 8120032 | 2026-02-04 00:02:11+00
Qué significa: Alto número de tuples muertas significa bloat en tablas, peor tasa de aciertos de caché y consultas más lentas.
Decisión: Ajusta autovacuum, cambia fillfactor o particiona tablas con mucho churn. El bloat no es «lentitud misteriosa»; es física.
Tarea 10: Comprobar presión de conexiones (Postgres)
cr0x@server:~$ psql -d appdb -c "SELECT count(*) AS connections, state FROM pg_stat_activity GROUP BY state ORDER BY connections DESC;"
connections | state
------------+---------------------
220 | active
180 | idle
40 | idle in transaction
Qué significa: Demasiadas conexiones activas pueden thrashear la CPU. «Idle in transaction» es un asesino silencioso (mantiene locks e impide vacuum).
Decisión: Añade un pooler (p.ej., pgbouncer), corrige manejo de transacciones y limita conexiones por servicio.
Tarea 11: Detectar patrón de clave caliente en la capa de aplicación (ejemplo de logs de nginx)
cr0x@server:~$ awk '{print $7}' /var/log/nginx/access.log | grep -E '^/api/orders\?account_id=' | sort | uniq -c | sort -nr | head
18421 /api/orders?account_id=42
9211 /api/orders?account_id=17
4420 /api/orders?account_id=5
Qué significa: Un pequeño número de claves domina el tráfico. Eso se corresponde con particiones calientes, filas calientes o stampedes de caché.
Decisión: Añade caché, límites de paginación o vistas precomputadas para cuentas/tenants calientes. Considera también throttles por tenant.
Tarea 12: Verificar lag de consumidores de Kafka (si usas eventos para «verdad eventual»)
cr0x@server:~$ kafka-consumer-groups.sh --bootstrap-server kafka01:9092 --describe --group invoice-service
GROUP TOPIC PARTITION CURRENT-OFFSET LOG-END-OFFSET LAG CONSUMER-ID
invoice-service billing 0 1288812 1299910 11098 consumer-1
invoice-service billing 1 1290011 1299988 9977 consumer-2
Qué significa: El lag significa que tus proyecciones están obsoletas. Si tu UI o flujos dependen de esas proyecciones, los usuarios verán estado inconsistente.
Decisión: Escala consumidores/arregla el procesamiento, o deja de usar proyecciones como sistema de registro para estado visible al usuario.
Tarea 13: Comprobar uso de índices en MongoDB (ejemplo con mongosh)
cr0x@server:~$ mongosh --quiet --eval 'db.orders.find({account_id: 42}).sort({created_at:-1}).limit(50).explain("executionStats").executionStats'
{
"nReturned" : 50,
"executionTimeMillis" : 187,
"totalKeysExamined" : 0,
"totalDocsExamined" : 580050
}
Qué significa: Examinar muchos documentos con cero keys examinadas sugiere que no hay un índice útil para esa forma de consulta.
Decisión: Añade un índice compuesto {account_id:1, created_at:-1} o reconsidera el modelo de datos. Tu clúster no está lento; tu consulta es cara.
Tarea 14: Comprobar memoria y política de expulsión en Redis (caché convirtiéndose en outage)
cr0x@server:~$ redis-cli INFO memory | egrep 'used_memory_human|maxmemory_human|mem_fragmentation_ratio'
used_memory_human:14.92G
maxmemory_human:16.00G
mem_fragmentation_ratio:1.62
Qué significa: Cerca de la memoria máxima con fragmentación significa que te acercas a expulsiones o comportamiento OOM según la configuración.
Decisión: Si Redis es caché, verifica que la política de expulsión esté establecida y sea segura. Si Redis almacena estado canónico, replantea tus elecciones y añade disciplina de persistencia/backup.
Errores comunes: síntomas → causa raíz → solución
1) Síntoma: registros «únicos» duplicados (usuarios, suscripciones, pedidos)
Causa raíz: la unicidad se hace en el código de la aplicación, no en el datastore; las carreras de concurrencia ganan.
Solución: aplica unicidad en el momento de la escritura con una restricción de base de datos o una escritura condicional atómica. Si no puedes, rediseña: escoge una clave canónica, usa claves de idempotencia e introduce un proceso de deduplicación con auditabilidad.
2) Síntoma: lecturas «aleatorias» obsoletas después de despliegues
Causa raíz: leer desde réplicas o proyecciones sin un contrato de frescura; lag de replicación o picos de lag de consumidores durante despliegues.
Solución: enrutamiento read-your-writes (pegar la sesión al primario), comprobaciones de estalideces acotadas o replicación síncrona para los flujos que la requieran.
3) Síntoma: picos de latencia tail bajo carga moderada
Causa raíz: saturación de almacenamiento (iowait), presión por compactación/vacuum o thrash de caché por bloat.
Solución: reduce amplificación de escritura, ajusta autovacuum/compactación, añade índices apropiados, particiona tablas con mucho churn y asegura que el disco esté dimensionado para la carga.
4) Síntoma: la base de datos está «lenta», pero la CPU es baja
Causa raíz: contención de locks o encolamiento en una clave/partición/fila caliente. O estás limitado por IO.
Solución: identifica bloqueos (vistas de locks), rediseña contadores calientes, shardéa por una clave mejor o agrupa actualizaciones. Para IO-bound, aborda disco y patrones de escritura.
5) Síntoma: cambios de esquema llevan semanas y dan miedo
Causa raíz: falta de disciplina de migraciones; reescrituras de tablas grandes; ausencia de feature flags; estrategia de backfill insuficiente.
Solución: adopta migraciones expand/contract, hace backfills en lotes controlados y mantén compatibilidad de código entre versiones hasta completar el despliegue.
6) Síntoma: «no podemos restaurar rápido»
Causa raíz: existen backups pero las restauraciones no están probadas; no hay RTO/RPO definidos; no hay runbook.
Solución: realiza simulacros de restauración, define RPO/RTO, implementa PITR donde sea posible y automatiza la provisión de un entorno de restauración.
7) Síntoma: resultados de búsqueda no coinciden con la fuente de verdad
Causa raíz: índice de búsqueda tratado como base de datos; la canalización de indexado tiene lag, caídas o reordenamientos.
Solución: trata la búsqueda como estado derivado; añade capacidad de reindexado; usa versionado e idempotencia; crea una comprobación de integridad comparando índice vs fuente.
8) Síntoma: el clúster NoSQL está «saludable» pero aumentan los errores de la app
Causa raíz: timeouts y reintentos amplifican la carga; clientes mal configurados; latencia p95 sube pero la media parece bien.
Solución: instrumenta latencia en el cliente, limita reintentos, añade circuit breakers y ajusta timeouts para que coincidan con el SLA y el comportamiento ante fallos.
Listas de verificación / plan paso a paso
Paso a paso: elegir el datastore sin mentirte
- Lista las invariantes (verdad en escritura vs verdad eventual).
- Define tolerancia a fallos: ¿qué pasa si las escrituras son parcialmente exitosas? ¿Cuál es el impacto visible al usuario?
- Define patrones de lectura: lista las 10 consultas/endpoints principales, no «buscaremos después».
- Estima el crecimiento: volumen de datos, QPS de escritura, QPS de lectura y distribución entre tenants/keys.
- Elige un sistema de registro. Todo lo demás es derivado.
- Escoge la BD más simple que satisfaga las invariantes. Si es relacional, acéptalo.
- Planifica migraciones y rollbacks. Si no puedes revertir con seguridad, no tienes plan.
- Operationaliza: monitorización, backups, simulacros de restauración, política de cambios de esquema, alertas de capacidad.
Lista de verificación: preparación para producción de cualquier base de datos
- RPO y RTO definidos, por escrito, acordados con el negocio
- Backups automatizados, cifrados y verificados
- Simulacro de restauración realizado (no «revisamos los logs»)
- Alertas de capacidad para CPU, memoria, disco e IOPS
- Visibilidad de consultas lentas con muestreo y retención
- Propiedad clara y ruta de escalado
- Timeouts y reintentos de cliente configurados intencionalmente
- Proceso de esquema/migración con expand/contract
- Plan de retención y archivado de datos
- Pruebas de carga que incluyan modos de fallo (pérdida de nodo, particiones de red, lag de consumidor)
Lista de verificación: si insistes en «consistencia eventual»
- Define «eventual»: ¿segundos? ¿minutos? ¿horas? ¿Qué es aceptable?
- Claves de idempotencia para cada consumidor que muta estado
- Estrategia de replay probada (incluyendo fuera de orden y eventos duplicados)
- Cola de mensajes muertos (dead-letter) con propiedad operacional
- Trabajo de reconciliación auditable, no una caja negra
- Un único lugar para consultar la verdad actual (sistema de registro)
FAQ
1) ¿Es «SQL vs NoSQL» siquiera la pregunta correcta?
No exactamente. La pregunta real es: ¿dónde haces cumplir las invariantes y qué modos de fallo puede operar tu equipo? SQL suele ser la respuesta más simple y correcta para sistemas de registro.
2) ¿Pero NoSQL no escala mejor?
Algunos sistemas NoSQL escalan muy bien el throughput de escritura y la distribución horizontal—cuando modelas claves y patrones de acceso correctamente. Los sistemas relacionales también escalan mucho más allá de lo que la mayoría de los equipos alcanza, especialmente con indexación adecuada, particionado y escalado de lectura.
3) ¿Los joins son inherentemente lentos?
No. Los joins malos son lentos. Los índices faltantes son lentos. Hacer join de datasets enormes sin filtros es lento. Un join bien indexado con predicados selectivos suele ser rápido y predecible.
4) ¿Cuál es el mayor coste oculto de las bases «sin esquema»?
La deriva operativa y organizacional: pierdes un punto central de enforcement. Cada servicio se convierte en validador de esquema, las migraciones son «mejor esfuerzo» y la limpieza de datos se vuelve un flujo de trabajo permanente.
5) ¿Puedo hacer transacciones en bases NoSQL?
A veces, sí—dentro de límites. La cuestión es si el modelo de transacción es maduro, qué cuesta y si es predecible operativamente bajo carga y particiones. Prueba modos de fallo, no demos.
6) ¿Cuál es la «alternativa real» si mi equipo quiere velocidad y flexibilidad?
Usa una BD relacional como libro mayor de la verdad y luego construye modelos derivados flexibles: columnas JSON donde proceda, vistas materializadas, cachés e índices de búsqueda. Obtienes agilidad sin abandonar la integridad.
7) ¿Cuándo el almacenamiento de documentos es el sistema de registro correcto?
Cuando los documentos son verdaderamente independientes y tus invariantes están mayormente dentro del límite del documento. Ejemplo: blobs de gestión de contenidos, preferencias de usuario o datos tipo sesión—siempre que diseñes para migraciones y validación de datos.
8) ¿Cómo evitamos particiones calientes en un almacén NoSQL distribuido?
Diseña claves de partición para distribución uniforme, evita prefijos monotónicos, y simula tráfico real. Vigila las claves principales y la skew. Si un tenant domina, construye aislamiento y throttling conscientes del tenant.
9) ¿Y si ya elegimos NoSQL y ahora lo lamentamos?
No reescribas todo en pánico. Identifica las invariantes que te están dañando, introduce un almacén autoritativo para esas (a menudo relacional) y migra flujos incrementalmente con escrituras duales solo si puedes verificar la corrección.
10) ¿Cuál es la primera «mejora» de base de datos que la mayoría de equipos debería hacer?
No es shardear. Usualmente: arreglar indexación, adoptar pooling de conexiones, implementar backup+PITR y construir un proceso disciplinado de migraciones. Sharding es un estilo de vida operacional, no un flag de feature.
Próximos pasos que puedes hacer esta semana
- Escribe tus 10 invariantes principales y marca cuáles deben ser verdaderas en el momento de la escritura.
- Elige el sistema de registro para cada invariante (idealmente uno). Declara todo lo demás como derivado.
- Ejecuta el guion de diagnóstico rápido sobre tu dolor actual: ¿es saturación, contención o corrección?
- Haz un simulacro de restauración a un entorno limpio. Cronométralo. Registra los pasos. Ese es tu RTO real.
- Elimina un trabajo de conciliación «temporal» moviendo la invariante a una restricción en escritura o a una operación atómica.
- Haz una consulta aburrida: añade el índice correcto, verifica con
EXPLAIN (ANALYZE)y fija el rendimiento con una prueba de regresión.
La tecnología de bases de datos no te salvará de la verdad poco clara. Pero un modelo de verdad claro te salvará de la mayoría de los debates sobre tecnología de bases de datos. Elige sistemas como eliges la respuesta ante incidentes: por lo que ocurre cuando fallan, no por lo que ocurre en una demo.