Skip to content
SeeItFlow
System Design · 8 chapters · ~14 min read

Web Crawler Failure and Scale Scenarios: 31 Incidents

What actually goes wrong in a production web crawler, and how is each failure detected, contained and recovered? This page walks 31 concrete incidents in the Atlas Discovery design, from URL traps and robots.txt changes through publisher overload, crashed workers and expired leases, SSRF and decompression bombs, false duplicates and stale validators to region split-brain, a two-hour storage outage with its backlog arithmetic and an opt-out deletion workflow. Each incident uses the same eight fields so failures can be compared.

Mangesh MandlikLast updated: How lessons are made
Chapter 01

The Healthy Path, and How to Read the Incidents

A healthy crawl is dull: a URL is discovered, normalized, approved, scheduled, leased, fetched, parsed, deduplicated, stored and announced, with every state change carrying a reason code. This page takes each stage and breaks it with a concrete incident, using the same eight-point template every time so you can compare them and see how one failure type shares its fix with another.

Swipe to pan
DG09 — Durable frontier state machine with lease expiryDG09 — Durable frontier state machine with lease expiryEvery URL row is in exactly one stateDISCOVEREDscope + robots okELIGIBLEclaimLEASEDlease + fencecommitFETCHEDOther exitsFAILED_RETRYABLEbackoffBLOCKEDrobots / scopeTERMINAL404 · max attemptsLease expiryLEASED past lease_expires_at → ELIGIBLE, attempt_count kept.Why a tableTime-ordered lookup, priority, unique key dedupe and retrycounts.
The healthy path through the URL states. Incidents below leave it at a specific edge.
Template fieldQuestion answered
Initiating eventWhat went wrong first?
TimelineHow does it unfold, with times?
Affected stateWhich rows, buckets or stores are wrong?
DetectionWhich signal or check notices?
ContainmentHow is damage limited right now?
RecoveryHow does the system return to correct?
Idempotency boundaryWhich step is safe to repeat and which is not?
PreventionWhat change stops a repeat?
Every incident uses these eight fields.

Incidents are grouped by the stage that fails. The Atlas Discovery crawler, the three origins and the numbers match the other Atlas pages. Capacity facts: about 116 requests in flight on average, 144.68 attempts per second, 10 million successes per day. Incident numbers F01–F30 are stable references used by the interview page. Opt-out and deletion handling is covered separately as F31 because it is an operations and policy scenario.

Chapter 02

Identity and Scope Failures

The frontier is only as good as its idea of what a page is.

Swipe to pan
DG04 — Calendar and tracking explosion, bounded by scopeDG04 — Calendar and tracking explosion, bounded by scopeDistinct pages stay small; rejected variants are counted with a reasonNaive visited set40,000 entriesAfter normalization≈ 9,000 (calendar remains)After scope guard312 admittedScope rulesAllowed hosts, depth limit, per-pattern value cap, paginationcap.AuditEvery rejection stores a reason: calendar capped at 30 values.
Normalization and scope guard shrink a trap's queue to a bounded set.

F01 · A calendar generates millions of URLs

AspectWhat happens
Initiating event/calendar?day=N links to the next day forever
TimelineOver six hours the queue grows by 40,000 entries per hour for one origin
Affected stateQueue rows for one origin outnumber real pages 100 to 1
DetectionDistinct-values-per-query-key and per-origin URL counts spike; admission rejects rising
ContainmentPer-origin URL budget and cap on distinct values for the day key; reason code trap_budget
RecoveryDelete or park rows over budget; keep a sample for operators; raise the cap only per origin if justified
Idempotency boundaryInsert-if-absent keeps re-discovery harmless
PreventionScope guard with depth, per-origin budget and key-value caps; alert before the cap

F02 · Two distinct case-sensitive paths merged

AspectWhat happens
Initiating eventOver-eager rules lowercase every path
TimelineDay 0 a rule ships; day 1 docs-b pages vanish from search; day 3 an editor notices; day 3 rule disabled and replay starts
Affected state/Guide/Setup and /guide/setup become one identity; one page is never fetched
DetectionPage count for docs-b falls below the sitemap count; users report missing pages
ContainmentDisable the rule for that origin; stop merging new URLs
RecoveryReplay the raw discovered URLs with corrected rules (kept in raw_discovered_url)
Idempotency boundaryRe-inserting by corrected identity is safe
PreventionConservative identity, per-origin rules, audit of rules fired, canary comparison to sitemaps

