Scenario library
A fixed set of failure shapes, chosen because each one is quiet — the kind that produces no error and therefore tests whether you are alerting on the right signal. Every scenario sends a payload describing the failure. None of them touch data.
Data deletion
- target 15m
Bulk record deletion
4,120 rows removed from a customer-facing table in a single statement.
Sends the event your system would emit if a maintenance script deleted thousands of live records at once. Nothing in your database is touched — the payload only describes the deletion.
should be caught byA row-count or delete-volume monitor, or an audit-log rule on unusually large mutations.
Set up this simulationShow the payload this sends
POST → your webhook endpoint
{ "source": "datalosssimulator", "synthetic": true, "event": "drill.fired", "run_id": "run_example", "fired_at": "2026-01-01T02:00:00.000Z", "scenario": { "id": "bulk-record-deletion", "name": "Bulk record deletion", "category": "deletion" }, "summary": "[DRILL] Bulk record deletion — 4,120 rows removed from a customer-facing table in a single statement.", "signal": { "table": "customer_records", "rows_deleted": 4120, "actor": "svc-maintenance", "window_seconds": 3 }, "note": "Synthetic drill from DataLossSimulator. No production data was read, changed or deleted." } - target 30m
Cascade delete overreach
One parent row removed; 38 child rows disappear with it, across two tables.
Models the quiet version of data loss — a foreign key cascade that removes more than the operator intended. The event names the parent, the tables touched, and the counts.
should be caught byReferential-integrity alerting, or a monitor on deletes in tables no human writes to directly.
Set up this simulationShow the payload this sends
POST → your webhook endpoint
{ "source": "datalosssimulator", "synthetic": true, "event": "drill.fired", "run_id": "run_example", "fired_at": "2026-01-01T02:00:00.000Z", "scenario": { "id": "cascade-overreach", "name": "Cascade delete overreach", "category": "deletion" }, "summary": "[DRILL] Cascade delete overreach — One parent row removed; 38 child rows disappear with it, across two tables.", "signal": { "parent_table": "accounts", "parent_id": "acct_9f31c2", "cascaded_tables": "invoices, invoice_lines", "rows_deleted": 38 }, "note": "Synthetic drill from DataLossSimulator. No production data was read, changed or deleted." }
Export & backup
- target 60m
Export job silent failure
Nightly export writes a 0-byte file and exits 0.
The failure mode where nothing errors. The job reports success, the file lands, and the file is empty. Your alerting has to notice the size, not the exit code.
should be caught byA file-size or record-count assertion on the export artefact, not just a job-status check.
Set up this simulationShow the payload this sends
POST → your webhook endpoint
{ "source": "datalosssimulator", "synthetic": true, "event": "drill.fired", "run_id": "run_example", "fired_at": "2026-01-01T02:00:00.000Z", "scenario": { "id": "export-silent-failure", "name": "Export job silent failure", "category": "export" }, "summary": "[DRILL] Export job silent failure — Nightly export writes a 0-byte file and exits 0.", "signal": { "job": "nightly_export", "exit_code": 0, "bytes_written": 0, "expected_rows_min": 50000 }, "note": "Synthetic drill from DataLossSimulator. No production data was read, changed or deleted." } - target 120m
Backup window passes with no run
The scheduled backup produces no record at all — no success, no failure.
Absence of a signal, which is the hardest thing to alert on. A monitor that only reacts to failures stays silent forever here.
should be caught byA dead-man's-switch or heartbeat monitor that fires when an expected event does not arrive.
Set up this simulationShow the payload this sends
POST → your webhook endpoint
{ "source": "datalosssimulator", "synthetic": true, "event": "drill.fired", "run_id": "run_example", "fired_at": "2026-01-01T02:00:00.000Z", "scenario": { "id": "backup-never-started", "name": "Backup window passes with no run", "category": "export" }, "summary": "[DRILL] Backup window passes with no run — The scheduled backup produces no record at all — no success, no failure.", "signal": { "job": "pg_basebackup", "expected_at": "02:00 UTC", "observed_runs": 0, "last_successful_backup_hours_ago": 51 }, "note": "Synthetic drill from DataLossSimulator. No production data was read, changed or deleted." }
Dependency outage
- target 10m
Third-party API returns 500s
A dependency answers 500 for ten minutes; requests fail behind a retry layer.
Simulates a partner API degrading while your retry logic absorbs it. Latency rises, the error budget burns, and the user-visible symptom lags the cause.
should be caught byAn error-rate or SLO burn-rate alert on the dependency, distinct from your own 5xx rate.
Set up this simulationShow the payload this sends
POST → your webhook endpoint
{ "source": "datalosssimulator", "synthetic": true, "event": "drill.fired", "run_id": "run_example", "fired_at": "2026-01-01T02:00:00.000Z", "scenario": { "id": "upstream-500s", "name": "Third-party API returns 500s", "category": "outage" }, "summary": "[DRILL] Third-party API returns 500s — A dependency answers 500 for ten minutes; requests fail behind a retry layer.", "signal": { "dependency": "payments-api", "status": 500, "error_rate": "97%", "duration_minutes": 10 }, "note": "Synthetic drill from DataLossSimulator. No production data was read, changed or deleted." } - target 30m
Outbound webhook queue backs up
Queue depth climbs to 12,400 and the oldest message is 46 minutes old.
Delivery has not stopped, it has slowed — so success-rate dashboards stay green while customers stop receiving events. Queue age is the signal that matters.
should be caught byA queue-depth or oldest-message-age alert, rather than a delivery success-rate alert.
Set up this simulationShow the payload this sends
POST → your webhook endpoint
{ "source": "datalosssimulator", "synthetic": true, "event": "drill.fired", "run_id": "run_example", "fired_at": "2026-01-01T02:00:00.000Z", "scenario": { "id": "webhook-backlog", "name": "Outbound webhook queue backs up", "category": "outage" }, "summary": "[DRILL] Outbound webhook queue backs up — Queue depth climbs to 12,400 and the oldest message is 46 minutes old.", "signal": { "queue": "outbound_webhooks", "depth": 12400, "oldest_message_age_minutes": 46, "consumers": 2 }, "note": "Synthetic drill from DataLossSimulator. No production data was read, changed or deleted." }