Skip to main content

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

  • Bulk record deletion

    4,120 rows removed from a customer-facing table in a single statement.

    target 15m

    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.

    Show 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."
    }
    Set up this simulation
  • Cascade delete overreach

    One parent row removed; 38 child rows disappear with it, across two tables.

    target 30m

    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.

    Show 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."
    }
    Set up this simulation

Export & backup

  • Export job silent failure

    Nightly export writes a 0-byte file and exits 0.

    target 60m

    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.

    Show 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."
    }
    Set up this simulation
  • Backup window passes with no run

    The scheduled backup produces no record at all — no success, no failure.

    target 120m

    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.

    Show 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."
    }
    Set up this simulation

Dependency outage

  • Third-party API returns 500s

    A dependency answers 500 for ten minutes; requests fail behind a retry layer.

    target 10m

    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.

    Show 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."
    }
    Set up this simulation
  • Outbound webhook queue backs up

    Queue depth climbs to 12,400 and the oldest message is 46 minutes old.

    target 30m

    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.

    Show 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."
    }
    Set up this simulation