F03 · Two threads discover one URL

AspectWhat happens
Initiating eventBoth parse pages linking to U-106 at the same moment
Timelinet=0 both threads parse pages linking to U-106; t=2 ms both check the set; both enqueue
Affected stateAn in-memory set lets both see 'new' and both enqueue it
DetectionDuplicate-attempt rate per URL
ContainmentUnique key on normalized URL
RecoveryDedupe duplicate rows; no data lost
Idempotency boundaryThe insert is atomic: one wins, the other sees 'exists'
PreventionFirst-seen decided only by the database, never by a process-local set

F04 · Redirect loop and cross-origin redirect

AspectWhat happens
Initiating eventdocs-b redirects /a → /b → /a; another URL redirects to a third-party host
Timelinet=0 /a → 301 /b; t=1 s /b → 301 /a; after five hops or a repeated URL the chain stops
Affected stateHops accumulate until the cap of five; or the target host is unchecked
DetectionHop-count histogram; redirects to unfamiliar origins
ContainmentStop at five hops or a repeated normalized URL; treat a new origin as a new candidate
RecoveryMark FAILED with redirect_loop; the new origin passes its own robots and bucket
Idempotency boundaryEach hop is a separate state record
PreventionHop limit, repeated-URL memory, full gate re-evaluation per hop
Chapter 03

Robots and Policy Failures

Policy is data that changes underneath queued work.

Swipe to pan
DG27 — robots rule changes while a URL is queuedDG27 — robots rule changes while a URL is queuedRecheck the cached policy version immediately before fetchingFrontierRobots cacheWorkerstore-cU-108 admitted (policy v3)robots.txt now Disallow /products/refresh → policy v4pre-fetch recheck U-108v4: BLOCKEDstate := BLOCKED, no connectionRulePolicy checks are not one-time admission; they recur at fetch time and at redirects.
A rule change while URLs are queued: recheck blocks without connecting.

F05 · Robots disallow added while URLs are queued

AspectWhat happens
Initiating eventstore-c adds Disallow: /products/ at 10:00
Timeline10:00 file refetched (version 4); 10:02 claim sees U-108 verdict at version 3
Affected stateQueued product URLs with stale verdicts
Detectionpolicy_version_mismatch counter
ContainmentRecheck at claim time; block U-108 before any connection
RecoveryRe-admit if a later rule allows it
Idempotency boundaryThe block is a state change, repeatable
PreventionVersion on every verdict; short robots cache; recheck after redirects

F06 · Robots returns 404, 503 or times out

AspectWhat happens
Initiating eventOne origin returns three different failures on three days
TimelineMon robots 404 (crawl); Tue 503 for 20 minutes (pause, last good copy); Wed timeout (pause, back off)
Affected state404: no restrictions published; 503 and timeouts: origin unreachable
DetectionRobots status counters by class
Containment404: crawl normally; 5xx/timeouts: pause, use last good copy for a bounded time, back off
RecoveryOn recovery, refetch and clear the pause
Idempotency boundaryRefetching robots is safe
PreventionStandards-based semantics; the conservative choice for unreachable; documented operator override

F07 · Wildcard and longest-match edge

AspectWhat happens
Initiating eventAllow: /account/help/ against Disallow: /account/; a buggy matcher picks the first rule
TimelineDeploy at 09:00; 09:05 a path under /account/help/ is wrongly blocked or fetched; audit at 10:00 finds it
Affected stateA path under /account/ is fetched or wrongly blocked
DetectionParser unit tests; blocked-versus-allowed spot audits
ContainmentDisable the parser change; replay checks over recent fetches
RecoveryRe-evaluate and tombstone documents that should not have been fetched
Idempotency boundaryRe-evaluation is deterministic
PreventionTable-driven tests from the standard's examples; longest match with Allow winning ties
Chapter 04

Politeness and Scheduling Failures

Fairness and rate limits are enforced in shared state; they fail when it is bypassed or unavailable.

