Playbook
Patrón de bandeja de salida/bandeja de entrada en sistemas de pago (Patron DE Bandeja DE Salidabandeja DE Entrada EN Sistemas DE Pago)
Si escribir en la base de datos y publicar un evento no están en la misma transacción, uno de ellos se perderá o se repetirá. Transmisiones de bandeja de salida, desduplicación de bandeja de entrada para el consumidor.
Motor de pago distribuido
Parte 7 de 22
Una serie de arquitecturas de pago distribuidas que cierran la brecha entre la captura y la finalización.
Dos escrituras, ni una transacción
Cuando un servicio decide "realizar el pedido", generalmente quiere hacer dos cosas: escribir una fila en la base de datos y publicar un evento en una cola/broker de mensajes. Dado que estas dos escrituras van a sistemas diferentes, no se pueden realizar de forma atómica en una sola transacción. El espacio entre ellos se llama "agujero de doble escritura".```text DbContext.SaveChanges() → veritabanına yazıldı Broker.Publish(event) → burası başarısız olursa event kaybolur (veya sıra tersine çevrilirse, event gönderilir ama DB commit rollback olur)
## Conceptos en la primera mención```text
📦 Dual-Write Problemi
Aynı iş kararının iki farklı sisteme (DB ve broker) atomik olmayan iki yazma ile yansıtılmaya çalışılması.
📦 Outbox
İş kararıyla aynı veritabanı transaction'ında yazılan, sonradan ayrı bir süreç tarafından yayınlanan olay tablosu.
📦 Lease
Bir publisher sürecinin, outbox'taki bir satırı işlerken diğer publisher süreçlerinin aynı satırı almasını önleyen geçici kilit.
📦 Inbox
Tüketici tarafında, gelen bir olayın işlenmeden önce kaydedildiği ve tekrarları filtreleyen tablo.
📦 Effectively-once
At-least-once teslimat ile idempotent tüketicinin birleşiminden elde edilen, pratikte “tam olarak bir kez” gibi davranan sonuç.
```Outbox resuelve el problema del lado emisor (escritura + atomicidad de transmisión). Inbox soluciona el problema del receptor (reenvío). Juntos, establecen un flujo de eventos confiable de un extremo a otro.
## Cómo abrir el agujero de escritura dual
Si un controlador emite un evento al corredor inmediatamente después de escribir en la base de datos, el sistema puede fallar entre dos pasos separados. Si la escritura de la base de datos tiene éxito pero la llamada del corredor falla, la decisión comercial persiste pero el evento nunca se publica; los servicios posteriores nunca serán notificados de esta decisión.```text
1. DB: Order.Status = Captured ✓ (commit edildi)
2. Broker.Publish(OrderCaptured) ✗ (network hatası, process crash)
Sonuç: Order tablosunda Captured var, ama hiçbir servise event gitmedi
```También es posible lo contrario: el evento se publica pero la transacción de la base de datos se revierte; En este caso, los servicios posteriores reaccionan ante un evento que nunca ocurrió.
## Bandeja de salida: mueve escritura y transmisión a la misma transacción
El patrón de bandeja de salida escribe el evento en una tabla de bandeja de salida dentro de la misma transacción de base de datos que la decisión comercial, en lugar de transmitirlo directamente al corredor. Esta línea ahora persiste atómicamente mediante decisiones comerciales: ambos se comprometen o ninguno.```text
Tek transaction:
UPDATE orders SET status = 'Captured' WHERE id = 42;
INSERT INTO outbox (event_type, payload, dispatched_at) VALUES ('OrderCaptured', ..., NULL);
COMMIT
```Un proceso de publicación independiente escanea periódicamente las `dispatched_at IS NULL` filas en la bandeja de salida, las publica en el intermediario y, si tiene éxito, completa el campo `dispatched_at`. Si este editor trabaja con más de una instancia, se utiliza un mecanismo de concesión (bloqueo a corto plazo) para evitar que dos instancias reciban la misma fila al mismo tiempo.```text
Publisher A: satır #7'yi lease'ler (30 sn) → broker'a yayınlar → dispatched_at = now()
Publisher B: satır #7 lease'li, atlar; başka satır arar
```Este paso garantiza que el evento se transmita al menos una vez, pero puede transmitirse dos veces si el editor falla. Por eso es necesaria una defensa por parte del consumidor.
## Bandeja de entrada: deduplicación del lado del consumidor
Dado que Outbox garantiza al menos una transmisión, el consumidor puede recibir el mismo evento más de una vez. En lugar de procesar el evento directamente, el patrón de la bandeja de entrada primero lo escribe en la tabla de la bandeja de entrada con una identificación de evento única; Si esta escritura ya existe (violación de restricción única), el evento no se procesa nuevamente.```text
Tüketici event alır: event_id=evt_001
INSERT INTO inbox (event_id, status) VALUES ('evt_001', 'received')
→ başarılı: iş bir job'a kuyruklanır
→ unique constraint hatası: zaten görülmüş, sessizce atlanır
```## Bandeja de salida + Bandeja de entrada = efectivamente, una vez
La bandeja de salida garantiza que la transmisión no se pierda (al menos una vez). Inbox garantiza que la misma transmisión no se procese dos veces en el lado del consumidor (consumidor idempotente). Cuando estos dos se combinan, el sistema prácticamente se comporta “exactamente una vez”; a esto se le llama efectivamente una vez; Las verdaderas garantías exactamente una vez son casi imposibles en sistemas distribuidos, pero esta combinación es una aproximación adecuada en la práctica.```text
Outbox (gönderen) → at-least-once yayın
Inbox (alan) → idempotent tüketim
─────────────────────────────────────────
Toplam davranış → effectively-once
Los enfrentamientos más confusos de este episodio.```text
❌ DB'ye yazıp hemen ardından broker'a publish etmek güvenlidir ✓ Bu iki adım atomik değildir; aralarında dual-write hole vardır
❌ Outbox tek başına tekrar teslimatı önler ✓ Outbox at-least-once garanti eder; tekrar teslimata karşı tüketici tarafında inbox gerekir
❌ Lease, kalıcı bir kilittir ✓ Lease geçici ve süreli bir kilittir; publisher crash olursa süre dolunca serbest kalır
❌ Effectively-once, exactly-once ile aynı şeydir ✓ Exactly-once dağıtık sistemlerde pratik değildir; effectively-once, at-least-once + idempotency'nin sonucudur
## Lista de verificación de la configuración de su bandeja de salida/bandeja de entrada
1. ¿Escribir su decisión comercial en la base de datos y publicar el evento se realiza en la misma transacción o en dos pasos separados?
2. Si su editor de Bandeja de salida trabaja con más de una instancia, ¿tiene algún mecanismo de concesión que impida que dos instancias reciban la misma línea?
3. Si Publisher falla, ¿se puede volver a intentar la línea después de que expire el contrato de arrendamiento o permanecerá "alquilado" para siempre?
4. Del lado del consumidor, ¿existe una restricción única para la identificación del evento en su tabla de bandeja de entrada?
5. ¿Existe una métrica/alarma que rastrea el número de filas con `dispatched_at IS NULL` en la Bandeja de salida (indicador de retraso de publicación)?
Si no puede responder dos de estas cinco preguntas con confianza, es probable que su sistema aún sea vulnerable a un agujero de doble escritura.
## Cosas para recordar de esta sección
1. Escribir en la base de datos y publicar un evento van a sistemas diferentes y no son atómicos; Este espacio es un agujero de doble escritura.
2. La bandeja de salida garantiza la atomicidad para el remitente al escribir el evento en la misma transacción que la decisión comercial; Un proceso de publicación independiente lo publica.
3. El arrendamiento evita que varias instancias del editor procesen la misma fila de la bandeja de salida al mismo tiempo; Es un bloqueo temporal, no permanente.
4. Inbox evita que el mismo evento se procese dos veces por parte del consumidor; Hace que la garantía de al menos una vez de Outbox sea idempotente.
> Un evento publicado sin Bandeja de salida puede desaparecer tan pronto como se lanza. Un evento recibido sin Inbox se puede procesar dos veces, lo que hace que perderse sea el doble de caro.
FAQ
Frequently asked questions
¿Qué es el problema de la escritura dual?
Intentar reflejar la misma decisión comercial en dos sistemas diferentes (DB y broker) con dos escrituras no atómicas.
¿Qué es la bandeja de salida?
Tabla de eventos escrita en la misma transacción de base de datos que la decisión comercial, publicada posteriormente mediante un proceso separado.
¿Es cierto que "es seguro escribir en la base de datos y luego publicarlo inmediatamente en el corredor"?
Estos dos pasos no son atómicos; Hay un agujero de doble escritura entre ellos.
¿Qué soluciona esta sección?
Esta sección describe los patrones de las bandejas de entrada y de salida que cierran esta brecha. Escribir en la base de datos y publicar un evento van a sistemas diferentes y no son atómicos; Este espacio es un agujero de doble escritura. Cuando un servicio decide "realizar el pedido", generalmente quiere hacer dos cosas: escribir una fila en la base de datos y publicar un evento en una cola/broker de mensajes. Dado que estas dos escrituras van a sistemas diferentes, no se pueden realizar de forma atómica en una sola transacción. El espacio entre ellos se llama "agujero de doble escritura".
Principios de ingeniería aprendidos
- Si la escritura de la base de datos y la publicación de eventos no están en la misma transacción, el agujero de la escritura dual permanece abierto; La bandeja de salida cierra esta brecha en el lado del remitente.
- Inbox es el complemento obligatorio que hace que la garantía de al menos una vez de Outbox sea idempotente para el consumidor.
- Efectivamente, una vez no sustituye a exactamente una vez; Es el resultado práctico producido por la entrega al menos una vez y el consumo idempotente.
Continuar leyendo
Continuar leyendo
Siguiente en la serie
Comprobante de pago y estado de pago: por qué no debe confundirse
La evidencia es lo que PSP dice que es. El estado es lo que tú decides. Si mantiene estos dos en el mismo registro, sabrá en cuál confiar durante la…
Siguiente en la serie
Fiabilidad del webhook en sistemas de pago
Los webhooks se repiten, desaparecen, llegan desordenados y con retraso. Verifique la firma, proporcione ACK rápido, nunca ejecute trabajos pesados…
Misma serie
Idempotencia (transacción segura repetible) más allá de la solicitud de API (interfaz de programación de aplicaciones)
La idempotencia no es un único encabezado. Es una pila de defensa que debe configurarse por separado en cinco capas diferentes, desde la clave API hasta el…