An educational field note with illustrative architecture and code. Example metrics and outputs are not VENIZOR production benchmarks.
Why 90% of Event-Driven Systems Have Silent Data Loss
Imagine an e-commerce checkout service. It updates the order status in PostgreSQL to 'PAID', and then immediately calls `kafkaProducer.send('OrderPaidEvent')`. If the network blips before Kafka responds, or if the server crashes in that 2-millisecond window, the database says the order is paid, but the shipping service never receives the event.
Conversely, if you publish to Kafka first and the database transaction fails, you ship goods for an order that was never recorded. This is the dual-write dilemma.
1. The Transactional Outbox Pattern Architecture
The solution is elegant: never call an external message broker inside your business transaction. Instead, write an event record into a dedicated `outbox_events` table within the exact same database transaction as your business mutation. PostgreSQL's ACID guarantees that either both write, or neither does.
-- Atomic Outbox Transaction Pattern
BEGIN;
-- 1. Mutate primary business entity
UPDATE accounts
SET balance = balance - 1500.00, updated_at = NOW()
WHERE account_id = 'acc_984192';
-- 2. Append event to outbox in the SAME transaction
INSERT INTO outbox_events (
id,
aggregate_type,
aggregate_id,
event_type,
payload,
created_at
) VALUES (
gen_random_uuid(),
'Account',
'acc_984192',
'AccountDebited',
jsonb_build_object('amount', 1500.00, 'currency', 'USD', 'timestamp', NOW()),
NOW()
);
COMMIT;
-- Change Data Capture (Debezium) tails WAL and streams to Kafka with zero loss!2. Log-Based CDC vs Polling Publishers
Older implementations polled the outbox table every 500ms using `SELECT ... FOR UPDATE`, causing database lock contention and CPU spikes. Modern production systems use Debezium connected directly to the PostgreSQL Write-Ahead Log (WAL), streaming events to Kafka with sub-20ms latency and zero query overhead.