Swipe to pan
DG19 — Origin rate, concurrency cap and Retry-AfterDG19 — Origin rate, concurrency cap and Retry-Afternews-a answers 429 at t=10 s with Retry-After: 600 s10 s40 s70 s80 snews-a requests429retryretrybucket stateeligible every 5 sbackoff: all news-a URLs waitjittered429Retry-After endsRuleBackoff belongs to the origin bucket: every worker and region obeys it.
429 with Retry-After: the whole origin cools.

F08 · Two hundred workers sleep and fire together

AspectWhat happens
Initiating eventWorkers each wait five seconds without a shared clock
Timelinet=0 200 workers wake together; t=0.2 s news-a answers 429 to most; t=1 s origin enters backoff
Affected stateAt t=0 all 200 send to news-a; 429 follows
DetectionRequests per origin per second above its cap
ContainmentOrigin bucket claim advances the clock atomically; others get other origins
RecoveryOrigin enters backoff
Idempotency boundaryThe claim is atomic; retrying the claim is safe
PreventionShared per-origin slot reservation; no per-worker sleeps

F09 · 429 with Retry-After: 60

AspectWhat happens
Initiating eventW-2 receives 429 at t=10 s
Timelinet=10 s 429 with Retry-After: 60; t=10 s bucket sets backoff_until=70 s; t=70 s plus jitter resumes
Affected stateAll of news-a's queued URLs wait until t=70 s plus jitter
Detection429 share per origin
Containmentbackoff_until set on the origin, not the URL
RecoveryResume with jittered release
Idempotency boundaryState updates are idempotent
PreventionOrigin-wide backoff; honor the header exactly

F10 · Repeated 503s with no Retry-After

AspectWhat happens
Initiating eventdocs-b degrades under load
Timelinet=0 503; t=10 s retry 503; t=30 s retry 503; delays 10, 20, 40 s up to a 15-minute cap, then probe
Affected stateDelays grow 10 s, 20 s, 40 s … capped at 15 min
Detection5xx rate and latency per origin
ContainmentExponential backoff, jitter, circuit cooling
RecoveryProbe with one request; ramp slowly
Idempotency boundaryBackoff state is derived from counters
PreventionCap, jitter and a probing recovery
AspectWhat happens
Initiating eventnews-a has four million high-priority URLs
TimelineHour 0 news-a backlog arrives; by hour 2 small origins wait 90 minutes; alert fires; quotas apply
Affected stateSmall origins' wait rises to hours
DetectionOldest-eligible age by origin class
ContainmentPer-class quotas and aging
RecoveryStarved origins served within the starvation cap
Idempotency boundaryRescheduling is idempotent
PreventionOrigin-first scheduling; quotas; starvation alert
Swipe to pan
DG24 — Per-origin fairness vs popular-domain starvationDG24 — Per-origin fairness vs popular-domain starvationShare of fetch slots over an hour (illustrative)Flat priority: news-a92% of attempts (mostly waiting)Flat priority: docs-b6%Fair: news-a≤ 25% quotaFair: docs-bby eligibility + agingMechanismsPer-origin ready buckets, aging, per-class quotas, starvation cap.
Fair share protects small origins from a dominant publisher.

F12 · Scheduler dies after reserving a slot

AspectWhat happens
Initiating eventSlot reserved at t=0; process crashes before dispatch
Timelinet=0 slot reserved; t=0.1 s scheduler crash; t=30 s reservation expires; sweeper releases it
Affected stateThe origin looks busy: in_flight is 1, next eligible 5 s
Detectionin_flight stuck beyond lease time
ContainmentReservation carries an expiry; sweeper
RecoverySweeper releases the reservation; clock is not rewound (conservative)
Idempotency boundaryReservation rows are idempotent
PreventionShort reservation expiry; sweeper
Chapter 05

Worker, Lease and Storage Failures

These incidents show the guarantee the crawler can honestly make: at-least-once attempts, idempotent results.

Swipe to pan
DG21 — Worker crash after the HTTP response, before commitDG21 — Worker crash after the HTTP response, before commitRecovery repeats the fetch; the stored result is idempotentW-2FrontierW-3news-alease U-104 (fence 7)GET200crash before commitlease expires → ELIGIBLElease (fence 8)GET (second request)commit fence 8Honest guaranteeAt-least-once fetch attempts. Never exactly-once network I/O.Idempotent outputCommit keyed by url_id + fence; replay does not duplicatedocuments.
The worker crashes after the HTTP response and before commit.

F13 · Worker crashes after fetch, before commit

