SYSTEM_DESIGN
System Design: Faceted Search (E-Commerce Style)
Design a faceted search system that powers e-commerce product filtering by category, price, brand, rating, and attributes at Amazon scale. Covers aggregation strategies, dynamic facets, and real-time inventory sync.
Requirements
Functional Requirements:
- Search products by keyword and filter by category, price range, brand, ratings, and custom attributes
- Display facet counts (e.g., "Nike (342)", "Under $50 (1,204)") updated dynamically
- Support multi-select filters with AND/OR semantics per facet
- Return results sorted by relevance, price, rating, or newness
- Update inventory and price facets within seconds of changes
- Support 50+ attribute facets per product category
Non-Functional Requirements:
- Sub-200ms query latency including facet computation
- 500 million product listings
- 100,000 concurrent search QPS
- Facet counts accurate within 5 seconds of inventory changes
- 99.99% availability
Scale Estimation
Amazon lists ~350 million products. At 2 KB average metadata per product (attributes, price, stock status), the document store requires ~700 GB. The search index with doc values for facet fields adds another 1–2 TB. Peak QPS during sales events (Prime Day) reaches 500,000+ requests/sec. Each request triggers 1 keyword query + N facet aggregations. With Elasticsearch, aggregation queries are computed using doc values (columnar storage), keeping per-query aggregation cost under 10ms for most fields.
High-Level Architecture
The architecture centers on a search cluster (Elasticsearch or Solr) that stores product data with rich attribute metadata. An indexing pipeline ingests product updates from the catalog service and inventory updates from the warehouse system. A query service translates user-facing search/filter requests into Elasticsearch queries with post-filter + aggregation constructs. A personalization layer re-ranks results using user preference signals before the final response is assembled.
Product data flows from a catalog database (DynamoDB or MySQL) through a Kafka change-data-capture stream into an indexing microservice. The indexer normalizes attributes (maps category-specific attribute names to canonical field names), enriches with real-time inventory status, and performs a bulk index operation into Elasticsearch. Price and stock updates bypass the full catalog pipeline and use a lightweight direct-write path to update only the relevant numeric fields, keeping facet data fresh within 5 seconds.
The query flow uses Elasticsearch's post_filter pattern: the main query runs without filters to compute accurate facet counts for all facets, then the post_filter applies the user's selected filters to narrow the result set. This ensures facet counts reflect the full product universe (minus the applied filters), which is the standard UX expectation in e-commerce ("how many results would I get if I added this filter").
Core Components
Facet Aggregation Engine
Facets are implemented as Elasticsearch aggregations executed alongside the main query. Term aggregations compute per-value counts for categorical facets (brand, category, color). Range aggregations handle price buckets. Histogram aggregations handle rating distributions. Doc values (columnar, on-disk) enable fast aggregation without loading stored source fields. Eager global ordinals pre-build term-to-integer mappings at index refresh time, reducing aggregation latency from O(distinct values) to O(matching docs).
Dynamic Attribute Schema
Different product categories have different attribute sets (electronics have voltage, clothing has size/color). A nested object or flattened field type in Elasticsearch handles dynamic attributes without schema explosion. Alternatively, a sparse attribute representation uses a keyword array of "key:value" strings (e.g., "color:red", "brand:Nike") that can be efficiently term-aggregated. Category-specific field mappings allow strongly-typed numeric fields for filterable numeric attributes (screen size, weight) while generic string fields handle everything else.
Real-Time Inventory Sync
A dedicated inventory update service consumes warehouse events from Kafka. Rather than reindexing the full product document (expensive), it uses Elasticsearch's update by query or partial document update API to modify only the in_stock, quantity, and price fields. These fields are mapped as numeric types with doc values enabled, so changes immediately affect facet counts on the next index refresh (1-second cycle). A circuit breaker caps inventory update throughput to prevent the update stream from overwhelming the indexing cluster during flash sales.
Database Design
Products are stored in Elasticsearch with a rich mapping: title (text, analyzed), description (text, analyzed), brand (keyword, doc values), category_path (keyword array for hierarchical categories), price (scaled_float), rating (half_float), review_count (integer), attributes (flattened or nested object). Numeric fields used in range filters (price, rating) are mapped as scaled_float or half_float to minimize doc values storage. The category_path uses a path hierarchy encoding ("Electronics/Computers/Laptops") to support hierarchical category facets with a single field.
API Design
Scaling & Bottlenecks
Facet computation is the dominant latency cost. A query with 20 active facet aggregations can take 50–150ms even with doc values. Optimization strategies include: (1) shard-level caching — Elasticsearch caches aggregation results for queries that match ≤10% of documents; (2) sampler aggregations — compute facets on a representative sample of top-scoring documents rather than the full result set; (3) pre-computed facets — for high-traffic category pages, pre-compute and cache facet counts for the default (no filter) state and apply differential counts when filters are added.
Inventory update spikes (e.g., 10,000 products going out of stock simultaneously) can saturate the indexing cluster. A rate limiter and priority queue separate high-priority inventory updates (price changes, stock-out) from lower-priority attribute updates (new reviews). The search cluster is sized to absorb 2x normal indexing throughput without impacting query latency. During extreme load, a fallback mode disables real-time inventory updates and serves slightly stale facet data with a banner indicating counts may be approximate.
Key Trade-offs
- Post-filter vs. filter context: Post-filter gives accurate cross-facet counts but is slower; filter context caches and is faster but requires separate facet queries per filter combination
- Facet accuracy vs. speed: Sampling-based aggregations are 5–10x faster but may show ±5% inaccurate counts on tail facet values
- Schema flexibility vs. indexing efficiency: Flattened/nested dynamic attributes handle arbitrary product types but prevent field-level caching optimizations
- Real-time inventory vs. index stability: Frequent partial updates fragment segments and increase merge overhead; batch updates every 30 seconds are more efficient but less fresh
GO DEEPER
Master this topic in our 12-week cohort
Our Advanced System Design cohort covers this and 11 other deep-dive topics with live sessions, assignments, and expert feedback.