Distributed Search Engine — Masterclass
Three additional artifacts a Staff/Principal candidate should be able to produce for this problem: an Architecture Decision Record, a business-driven design exercise, and a production incident scenario.
1. Architecture Decision Record
The format working architects use to document a decision so future teams understand context, options, and reversal conditions.
Sharded inverted-index (Elasticsearch on Lucene) with 30 GB shards, RF=2, dedicated coordinator nodes + adaptive replica selection + hedged requests, 30 GB JVM heap cap (compressed oops), and cross-cluster search for global federation — over managed Algolia, self-hosted Solr, Meilisearch, or Postgres full-text search.
- 500M-2B documents; ~40 KB avg _source; 20-80 TB primary storage (before replication)
- 8-20K QPS peak (Black Friday: 60K QPS spikes)
- p99 latency: <100ms for keyword; <200ms for kNN vector; <500ms for facet-heavy
- Fresh-write visibility: 1s for catalog (default refresh); 30s for logs (throughput priority)
- Availability: 99.95% (search is user-facing but degradable to cache + featured items on failure)
- Multi-region: US-East primary; EU + APAC read-followers (CCR-replicated)
- Cost bound: $75K/mo at scale (year-2); linear scale w/ corpus size
- Team: 4 engineers, 1 with prior Elasticsearch ops experience — this is the critical constraint on managed-vs-self-hosted
Elasticsearch/OpenSearch self-hosted + Lucene + hedged requests + CCR (chosen)
- Full control over shard sizing (target 30 GB — the compressed-oops sweet spot)
- Adaptive replica selection + hedged requests reduce p99 by 30-70% (Dean & Barroso Tail-at-Scale)
- Ingest pipelines for enrichment (PII redact, language detect, embed generation) run at index-time
- Cross-cluster search (CCS) for multi-region federation without full data replication
- Vector kNN via HNSW indexes (native since ES 8.0) — same cluster as BM25, single query interface
- Battle-tested at hyperscale: Uber 8 PB, Netflix 40 PB, GitHub, Slack all run OSS Elasticsearch/OpenSearch
- Requires 1-2 dedicated ops engineers for shard rebalancing, JVM tuning, heap sizing, upgrades
- License uncertainty (Elastic License 2.0 vs OpenSearch/Apache 2.0) — pin to OpenSearch or ES 7.10 for long-term dependency
- Cold-shard 'first query' latency: 500ms-2s after node restart until file cache warms
- JVM GC pauses cause tail-at-scale amplification even with G1GC/ZGC tuning
Algolia (managed SaaS search)
- Zero ops — Algolia handles sharding, replication, scaling
- Sub-10ms p99 latency (highly optimized C++ engine)
- Excellent DX: instant search UI kits, dashboard analytics
- Cost: $1.50-$2.50 per 1000 search operations → $25K-$40K/mo at 2K QPS avg, scales BADLY past that
- At 2B documents Algolia becomes $150K-$300K/mo (records + operations both metered)
- Limited flexibility: no custom analyzers beyond built-ins, no vector kNN as of 2024, no arbitrary aggregations
- Vendor lock-in: re-indexing 2B docs elsewhere = 2-3 months of work + double-run cost
- Data residency: US + EU regions only — no APAC-native (200ms+ RTT from APAC users)
Solr (self-hosted, Cloud mode)
- Also Lucene-based — same underlying inverted index and BM25 semantics
- Better for facet-heavy workloads (Solr's facet engine outperforms ES for high-cardinality facets)
- Fully Apache 2.0 licensed — no ambiguity
- Weaker distributed story than ES — ZooKeeper dependency, shard rebalancing is manual
- Smaller community + ecosystem — Kibana/OpenSearch Dashboards has no Solr equivalent at parity
- Native vector kNN added late (Solr 9.0, 2022) — HNSW performance still behind ES/OpenSearch
Postgres full-text search (tsvector + GIN)
- Zero new infrastructure — reuse existing Postgres cluster
- Transactional consistency — search results reflect writes IMMEDIATELY (no refresh interval)
- SQL-native — join search results with any other relational data trivially
- Ceiling: ~1-10M documents on a single Postgres node before GIN rebuilds become impractical
- BM25 is not native (Postgres uses simpler ts_rank / ts_rank_cd — weaker relevance)
- No sharding for search-specific workload — general Postgres sharding (Citus) helps but is complex
- No vector kNN natively (pgvector helps but is separate index; combining full-text + vector is awkward)
- Kills OLTP workload if search QPS grows — search becomes 40% of CPU under load
Meilisearch (Rust, single-node)
- Excellent DX and typo-tolerance out of the box
- Very fast on small-to-medium corpuses (<100M docs, <5K QPS)
- Simple to operate — single binary, no JVM tuning
- Not truly distributed until v1.x + Cloud offering — single-node write model in OSS
- 500M-2B document corpus is beyond current Meilisearch operational envelope
- Ranking model is proprietary — hard to tune or reason about
- Small ecosystem — no Kibana equivalent, limited monitoring tooling
Elasticsearch/OpenSearch self-hosted with dedicated coord role + hedged requests + CCR
- Algolia — non-linear cost trajectory becomes $150K-$300K/mo at 2B docs + 20K QPS
- Solr — weaker distributed operations story; vector kNN added late
- Postgres full-text search — 10M-doc ceiling, no BM25, kills OLTP under load
- Meilisearch — 100M-doc operational envelope, not distributed at OSS tier
- Accept 1-2 engineer FTE ops overhead — shard sizing, JVM tuning, upgrades, incident response
- Accept G1GC/ZGC tuning is recurring work — heap sizes, region size, pause target
- Accept cold-cache latency (500ms-2s) after node restart or shard migration
- Accept cluster-wide operations (mapping updates, rebalance) can trigger yellow states — plan runbooks
- Accept licensing risk (Elastic License 2.0 vs Apache 2.0) — pin to OpenSearch for future-proofing
- Full control over shard sizing (target 30 GB) and JVM heap (30 GB compressed-oops boundary)
- Adaptive replica selection + hedged requests give us Dean & Barroso Tail-at-Scale wins
- Ingest pipelines centralize enrichment logic — one place for PII redact, language detect, embedding
- Vector kNN + BM25 in the same cluster and query — semantic + keyword search unified
- Cross-cluster search enables multi-region reads without full data replication cost
- 40% cost savings vs Algolia at 2B docs — capital reinvested in ML relevance model training
- Dedicated Search SRE role emerges within 12 months — this becomes a first-class discipline
- Ops overhead exceeds 40% of one FTE for 2+ consecutive quarters → managed offering (Elastic Cloud, AWS OpenSearch Service) becomes worth its 30-50% premium
- Corpus growth exceeds 5B docs AND query mix becomes facet-dominated → evaluate Solr or split into per-domain clusters
- Semantic search becomes 60%+ of queries → dedicated vector DB (Pinecone, Weaviate, Qdrant) may outperform ES HNSW at that mix
- Team drops below 3 engineers → cannot maintain self-hosted; migrate to managed offering or Algolia
2. Business constraint exercise
Given real-world constraints (team size, budget, deadline), what architecture do you propose — and how do you push back when leadership asks for the wrong thing? This teaches engineering judgment.
You are engineer #4 at a 3-year-old B2C marketplace startup (250 engineers). CTO drops in Monday: 'Search is our #1 customer complaint. We built it on Postgres full-text 4 years ago; now we have 180M product listings and search takes 3-8 seconds. Board approved $500K annual budget for a search rebuild. Options I'm hearing: (1) buy Algolia for $200K/year, (2) buy Elastic Cloud managed for $180K/year, (3) hire 2 engineers and self-host OpenSearch for ~$220K/year total. Timeline: must ship v1 in 4 months. CTO wants Algolia because he can wire it up in 2 weeks. Which do we pick and how do you defend it to the board?'
- 112M MAU, 180M products indexed, 15K search QPS peak, 4-8K sustained
- 25.4 TB primary corpus (180M docs × 30 KB avg _source); 15 TB with 2x replication
- 33 backend engineers on search team; ~40 engineers overall
- 4v1 must ship in 4 months (Q1 2026) to hit board's roadmap commitment
- 5$500K/year approved — compute + tooling + prorated engineer salaries
- 6Existing stack: Postgres 14 (main DB), Rails monolith, Go services, AWS us-east-1 primary
- 7CTO preference: Algolia (fast to ship, zero ops)
- 8Growth trajectory: expected 12K sustained QPS in 18 months
What's your recommendation to the board? Address (a) the real 3-year cost of Algolia at growth trajectory, (b) whether the 4-month timeline is realistic for self-hosted, (c) whether a 3-engineer team can operationally support self-hosted OpenSearch, and (d) present a sequenced strategy that threads the timeline pressure without locking in unfavorable long-term economics.
3. Production incident scenario
You are on-call at 3:47am. p99 has spiked. Walk through the investigation, hypothesis, mitigation, and postmortem. This teaches real production reasoning — not just design.
PagerDuty alert at 11:50am ET on Black Friday. Search QPS is at 55K (3.3x normal 17K). Coordinator node ES-COORD-2 just threw OutOfMemoryError and went offline. ES-COORD-1 heap is climbing (27 GB / 30 GB). p99 search latency has spiked from 45ms to 4.2s in 3 minutes. Customer-facing catalog is timing out. Marketing is texting the on-call.
- search.qps: 55K (baseline 17K — 3.3x Black Friday multiplier)
- es.coord2.heap_percent: 100% → OOM at 11:50:00 (was 65% at 11:47)
- es.coord1.heap_percent: 90% climbing (was 60% at 11:47)
- es.coord3.heap_percent: 75% climbing
- search.p99_latency_ms: 4200 (baseline 45)
- search.p99_latency_ms.by_query_type.aggregation: 6800 (WORST offender)
- search.p99_latency_ms.by_query_type.keyword_bm25: 380
- es.data_nodes.cpu_percent: 55% average (NOT saturated — data plane is fine)
- es.data_nodes.heap_percent: 45% average (NOT saturated)
- es.aggregation.max_buckets_setting: 65535 (cluster default, never adjusted)
- elb.5xx_rate: 12% (was 0.02% baseline)
- affiliate_dashboard.top_terms_query_qps: 8 (baseline 0.5 — sudden spike from partner integration)
- 11:47:12 ES-COORD-2 slow_log: 'query took 2145ms, size=50000, terms aggregation, index=catalog'
- 11:48:31 ES-COORD-2 slow_log: 'query took 3892ms, size=50000, terms aggregation, index=catalog' (concurrent)
- 11:49:45 ES-COORD-2 GC log: 'G1 Concurrent Cycle 4234ms' (usually <200ms — heap pressure)
- 11:50:03 ES-COORD-2 log: 'OutOfMemoryError: Java heap space during query reduce phase'
- 11:50:04 ES-COORD-2 log: 'JVM exit signaled by systemd (auto-restart in 90s)'
- 11:51:12 ES-COORD-1 GC log: 'G1 Concurrent Cycle 2891ms' (climbing heap)
- 11:52:00 ES-COORD-1 log: 'circuit_breaking_exception: parent circuit breaker tripped, request rejected'
- affiliate-api access log: 'partner_id=aff_47 issued 3 queries with size=50000 terms aggregation between 11:47-11:50'
- Data plane (12 data nodes): HEALTHY — CPU 55%, heap 45%, disk I/O <60%
- Master quorum (3 dedicated master nodes): HEALTHY — cluster state stable
- ELB: HEALTHY at LB layer; 5xx spike is from ES cluster returning 503s
- Redis (result cache in front of ES): HEALTHY — 95% hit rate on repeat queries
- Postgres (source-of-truth for reindex): HEALTHY — no read pressure from search
- Affiliate partner API: 3 clients issuing high-cardinality aggregation queries (root cause candidate)
It's 11:50am and PagerDuty is firing. What's the SINGLE metric you check first to distinguish 'cluster overloaded' from 'JVM heap exhaustion on coord' from 'downstream dependency failure'?
You've confirmed it's JVM heap exhaustion on coord nodes. Give your top 3 hypotheses for WHY, ranked by likelihood given the metrics + logs.
Design a 60-second mitigation you can execute WITHOUT rolling deploys. Constraint: search MUST come back online in 90 seconds. What's your action?
Postmortem the next day. Name 3 action items ranked by impact, with 'expected reduction in incident probability' quantified for each.
Draft the customer-facing status page update at T+8 minutes (when search recovers first time). Constraint: honest, no jargon, no over-promising.
Learn these first
- Inverted index + BM25 scoring fundamentals
- Scatter-gather query execution + coordinator role
- Dean & Barroso Tail-at-Scale + hedged requests
- JVM heap sizing (30 GB compressed-oops boundary)
Where this appears in the curriculum
This is the Gold Standard.
Every other system will eventually have a masterclass tab like this one. The pattern proven here — ADR + business exercise + incident scenario — scales to all 50+ problems on the platform.