AspectWhat happens
Initiating eventW-2 receives the 200 for U-104 at t=1 s and dies
TimelineLease expires at 60 s; sweeper returns the row
Affected stateU-104 stays LEASED then ELIGIBLE with attempts=1
DetectionLease expiry counter
ContainmentSweeper requeues the URL; another worker fetches it, so the publisher sees a second request
RecoveryThe second fetch commits normally; the duplicate request is honest, visible and counted
Idempotency boundaryCommit is fence-checked; body is content-addressed
PreventionShort leases; do not promise exactly-once
Swipe to pan
DG22 — Stale lease commit and fence rejectionDG22 — Stale lease commit and fence rejectionA slow worker is not a dead workerW-2 (fence 7)Frontier / storeW-3 (fence 8)lease (fence 7)pause / slow networklease expireslease (fence 8)commit fence 8 ✓commit fence 7 …REJECTED: current fence is 8Why tokensClocks and pauses lie; a monotonically increasing token does not.
A late commit with fence 7 is rejected.

F14 · A slow fetch outlives its lease

AspectWhat happens
Initiating eventW-2's fetch stalls for 70 s, longer than its 60 s lease
Timelinet=0 W-2 leases U-104 (fence 7); t=60 s lease expires; t=61 s W-3 leases (fence 8) and commits; t=70 s W-2 tries fence 7
Affected stateU-104 now belongs to fence 8; W-2 holds a stale result that must not overwrite it
DetectionStale-writer rejections
ContainmentReject stale commit; discard result
RecoveryNone needed
Idempotency boundaryFence compare-and-set
PreventionFencing tokens; lease extensions only with the current fence
AspectWhat happens
Initiating eventDocument row written; the links batch fails
Timelinet=0 fetch ok; t=1 s document row committed; t=1.1 s links batch fails; no retry path without a record
Affected stateThe document exists, its outlinks never reach the frontier
DetectionLink count mismatch versus parsed output
ContainmentWrite links in the same transaction or via outbox
RecoveryReplay extraction from the stored body
Idempotency boundaryInsert-if-absent for links; document upsert by hash
PreventionSame-transaction link writes or outbox
Swipe to pan
DG20 — Raw store, metadata DB and outbox: where atomicity endsDG20 — Raw store, metadata DB and outbox: where atomicity endsNo single ACID transaction spans HTTP, object storage and the broker1 Object storeput body (idempotent)body key2 One DB transactionstate + doc + outboxoutbox3 Relaypublish, may repeatevent4 Indexerdedupes by event idCrash after 1Orphan body is harmless; a sweepercollects unreferenced bodies.Crash after 2Relay resumes from the outbox; no event islost.NeverPublish before the transaction commits.
Atomicity ends at the database transaction.

F16 · Publish succeeds but commit fails, or the reverse

AspectWhat happens
Initiating eventEvent sent, then the database errors; or the commit succeeds and publishing fails
TimelineOrder A: t=0 publish ok; t=0.1 s commit fails. Order B: t=0 commit ok; t=0.1 s publish fails
Affected stateIndex points at a missing document; or a document is never announced
DetectionOutbox backlog; index-versus-document reconciliation
ContainmentOutbox pattern: event exists only if the row exists
RecoveryRelay resends; reconciler compares both sides
Idempotency boundaryConsumer dedupes by event_id
PreventionNever publish before commit; outbox plus idempotent consumer
Chapter 06

Network, Security and Parsing Failures

The crawler reads untrusted data and follows untrusted addresses.

Swipe to pan
DG29 — SSRF gate and DNS rebinding defenseDG29 — SSRF gate and DNS rebinding defenseValidate the address you actually connect toWorkerEgress gateDNSTargetfetch evil-link.example.testresolve10.0.0.5BLOCKED: private rangepublic name: resolve203.0.113.9connect to pinned 203.0.113.9 (TLS verified)Block listPrivate, loopback, link-local, metadata, multicast; IPv4 andIPv6.InsufficientA hostname-only precheck: the second lookup can differ.
Only the validated address may be connected to.

F17 · DNS rebinding

