Dual-Write Problem
Qué es
El Dual-Write Problem aparece cuando una intención necesita actualizar dos sistemas independientes y el diseño no tiene una operación atómica que confirme o aborte ambos cambios juntos. Una escritura puede completarse y la otra fallar, dejando un estado parcial.
«Dos sistemas» describe dominios de commit distintos, no necesariamente dos servidores. Dos tablas dentro de una transacción local no presentan esa separación; una base de datos y un broker, o una base de datos y una API externa, suelen hacerlo.
Qué problema resuelve
El concepto permite reconocer una ventana que queda oculta en una secuencia aparentemente simple:
write A succeeds
write B failsEn el caso clásico, el pedido existe en la base de datos pero su evento no llega al broker. La documentación de AWS usa este caso para explicar por qué actualizar almacenamiento y enviar una notificación como acciones independientes puede dejar estados inconsistentes. AWS: transactional outbox y dual write.
El problema no es solamente detectar un error. Hay que conservar suficiente intención para recuperarse y definir qué deben observar los demás sistemas mientras falta uno de los cambios.
Cómo funciona
Ventanas de fallo
Una aplicación ejecuta guardar pedido y luego publicar PedidoCreado. El proceso puede caer después de la primera operación, sin llegar a invocar la segunda. También puede recibir un timeout de publicación sin saber si el broker aceptó el evento.
| A: base de datos | B: broker | Consecuencia |
|---|---|---|
| Confirmado | No publicado | El estado cambió, pero el consumidor no recibe la intención. |
| Confirmado | Publicado, respuesta perdida | Reintentar puede duplicar la publicación. |
| No confirmado | Publicado | Los consumidores reciben un hecho que no está confirmado en la fuente. |
Cambiar el orden desplaza la ventana: publicar primero permite emitir el evento de un pedido que después no se guarda. Agregar más try/catch no hace atómicas las dos confirmaciones.
Recuperación y fuente autoritativa
Una recuperación necesita decidir qué representa la intención durable. Si después del crash solo queda una fila de pedido sin memoria de su publicación pendiente, «reintentar lo que falta» puede ser imposible sin reconstrucción o reconciliación.
Hacer idempotentes las escrituras permite repetir con menos riesgo, pero no demuestra que ambas ocurran. idempotency y deduplication controlan repeticiones; no sustituyen la obligación de recuperar la escritura ausente.
Una confirmación del broker acredita su frontera, no que una transacción en otra base de datos haya ocurrido. delivery-guarantees ayuda a distinguir esas responsabilidades.
Ejemplo
El pedido 42 se confirma en A. Antes de publicar el evento, el proceso cae. El servicio de envíos no sabe que debe preparar el pedido. Reentregar los mensajes que ya tiene el broker no ayuda: nunca recibió este evento.
Un diseño puede guardar en la misma transacción local el pedido y una intención de publicación pendiente. Otro proceso publica esa intención y conserva la responsabilidad de reintentar hasta resolverla. Si cae después de publicar pero antes de registrar la confirmación, puede publicar de nuevo; el consumidor sigue necesitando identidad estable y protección contra duplicados.
El ejemplo introduce la motivación de transactional-outbox, cuyo canónico vive en Integration and Messaging / Messaging. La combinación transforma una ventana de publicación perdida en una obligación durable de entrega, pero no promete efectos exactly-once en cualquier consumidor externo.
Cuándo usarlo
Usar este análisis al diseñar estado de negocio más eventos, índices derivados, invalidaciones de cache, pagos o notificaciones. Para cada par de escrituras, preguntar quién puede confirmar ambas y quién recupera una ejecución parcial.
También aplica a consumidores que actualizan una base de datos y registran progreso o envían otro mensaje fuera de ella. El origen del problema no depende de que el actor sea productor o consumidor.
La obligación de negocio que exige conservar ambos efectos puede actuar como uno de los architectural-drivers. Lo significativo es definir fronteras de confirmación y recuperación; la preferencia por una base de datos o un broker no expresa por sí sola esa necesidad.
Cuándo NO usarlo
No asumirlo por contar dos sentencias. Si las dos escrituras pertenecen a una transacción local con las propiedades necesarias, su commit ya aporta una frontera conjunta para esas escrituras.
No usar «dual write» para afirmar que toda inconsistencia es inaceptable. Dos cambios independientes pueden tolerar divergencia temporal o incluso pérdida de uno. Lo necesario es declarar ese contrato y sus consecuencias, no agregar coordinación por defecto.
Trade-offs y alternativas
Hay familias de respuestas distintas:
- Transacción local más intención durable: reduce la atomicidad necesaria a un store; acepta propagación posterior y necesita reintentos y observabilidad. transactional-outbox aplica esta familia a la publicación de mensajes.
- atomic-commit mediante two-phase-commit: busca un commit conjunto entre participantes compatibles. Su costo de coordinación, recuperación y posible bloqueo debe evaluarse; no se obtiene por envolver una llamada al broker en una transacción local.
- Saga: coordina una secuencia de pasos y compensaciones de negocio. No equivale a un commit atómico ni borra efectos que ya fueron observados.
- Reconciliación o reconstrucción: acepta divergencia y repara comparando con una fuente autoritativa. Requiere poder identificar lo faltante y una latencia de reparación aceptable.
- Eliminar una escritura independiente: derivar el segundo estado desde un registro durable puede simplificar el modelo; hay que evaluar sus límites y costos.
El MOC distributed-transactions compara formas de coordinación y reservas de negocio. reliable-messaging continúa hacia publicación y consumo: una outbox puede republicar, y idempotent-consumer o inbox-pattern pueden proteger una frontera local del consumidor. No forman una cadena obligatoria ni un commit global.
Errores comunes: reordenar llamadas y declarar resuelto el problema; hacer reintentos sin conservar intención durable; confundir idempotencia con atomicidad; y confirmar al emisor antes de asegurar el efecto prometido.
Relaciones
- delivery-guarantees delimita qué acredita cada confirmación.
- at-least-once ayuda a propagar una intención pendiente con posibles repeticiones.
- idempotency y deduplication reducen el riesgo de repetir durante recuperación.
- exactly-once-semantics explica por qué la garantía no cruza automáticamente dominios de commit.