Layered Architecture
Traditional n-tier architecture pattern
A distributed crawler that discovers, fetches, and extracts text from 10 billion web pages within 5 days while respecting robots.txt and never losing progress.
Rough, order-of-magnitude numbers. The point is to justify the architecture, not to be exact.
A diagram shows what exists; a trace shows what happens. Press play on a flow, or click any step, and the diagram above lights up the exact path that request takes.
From dequeuing a URL to the raw page being durably stored.
From a fetched-page reference to newly discovered URLs re-entering the crawl.
How the pipeline survives a bad domain without losing the rest of the crawl.
Every box on the canvas, and the job it does. Click a card to locate it in the diagram; click the node itself for the full deep dive.
This design models a distributed web crawler that discovers, fetches, and extracts text from 10 billion pages in under 5 days, without losing progress and without overloading the servers it crawls.
Pipelined stages, not a monolithic worker. Fetching and parsing are split across two queues — the Frontier Queue and the Further Processing Queue — so a failure or slowdown in one stage never forces redoing the other. Fetching is I/O-bound and bandwidth-expensive; parsing is CPU-bound and cheap to redo. Keeping them independent lets each scale on its own bottleneck.
Politeness is a shared, not per-worker, constraint. A Redis-backed per-domain lock and sliding-window counter ensure the entire fleet — not just one thread — respects each domain's crawl-delay. Robots.txt rules are cached in the same layer so the hot path never blocks on a database read.
Two-tier deduplication. A Bloom filter rejects the overwhelming majority of already-seen URLs in O(1) before they ever reach the Metadata DB, while a content hash computed after each fetch catches pages that are byte-identical under different URLs — mirrors, tracking-parameter variants, and syndicated content.
The frontier is a loop, not a line. New URLs discovered during parsing flow back through the Dedup Filter and re-enter the Frontier Queue, expanding the crawl outward from the original seed list until either the frontier drains or the 5-day budget runs out.
Storage is split by purpose. Raw HTML and extracted text live in separate locations. Keeping the raw HTML means extraction logic can be improved and re-run against the whole corpus later without re-paying the bandwidth cost of re-fetching 10 billion pages.
ApproximateReceiveCount), messages move to a Dead Letter Queue instead of retrying forever.An architecture is a record of trade-offs. For every major choice here: what won, what lost, and why the constraints made it so.
The dominant failure mode in a 5-day, 10B-page crawl isn't total system failure — it's individual fetches timing out against slow or dead servers. SQS gives visibility timeouts, ApproximateReceiveCount-driven backoff, and dead-letter routing for free. Kafka would require building retry and DLQ semantics by hand on top of consumer offsets, which is exactly the kind of infrastructure work this deadline can't absorb.
A monolithic worker means any bug in text extraction forces the fetch to be redone too, wasting the exact bandwidth that is this system's scarcest resource (~23K pages/s required). By splitting fetch from parse across two queues, a parser crash only re-runs cheap local computation against HTML that's already safely in blob storage.
With ~8 fetcher hosts running many threads each, multiple workers can independently dequeue URLs from the same popular domain. A per-thread delay only bounds that thread's request rate; it does nothing to stop the fleet collectively exceeding 1 req/s against one domain. Politeness is a property of the whole system, so it needs shared state.
At 10B+ discovered link candidates, hitting DynamoDB for every single one to ask 'have we seen this?' would dominate the DB's read capacity. A Bloom filter answers 'definitely new' in O(1) from memory and rejects the vast majority of candidates — mostly re-discovered links to already-crawled pages — without a network round trip. The DB is only consulted on the rare positive match, where its false-positive tolerance costs nothing but an extra read.
Session IDs, calendar widgets, and faceted search filters generate effectively infinite unique URLs down a single path — URL dedup alone never triggers because every link really is new. Tracking depth from the nearest seed and cutting off a branch past a threshold bounds the worst case cheaply, without needing to detect the trap pattern itself.
Storage is cheap; the ~2MB average page fetched over the wire is the expensive resource. Keeping raw HTML around means text-extraction logic can be improved and re-run against the entire 10B-page corpus later without re-crawling a single URL — a rebuild that would otherwise cost another 5-day crawl.
Reading a system means sensing where it cracks under 10× load. These are the pressure points of this design, and how it holds.
DNS lookups have historically consumed up to 70% of a naive crawler thread's elapsed time, since every new domain requires a fresh resolution before the first byte can be fetched.
Each fetcher host maintains a local DNS cache, and lookups are round-robined across multiple upstream resolvers so no single resolver becomes the fleet's shared chokepoint.
Many pages across the web link to the same large domain (e.g. a major news site), so a burst of same-domain URLs can land in the Frontier Queue together and race for the same per-domain lock in the Rate Limiter.
The Rate Limiter defers contended fetches via SQS's ChangeMessageVisibility, with jitter added to the delay so deferred workers don't all retry in the same instant and re-create the collision.
Dynamically generated pages — calendar widgets, session-id-bearing links, faceted search — can produce an effectively unbounded number of unique URLs down a single branch, each one passing URL-level dedup because it genuinely is new.
Every URL carries a depth value in the Metadata DB. Branches past 15–20 hops from the nearest seed are simply never enqueued, bounding the worst case without needing to detect the trap pattern itself.
Fetching is I/O-bound and cheap to parallelize; parsing is CPU-heavier. If the parser fleet can't keep pace, the Further Processing Queue backs up and raw HTML accumulates in blob storage faster than it's consumed.
Parser workers autoscale directly on Further Processing Queue depth rather than running a fixed fleet size, so parse capacity grows automatically under sustained fetch pressure.
Every fetch against a popular domain would otherwise trigger a robots.txt / crawl-delay lookup against the same Domain table row, concentrating read load on a handful of partition keys.
The Rate Limiter caches each domain's robots.txt rules and crawl-delay in Redis with a periodic refresh, so the hot read path never touches the Metadata DB per-request.
If you can answer these without scrolling back up, the architecture is yours. Try each one out loud before revealing.
Every great system starts as a sketch on a whiteboard. The ability to zoom out and see the whole picture (how services connect, where data flows, what breaks and why) is what separates engineers who build features from engineers who build systems. This diagram is more than boxes and arrows. It's a map of decisions, trade-offs, and intentional design.
Studying architectures isn't just about passing interviews. It's about training your intuition. The more systems you take apart, the better you get at sensing where a monolith will crack, where a queue belongs, or when a cache is hiding a deeper problem. You start seeing patterns instead of chaos.
So keep reading, keep tracing those edges, keep asking "why this way and not that way." The engineers who truly understand large systems are the ones who never stop being curious about how things fit together. That curiosity is the only ingredient that really matters.
Atharva Arbat@arbat_atharva
Traditional n-tier architecture pattern
A server-side rate limiting system that protects APIs from abuse using sliding window counters and Redis-backed distributed state.
A ranked prefix-completion system that returns the top-K suggestions within the keystroke latency budget using an in-memory precomputed trie and an offline popularity build pipeline.