AspectWhat happens
Initiating eventA name resolves to a public address at validation and to 10.0.0.5 at connect
Timelinet=0 validator resolves to a public IP; t=0.2 s connect-time lookup returns 10.0.0.5
Affected stateIf the client resolves twice, an internal service is reached
DetectionEgress denies to private ranges; logs
ContainmentPin the validated address to the connection; network deny rules
RecoveryRotate any exposed credentials; review logs
Idempotency boundaryConnection retried only after re-validation
PreventionConnect-time validation, pinning, network-layer denies
Swipe to pan
DG13 — Redirects re-run SSRF and robots checks per hopDG13 — Redirects re-run SSRF and robots checks per hopMax 5 hops; a repeated URL ends the chainWorkerPolicy gatenews-acdn-news-ahop 0: checks passGET U-104301 → cdn-news-ahop 1: scheme, IP, scope, robots, slotallowed (or blocked)GET target200Store bothOriginal and final URL, hop count and each decision.Bound it5 hops; loops detected by repeated normalized URL.
Every hop is checked again.

F18 · Redirect to metadata or localhost

AspectWhat happens
Initiating eventA page redirects to http://169.254.169.254/ or localhost
Timelinet=0 U-104 returns 302 to http://169.254.169.254/; t=0 ms the hop is evaluated and refused
Affected stateThe hop is refused
Detectionblocked_network counter
ContainmentRefuse the hop; mark FAILED
RecoveryNone
Idempotency boundaryRefusal changes no external state
PreventionPer-hop scheme and network checks; metadata ranges blocked by network policy

F19 · Decompression bomb

AspectWhat happens
Initiating eventA 40 KB compressed response expands to gigabytes
Timelinet=0 40 KB response arrives; as it expands the reader passes 5 MB and aborts at t=0.3 s
Affected stateThe reader aborts at 5 MB decompressed
DetectionMemory and abort counters
ContainmentStreaming limit on decompressed bytes; parser in a limited process
RecoveryQuarantine the URL
Idempotency boundaryAbort leaves no stored body
PreventionLimit by streamed decompressed bytes, not Content-Length

F20 · HTML label, binary payload

AspectWhat happens
Initiating eventContent-Type: text/html on a PDF or garbage
Timelinet=0 headers say HTML; first bytes show %PDF; sniff rejects at t=0 before parsing
Affected stateSniffing sees a binary signature
DetectionMismatch counter
ContainmentType allowlist; sniff; reject or quarantine
RecoveryReprocess under the right handler if supported
Idempotency boundaryNo document written
PreventionAllowlist plus sniffing; no execution of unknown types
Swipe to pan
DG16 — HTML-only extraction vs sandboxed JavaScript laneDG16 — HTML-only extraction vs sandboxed JavaScript laneRender only where measured need existsDefault pathFetch HTML~0.8 s, low CPUParse textDocumentBudgeted lane (store-c products only)Render queueown budgetSandboxed browserCPU/mem/time capsSafe egresssame rulesRendered DOM → parseFallbackIf the lane saturates, fall back to HTML-only and record it.
The render lane is isolated and falls back to raw HTML.

F21 · The render lane exhausts CPU and memory

AspectWhat happens
Initiating eventA script-heavy class floods the browser pool
TimelineHour 0 a script-heavy store-c class is marked render-required; by hour 1 render queue age passes 10 minutes
Affected stateQueue depth rises; browsers evicted
DetectionLane CPU, memory and queue age
ContainmentSeparate queue and limits; fallback to HTML-only with render_skipped
RecoveryScale or tighten limits; re-render later
Idempotency boundaryRender results are keyed by URL and content hash
PreventionIsolation, budgets, fallback

F22 · One broken charset poisons a batch

AspectWhat happens
Initiating eventA parser crash on a malformed page kills a worker repeatedly
Timelinet=0 a page with a broken charset crashes W-4; the batch is redelivered; W-5 crashes the same way
Affected stateThe same batch fails over and over
DetectionCrash loops and quarantine counters
ContainmentPer-page isolation and timeouts; quarantine the URL
RecoveryFix the parser; replay quarantined pages
Idempotency boundaryQuarantine is a state, replay is safe
PreventionParser limits; skip poison pages; canary parsing
Chapter 07

Duplicate and Freshness Failures

Dedupe and conditional requests fail quietly, so they need verification built in.

