Evidence ledger
One row per claim in You cannot delete the dual write, only move it: who published it, what grade it carries, when it was written, when the link was last checked, and the quote or figure it rests on. Nothing in the guide is cited from memory, so anything not in this table is not in the guide.
Field guide: You cannot delete the dual write, only move it Topic: how production systems keep a database write and the event that announces it in agreement. Research date: 2026-08-29. All links fetched on 2026-08-29.
Tiers follow references/evidence.md: postmortem, source, adr, casestudy, blog,
paper, talk, vendor.
| # | Org | Title | Tier | Published | Checked | URL | Claim I take from it | Supporting quote or figure |
|---|---|---|---|---|---|---|---|---|
| 1 | PagerDuty | August 28 Kafka Outages: What Happened and How We're Improving | postmortem | 2025-09 | 2026-08-29 | https://www.pagerduty.com/eng/august-28-kafka-outages-what-happened-and-how-were-improving/ | An outbox converts a broker outage into a backlog and a duplicate-delivery event, not a loss event. | "No previously accepted events or data were lost during or after the incident." / "affected customers may have received duplicate webhooks." |
| 2 | PagerDuty | August 28 Kafka Outages | postmortem | 2025-09 | 2026-08-29 | https://www.pagerduty.com/eng/august-28-kafka-outages-what-happened-and-how-were-improving/ | The trigger was a producer-lifecycle bug, not a data-consistency bug; the consistency machinery held while the transport collapsed. | "nearly 4.2 million extra producers per hour at peak. This is 84 times higher than our typical number of new producers" |
| 3 | PagerDuty | August 28 Kafka Outages | postmortem | 2025-09 | 2026-08-29 | https://www.pagerduty.com/eng/august-28-kafka-outages-what-happened-and-how-were-improving/ | Blast radius and duration of the two incidents. | First incident 03:53 UTC to 10:10 UTC; second 16:38 UTC to 20:24 UTC; "18.87% of create requests returned 502 errors at peak" |
| 4 | Cloudflare | Cloudflare incident on November 14, 2024, resulting in lost logs | postmortem | 2024-11-26 | 2026-08-29 | https://blog.cloudflare.com/cloudflare-incident-on-november-14-2024-resulting-in-lost-logs/ | In an at-least-once event pipeline, the buffer tier is the component that turns a configuration error into permanent loss. | "about 55% of the logs we normally send to customers were not sent and were lost"; Buftee went from "40 million buffers globally" to roughly 40 times that |
| 5 | Cloudflare | Cloudflare incident on November 14, 2024 | postmortem | 2024-11-26 | 2026-08-29 | https://blog.cloudflare.com/cloudflare-incident-on-november-14-2024-resulting-in-lost-logs/ | Scale of the pipeline and the untested failsafe. | "4.5 trillion individual event logs" pushed daily; logreceiver processes "about 45 PB (uncompressed) of customer event logs each day"; the team "neglected to regularly test that the broader system was capable of handling a fail open event" |
| 6 | GitHub | October 21 post-incident analysis | postmortem | 2018-10-30 | 2026-08-29 | https://github.blog/news-insights/company-news/oct21-post-incident-analysis/ | When two stores each hold writes the other does not, the recovery cost is reconciliation, and it is measured in hours not seconds. | "Because the database clusters in both data centers now contained writes that were not present in the other data center, we were unable to fail the primary back over"; "one of our busiest clusters had 954 writes in the affected window" |
| 7 | GitHub | October 21 post-incident analysis | postmortem | 2018-10-30 | 2026-08-29 | https://github.blog/news-insights/company-news/oct21-post-incident-analysis/ | Event backlogs outlive the incident that produced them. | "over five million hook events and 80 thousand Pages builds queued"; degradation lasted 24 hours 11 minutes |
| 8 | Andreas Andreakis (author of Netflix DBLog) | Change-Data-Capture Doesn't Solve Dual-Writes | blog | 2026-08-05 | 2026-08-29 | https://aandreakis.com/posts/change-data-capture-doesnt-solve-dual-writes | The central thesis of this guide: CDC relocates the dual write rather than removing it. | "The remedy for dual writes is itself implemented as a dual write." / "CDC moves this crash window downstream and puts replayable history behind it. It does not close the window for side effects." |
| 9 | Apache Kafka | KIP-618: Exactly-Once Support for Source Connectors | adr | 2023-06-07 (accepted) | 2026-08-29 | https://cwiki.apache.org/confluence/display/KAFKA/KIP-618%3A+Exactly-Once+Support+for+Source+Connectors | Kafka's own design record confirms the relay-level dual write: records are produced, then offsets are written, and the gap between them is the duplicate window. | "without framework-level adaptations, exactly-once delivery for source connectors remains impossible"; the framework "periodically writes task offsets to an internal Kafka topic after records reach Kafka" |
| 10 | Apache Kafka | KIP-939: Support Participation in 2PC | adr | 2024-07-23 (accepted) | 2026-08-29 | https://cwiki.apache.org/confluence/display/KAFKA/KIP-939%3A+Support+Participation+in+2PC | Two-phase commit is being deliberately reintroduced to Kafka for exactly this problem, with its costs written down. | "Dual write is easy to implement but means that whenever there is a failure there is a high likelihood that the log and the database will diverge." / "The transaction is never aborted automatically" / read_committed consumers "cannot consume past the ongoing transaction" |
| 11 | Apache Kafka (JIRA) | KAFKA-15370: Support Participation in 2PC (KIP-939) | source | created 2023-08-17, updated 2026-08-13 | 2026-08-29 | https://issues.apache.org/jira/browse/KAFKA-15370 | Three years after acceptance the feature has not shipped. | Status "Open", Resolution "Unresolved", Fix Version "4.5.0", updated 13/Aug/26 |
| 12 | Apache Kafka | Apache Kafka 4.0.0 Release Announcement | vendor | 2025-03-18 | 2026-08-29 | https://kafka.apache.org/blog/2025/03/18/apache-kafka-4.0.0-release-announcement/ | KIP-939 is absent from the 4.0 headline feature set. | Headline KIPs listed are 848, 932, 966, 996, 890, 1102, 653; KIP-939 is not mentioned |
| 13 | Apache Flink | FLIP-319: Integrate with Kafka's Support for Proper 2PC Participation | adr | 2023-08-18 | 2026-08-29 | https://cwiki.apache.org/confluence/pages/viewpage.action?pageId=255071710 | The most widely deployed exactly-once Kafka sink has a documented data-loss mode and works by bypassing the protocol. | "Data loss can occur when Kafka aborts a successfully checkpointed transaction due to timeout." / the implementation "relies heavily on Java reflection in order to bypass the Kafka transaction protocol" |
| 14 | Zalando | Contributing to Debezium: Fixing Logical Replication at Scale | casestudy | 2025-12-19 | 2026-08-29 | https://engineering.zalando.com/posts/2025/12/contributing-to-debezium.html | The replication slot and the connector's offset store are two records of the same position, and they drift. | "we've always treated the PostgreSQL replication slot as the authoritative source of truth for stream position"; Debezium had "hard-coded the pgjdbc keepalive flush feature to disabled by setting withAutomaticFlush(false)" |
| 15 | Zalando | Contributing to Debezium | casestudy | 2025-12-19 | 2026-08-29 | https://engineering.zalando.com/posts/2025/12/contributing-to-debezium.html | Scale and duration of the deployment, and the WAL-growth failure it fixed. | "replication slots wouldn't advance without table activity, causing WAL to pile up until disk space ran out"; "hundreds of thousands of events per second across our 100+ Kubernetes clusters"; ran Debezium 2.7.4 "for nearly two years, processing billions of events with zero detected data loss" |
| 16 | Shopify | Capturing Every Change From Shopify's Sharded Monolith | casestudy | 2021-03-12 | 2026-08-29 | https://shopify.engineering/capturing-every-change-shopify-sharded-monolith | Log-based CDC of business tables is viable at very large scale, and its cost is schema coupling. | "~150 Debezium connectors across 12 Kubernetes pods"; "400TB+ of CDC data in our Kafka cluster"; "~65,000 records/second on average, handling spikes up to 100,000 records/second"; "p99 latency is less than 10 seconds" |
| 17 | Shopify | Capturing Every Change From Shopify's Sharded Monolith | casestudy | 2021-03-12 | 2026-08-29 | https://shopify.engineering/capturing-every-change-shopify-sharded-monolith | Snapshotting is the operational tax of table-level CDC. | "Several tables in Shopify's Core monolith are too big to snapshot in any reasonable time-frame"; initial snapshots hold read locks for hours and block binlog ingestion |
| 18 | Trade Republic (Sadeq Dousti) | PostgreSQL + Outbox Pattern Revamped, Part 1 | blog | 2025-06-08 | 2026-08-29 | https://dev.to/msdousti/postgresql-outbox-pattern-revamped-part-1-3lai | A polled outbox degrades by five orders of magnitude through index bloat, and partitioning is the fix. | "it fetches close to 100 million rows from the heap, which takes over 18.5 seconds to execute. This means a slowdown 5 orders of magnitude"; after partitioning, "a consistent result of 1000 heap fetches and an execution time of 1-3 ms" |
| 19 | Zapier | Lessons from using the outbox pattern at scale | blog | 2026-03-30 (date from search listing; not shown on page) | 2026-08-29 | https://zapier.com/blog/lessons-from-using-outbox-pattern-at-scale/ | A local durable outbox on the write path buys availability and charges you storage operations. | "fifteen thousand events per second" at peak; 50 sharded SQLite files per pod; replaced by an S3 and SQS sidecar that "removes the local SQLite write from the hot emit path" |
| 20 | Gunnar Morling (Confluent, ex-Debezium lead) | Can Debezium Lose Events? | blog | 2023-11-14 | 2026-08-29 | https://www.morling.dev/blog/can-debezium-lose-events/ | Loss in log-based CDC is an operational condition, not a defect: it happens when retention expires before the reader catches up. | "In general, Debezium by itself should never miss any event."; loss occurs when "due to operational deficiencies portions of the database's transaction log get discarded before Debezium gets a chance to capture them"; MySQL binlog_expire_logs_seconds defaults to 2,592,000 seconds |
| 21 | Gunnar Morling | Can Debezium Lose Events? | blog | 2023-11-14 | 2026-08-29 | https://www.morling.dev/blog/can-debezium-lose-events/ | The Postgres safety mechanism trades one failure for the other. | Replication slots prevent loss by default but risk disk exhaustion; PostgreSQL 13's max_slot_wal_keep_size reintroduces loss risk if configured |
| 22 | SQUER (David Leitner) | Stop overusing the outbox pattern | blog | undated on page | 2026-08-29 | https://www.squer.io/blog/stop-overusing-the-outbox-pattern | A credible dissent: the outbox makes the database the bottleneck of an architecture chosen to avoid one. | The pattern "turns the database into the system's bottleneck, something we wanted to avoid with EDAs in the first place." |
| 23 | Clear Street | Designing Clear Street's First Transactional Outbox Pattern | blog | 2020-10-01 | 2026-08-29 | https://www.clearstreet.io/news/blog/transactional-outboxes | An outbox table read by CDC is the common production shape, and its guarantee is explicitly at-least-once. | 2PC rejected as "slow and, frankly, complicated to implement"; "you are guaranteed that a corresponding event with the details of what you changed are published to Kafka at-least once" |
| 24 | Airbyte (issue #82266) | Full "Clear data" never erases global CDC shared state | source | 2026-07-20 | 2026-08-29 | https://github.com/airbytehq/airbyte/issues/82266 | The offset-versus-slot divergence is not a Zalando idiosyncrasy; it is a reproducible failure with its own error string. | "Saved offset is before replication slot's confirmed lsn. Please reset the connection, and then increase WAL retention and/or increase sync frequency to prevent this from happening in the future." |
| 25 | Airbyte (issue #31312) | [source-postgres] No records being synced via CDC for large WAL | source | 2023-10 | 2026-08-29 | https://github.com/airbytehq/airbyte/issues/31312 | A CDC reader can report success while capturing nothing, which is the worst failure shape available. | "WAL resume position 'null' discovered"; 900GB table, ~50GB WAL per day, ~25GB accumulated before sync |
| 26 | Debezium (PR #7687) | DDD-53: trigger-less outbox polling connector, proposal only | source | opened 2026-07-18, closed unmerged 2026-08-14 | 2026-08-29 | https://github.com/debezium/debezium/pull/7687 | The design space is still open: a proposal to detect changes without a replication slot was closed without technical objection, only relocated. | Maintainer response in full: "This DDD was moved to debezium/debezium-design-documents#51."; proposal cites "approximately 15 MB per million watched rows" |
| 27 | Airbnb | SpinalTap README | source | repo, master branch | 2026-08-29 | https://github.com/airbnb/SpinalTap/blob/master/README.md | An independent in-house CDC service converges on the same components as Debezium: source, destination, external state store. | "SpinalTap is a general-purpose reliable Change Data Capture (CDC) service"; state coordination via ZooKeeper, position tracked as initial_binlog_position with fileName, position, nextPosition |
| 28 | Netflix (Andreakis, Papapanagiotou) | DBLog: A Watermark Based Change-Data-Capture Framework | paper | 2020-10-23, arXiv:2010.12597 | 2026-08-29 | https://arxiv.org/abs/2010.12597 | Snapshot and stream can be interleaved without locks, which is the mechanism that makes backfill affordable. | "a watermark based approach that allows us to interleave transaction log events with rows that we directly select from tables"; "does not use locks and has minimum impact on the source"; "currently used in production by tens of microservices at Netflix" |
| 29 | Kleppmann, Beresford, Svingen | Online Event Processing: Achieving consistency where distributed transactions have failed | paper | CACM 62(5), May 2019 | 2026-08-29 | https://martin.kleppmann.com/2019/05/01/olep-cacm.html | The intellectual case for the log-first architecture, and the sentence the industry took as permission to stop using 2PC. | "Distributed transactions have failed as a mechanism for ensuring consistency across heterogeneous storage technologies in today's large-scale applications." |
| 30 | Facebook (Sharma et al., NSDI '15), via The Morning Paper | Wormhole: Reliable Pub-Sub to Support Geo-replicated Internet Services | paper | paper 2015; summary 2015-05-14 | 2026-08-29 | https://blog.acolyer.org/2015/05/14/wormhole-reliable-pub-sub-to-support-geo-replicated-internet-services/ | Reading the storage engine's own transaction log, with at-least-once delivery, is a decade-old production answer, not a new one. | "Wormhole publishers directly read the transaction logs maintained by the data storage systems"; "at least once delivery"; "over 35 GBytes/sec of updates at steady state across 5 trillion messages per day" |
| 31 | Gunnar Morling | Practical Change Data Streaming Use Cases With Apache Kafka and Debezium (QCon San Francisco 2019) | talk | 2019-11-12 | 2026-08-29 | https://speakerdeck.com/gunnarmorling/practical-change-data-streaming-use-cases-with-apache-kafka-and-debezium-qcon-san-francisco-2019 | The slogan that set the industry default, and the outbox design behind it. | Slide 10 and slide 60: "Friends Don't Let Friends Do Dual Writes"; outbox table design on slides 41 and 42 |
| 32 | Artem Livshits (Confluent) | Atomic Dual-write Recipes with Kafka Two Phase Commit (KIP-939), Current 2024 | talk | 2024 | 2026-08-29 | https://current.confluent.io/2024-sessions/atomic-dual-write-recipes-with-kafka-two-phase-commit-kip-939 | The 2PC revival is being presented to practitioners as the answer to exactly this problem. | "if failures happen Kafka and database will diverge"; the recipe guarantees "events are committed to Kafka iff changes are committed to the database, even in the presence of failures" |
| 33 | Chris Egerton (Aiven) | Exactly-Once, Again: Adding EOS Support for Kafka Connect Source Connectors, Kafka Summit London 2023 | talk | 2023 | 2026-08-29 | https://www.confluent.io/events/kafka-summit-london-2023/exactly-once-again-adding-eos-support-for-kafka-connect-source-connectors/ | Kafka Connect source connectors were not exactly-once until 3.3, which dates the gap precisely. | "Kafka Connect lacked this support for source connectors until version 3.3" |
| 34 | Confluent (Wade Waldron) | Understanding the Dual-Write Problem and Its Solutions | vendor | 2024-05-29 | 2026-08-29 | https://www.confluent.io/blog/dual-write-problem/ | The canonical vendor statement of the problem and the four-option menu everyone works from. | "The dual-write problem occurs when two external systems must be updated in an atomic fashion."; the outbox "works well for solving the dual-write problem but is restricted to transactional databases" |
| 35 | Confluent Developer | Designing Event-Driven Microservices: The Listen to Yourself Pattern | vendor | course page, undated | 2026-08-29 | https://developer.confluent.io/courses/microservices/the-listen-to-yourself-pattern/ | Listen-to-yourself removes the dual write by giving up read-your-writes, and the trade is stated plainly. | callers may query immediately and "it won't find what it is looking for"; validation failures after the caller disconnects create "additional inconsistencies in our system" |
Tier mix
postmortem 7 rows across 3 incidents (PagerDuty, Cloudflare, GitHub) · source 5 · adr 3 · casestudy 4 · blog 8 · paper 3 · talk 3 · vendor 2.
Distinct organisations represented: PagerDuty, Cloudflare, GitHub, Netflix, Zalando, Shopify, Trade Republic, Zapier, Clear Street, Airbnb, Airbyte, Debezium, Apache Kafka, Apache Flink, Facebook, Confluent, SQUER, Cambridge/independent authors. Eighteen.
Where the record runs out
- No published account quantifies duplicate rates. Every system here promises at-least-once and none publishes what the duplicate rate actually is under normal operation or after a rebalance. An architect sizing a deduplication store has no public number to work from.
- No public postmortem attributes a customer-visible incident to an outbox row that was committed and never published. The failure everyone designs against is the one nobody has written up. The published incidents are all about the transport, the buffer, the position record or the database underneath.
- KIP-939 has no production account at all. It was accepted in July 2024 and has not shipped. Everything written about it, including the Current 2024 session, describes intent.
- Trade Republic's part 2 and Clear Street's promised follow-up were not published. Both posts end by saying the harder material is coming. It did not come.