¿Cómo escalas lecturas y escrituras de forma independiente a nivel de infraestructura usando CQRS?
CQRS no es solo una réplica de lectura: permite que tu lado de lectura use un modelo de datos y un motor de almacenamiento completamente distintos, sincronizados mediante eventos y no mediante tablas.
APRENDE HACIENDOPon a prueba tu intuición3 pasos
¿Cómo escalas lecturas y escrituras de forma independiente a nivel de infraestructura usando CQRS?
Usa los ejemplos y la explicación de la lección para comprobar tu idea.
Reflexión personal. No se envía a ningún servicio de IA.#La idea central: dos modelos, no uno
CQRS —Command Query Responsibility Segregation, segregación de responsabilidad entre comandos y consultas— separa el modelo que maneja las escrituras (comandos: crear, actualizar, borrar) del modelo que maneja las lecturas (consultas), en lugar de usar un solo esquema para ambas cosas. Esta es una afirmación más fuerte que "agrega una réplica de lectura": una réplica de lectura es una copia física del mismo esquema exacto, todavía moldeada por las restricciones del lado de escritura. CQRS dice que el lado de lectura puede tener un modelo de datos completamente distinto, índices diferentes, e incluso un motor de almacenamiento diferente, mantenido en sincronía con el lado de escritura mediante eventos en lugar de mediante replicación física de las mismas tablas.
#Por qué compartir un esquema es un techo de escalabilidad
El lado de escritura quiere normalización —datos divididos en tablas pequeñas y no redundantes para evitar anomalías de actualización—, consistencia fuerte, y pocos índices, porque cada índice adicional ralentiza cada escritura. El lado de lectura quiere exactamente lo opuesto: datos desnormalizados y pre-unidos (joined), moldeados alrededor de patrones de consulta específicos, tantos índices (o estructuras de índice especializadas) como las consultas necesiten, y a menudo un motor de almacenamiento elegido para un patrón de acceso en particular en lugar de flexibilidad de propósito general — un índice de búsqueda para consultas de texto completo, una base de datos de grafos para recorridos intensivos, un almacén clave-valor simple para búsquedas puntuales, un almacén columnar para analítica.
Un único esquema compartido obliga a un compromiso entre estos objetivos: desnormalizar para velocidad de lectura perjudica la corrección de escritura y aumenta la complejidad de actualización, mientras que normalizar para corrección de escritura obliga a las lecturas a hacer joins costosos. CQRS resuelve la tensión negándose a comprometer — cada lado obtiene el modelo que realmente necesita.
#Arquitectura: almacén de escritura, propagación de eventos, almacenes de lectura
Un servicio de comandos acepta escrituras y las persiste en un almacén optimizado para escritura — típicamente una base de datos relacional normalizada que impone invariantes transaccionales. Al confirmarse, la escritura se captura como un evento de dominio (publicado explícitamente por el servicio de comandos) o se recoge mediante captura de datos de cambios (change data capture, una herramienta como Debezium que sigue el write-ahead log de la base de datos) y se coloca en un bus de eventos. Workers de proyección independientes consumen ese flujo y actualizan uno o más almacenes optimizados para lectura, cada uno moldeado para un patrón de consulta específico. Un servicio de consultas lee exclusivamente de esos almacenes de lectura — nunca toca la base de datos de escritura en absoluto.
#La ganancia real: escalado independiente y modelado independiente
Como los almacenes de lectura son sistemas físicamente separados, escalan de forma independiente de la capacidad de escritura — puedes agregar réplicas, particionar o redimensionar un almacén de lectura sin tocar en absoluto el throughput del camino de escritura ni sus garantías de aislamiento transaccional. El tráfico de lectura pesado (un pico viral, un trabajo de reportes escaneando millones de filas) nunca puede competir por los bloqueos, conexiones o I/O de la base de datos de escritura, porque estructuralmente es imposible que llegue a esa base de datos.
Igual de importante, cada almacén de lectura puede usar el modelo de datos y el motor de almacenamiento mejor adaptado a su patrón de acceso específico, en lugar de que cada patrón de consulta compita por índices y decisiones de esquema en una sola tabla compartida. Un catálogo de productos podría proyectarse hacia Elasticsearch para búsqueda, Redis para una caché de detalle de producto muy solicitada, y una tabla de reportes desnormalizada para analítica — tres modelos de lectura a partir de un modelo de escritura, cada uno escalable y optimizado de forma independiente.
#El costo: consistencia eventual y peso operativo
Una escritura es visible en el lado de lectura solo después de que su evento se propagó y se proyectó — hay un retraso real y medible, típicamente de milisegundos, pero ocasionalmente mayor bajo contrapresión. Esto crea el clásico problema de "lee tus propias escrituras": un usuario envía un formulario, el comando tiene éxito, y una lectura inmediata posterior (contra el almacén de lectura proyectado) puede seguir mostrando el valor antiguo. Las soluciones comunes son devolver la entidad fresca directamente en la respuesta del comando en lugar de volver a consultar, o dirigir la siguiente lectura de ese usuario en particular hacia el lado de escritura (o una réplica de baja latencia y sincronización estrecha) durante una breve ventana después de su propia escritura.
Reconstruir un almacén de lectura —tras un error en un proyector, un cambio de esquema, o para levantar un modelo de lectura completamente nuevo— significa reproducir el log de eventos desde el principio, lo cual solo funciona si cada proyector es idempotente y seguro de ejecutar múltiples veces sobre los mismos eventos. Y el sistema ahora tiene genuinamente más piezas móviles: un bus de eventos que operar, múltiples workers de proyección a los que vigilar por retraso, y evolución de esquema que gestionar a través de la cantidad de almacenes de lectura que existan, cada uno de forma independiente.
#Cuándo justifica su complejidad
CQRS se paga a sí mismo cuando la proporción lectura/escritura está muy desbalanceada, cuando los patrones de lectura son genuinamente heterogéneos (los mismos datos subyacentes necesitan soportar búsqueda de texto completo, búsquedas simples y analítica simultáneamente, patrones que ningún esquema único sirve igual de bien), o cuando el lado de escritura tiene suficiente complejidad de invariantes de negocio como para que mantener su modelo limpio y transaccional valga la pena por sí solo. Es excesivo para una aplicación CRUD sencilla donde los patrones de lectura ya coinciden razonablemente bien con el esquema de escritura — en ese caso, una réplica de lectura obtiene la mayor parte del beneficio de escalado a una fracción del costo operativo.