Swipe to pan
DG15 — Bloom filter false positive and exact verificationDG15 — Bloom filter false positive and exact verificationA 'maybe seen' answer is not a reason to discardDiscoveryBloom filterExact storeseen? U-210NO → definitely new, insertseen? U-211MAYBE (could be false positive)exact lookup U-211not found → insertSizingm ≈ −n·ln(p) / (ln 2)²: 1 billion URLs at p = 1% needs about 9.6billion bits ≈ 1.2 GB.RuleNever drop the only copy of a URL on a filter answer alone.
A 'maybe' is verified in the exact store before any discard.

F23 · A Bloom false positive hides a new URL

AspectWhat happens
Initiating eventThe filter says 'maybe seen' for the only link to a valuable page
Timelinet=0 the filter reports 'maybe' for a new URL; with the bug the URL is dropped; weeks later a gap is found
Affected stateIf the pipeline discards on 'maybe', the page is never fetched
DetectionExact-store confirmation counters; unexplained coverage gaps
ContainmentVerify every 'maybe' in the exact store
RecoveryBackfill pages found missing
Idempotency boundaryInsert is idempotent
PreventionThe filter only avoids lookups; it never discards

F24 · Different URLs, identical content

AspectWhat happens
Initiating eventU-110 /print returns the same text as U-104
Timelinet=0 U-104 stored; t=3 min U-110 /print fetched; the hash equals U-104's; a second URL row links to the document
Affected stateBoth rows reference one document; both provenance links kept
DetectionDedupe ratio per origin
ContainmentHash on normalized text
RecoveryNone
Idempotency boundaryUpsert by content hash
PreventionProvenance table; never drop the second URL

F25 · Near-duplicate merges two real versions

AspectWhat happens
Initiating eventA corrected article joins the cluster of the outdated version
TimelineDay 0 a corrected article arrives; fingerprint distance is below the threshold; it is clustered with the old version
Affected stateUsers see the wrong version first
DetectionCluster review sampling; complaints
ContainmentThreshold recorded; members kept
RecoverySplit the cluster; reprocess
Idempotency boundaryClusters are labels, not deletions
PreventionConservative threshold; audit; reversible clusters
Swipe to pan
DG17 — ETag, conditional GET and 304 vs a changed 200DG17 — ETag, conditional GET and 304 vs a changed 200Validators are hints, not guaranteesRecrawl workernews-aDocument storeGET U-104 · If-None-Match: v1304 Not Modified (~300 B)last_checked := now, reuse bodylater: GET · If-None-Match: v1200 · ETag v2 · full bodyparse + hash differs → new versionStill a request304 saves bytes and parsing; it still uses an origin slot.Weak validatorsETag may change without content change: confirm by hash.
A new ETag is checked by content hash.

F26 · ETag changes with no content change

AspectWhat happens
Initiating eventThe origin generates a fresh ETag per request
TimelineDay 0 origin begins issuing a new ETag per request; every recrawl returns 200; reparse yields the same hash
Affected stateEach recrawl is a 200 that reparses to the same hash
DetectionChanged-page yield near zero while 304 rate falls
ContainmentCompare content hash; stop trusting that origin's validators
RecoveryFall back to hash-based change detection
Idempotency boundaryHash comparison decides change
PreventionPer-origin validator reliability tracking

F27 · A 304 mishandled as missing

AspectWhat happens
Initiating eventCode treats the empty 304 body as a failure
Timelinet=0 recrawl returns 304 with an empty body; a bug marks the document missing
Affected stateDocuments appear deleted
DetectionSudden drop in documents with 304 responses
ContainmentTreat 304 as 'unchanged' and reuse the stored document
RecoveryRestore the affected rows from metadata
Idempotency boundary304 handling is read-only
PreventionLink the validator to the stored document; tests for 304

F28 · A news site speeds up and the schedule goes stale

AspectWhat happens
Initiating eventPublishing frequency doubles
TimelineWeek 0 the site publishes hourly; week 1 it publishes every 20 minutes; intervals lag until re-estimated
Affected stateFreshness gap rises for that origin
DetectionFreshness gap by class
ContainmentBounded fairness; temporary interval cap
RecoveryAdaptive scheduler shortens the interval
Idempotency boundaryInterval updates are derived from history
PreventionClamped intervals; regular re-estimation
Chapter 08

Region and Platform Failures

Scale-out and outages stress the guarantees the design makes.

Swipe to pan
DG26 — Region ownership split-brain and fenced failoverDG26 — Region ownership split-brain and fenced failoverOwnership token for news-a: 41 → 42EU regionControl planeUS regionnews-a bucketlease news-a, token 41partition: renew failslease news-a, token 42dispatch token 41dispatch token 42REJECTED stale tokenBoundOvershoot ≤ lease length × rate, or the pre-leased share.Not claimedPerfect global rate limits across independent regional counters.
Ownership token 41 is rejected once 42 exists.

F29 · Region split-brain

AspectWhat happens
Initiating eventA partition leaves both regions believing they own news-a
Timelinet=0 partition; t=0 to 30 s both regions schedule news-a; t=30 s partition heals; token 42 wins over 41
Affected stateBoth schedule news-a for up to the lease window
DetectionOrigin request rate above cap; token conflicts
ContainmentOwnership token 42 beats 41; the older owner is fenced
RecoveryReassign; reconcile duplicates
Idempotency boundaryFence compare on the bucket
PreventionBounded overshoot by lease length; preleased shares
Swipe to pan
DG30 — Storage outage: backlog growth and drainDG30 — Storage outage: backlog growth and drain200 tasks/s in; 50/s during a 2 h outage; 250/s after02 h5 h8 h10 hOutage phase+150/sdrain at 250 − 200 = 50/s netBacklog→ 1,080,000falls to 0 after 6 hservice backdrainedCaveatIf the backlog sits in a few origins with hard caps, thefleet-wide drain estimate is too optimistic.GuardsBackpressure, bounded spool, shed lowest priority.
The backlog math around a storage outage.

F30 · Multi-hour storage outage

AspectWhat happens
Initiating eventThe central metadata store is unavailable for 2 hours
Timelinet=0 store down; t=0 to 2 h arrivals 200/s, completions 50/s; t=2 h recovery; drain begins at 250/s
Affected stateBacklog = (200 − 50) × 7,200 = 1,080,000 tasks
DetectionQueue age; failed commits; spool occupancy
ContainmentWorkers stop leasing; a bounded spool holds finished results; shed low-priority work
RecoveryAfter recovery at 250 per second against 200 per second of inflow, drain = 1,080,000 ÷ 50 = 21,600 s = 6 h, provided origin budgets allow it; a backlog concentrated in capped origins drains slower
Idempotency boundarySpooled commits replay by fence and hash; reattempt storms are jittered
PreventionSpool limits, shedding rules, retry jitter, drills

The fleet-wide estimate assumes the backlog is spread evenly over many origins. If most queued work belongs to a few origins that are rate-capped, the true drain time is set by those caps (for news-a at one request per 5 s, one million URLs would need about 58 days), not by fleet capacity. Always compute the drain per bottleneck origin before promising a recovery time.

Opt-out and deletion requests (F31)

Swipe to pan
DG31 — Opt-out and deletion request lifecycleDG31 — Opt-out and deletion request lifecycleDistinct data classes have distinct retentionRequest verifiedorigin or ownerSuppression listblocks new fetchesDelete / tombstonedocs, bodies, indexAudit recordwhat, when, by whomData classesURL rows, fetch metadata, parsed content, raw bytes, outlinks,robots copies each have their own retention.Not silentNo record is retained forever by default.
Suppression, deletion workflow and audit.

F31 · A verified opt-out and deletion request

AspectWhat happens
Initiating eventA publisher asks that its pages be removed
TimelineDay 0 verified request received; day 0 suppression; day 1 to 3 deletions run; day 3 audit confirms
Affected stateFrontier rows, documents, raw bodies and index entries for that origin
DetectionRows and index entries still present after the deadline
ContainmentSuppression blocks new fetches immediately
RecoveryDelete or tombstone each data class; audit trail
Idempotency boundaryDeletion is repeatable by key
PreventionPer-class retention, deletion map, suppression record kept
Key takeaways
  1. 01Most failures are quiet: wrong merges, stale validators and lost links produce no errors.
  2. 02At-least-once attempts plus idempotent commits are the honest guarantee.
  3. 03Every shared limit lives in shared state; per-worker sleeps cannot enforce it.
  4. 04Security failures are stopped at connect time, per hop and by bounded streaming reads.
  5. 05Regional overshoot is bounded by the lease window.
  6. 06Recovery estimates must be computed per bottleneck origin.