Skip to content
advanced Phase 103 · Commerce Design

Search System Design

45m
1 problems
Topic Progress 0%

Search System Overview

Core Components

Search System:
├── Indexing Pipeline
│   ├── Data extraction
│   ├── Transformation
│   ├── Enrichment
│   └── Indexing
├── Query Processing
│   ├── Query parsing
│   ├── Query expansion
│   ├── Filtering
│   └── Sorting
├── Relevance Engine
│   ├── Text relevance
│   ├── Business rules
│   ├── Personalization
│   └── Learning
└── Response Generation
    ├── Results formatting
    ├── Facets calculation
    ├── Highlighting
    └── Suggestions

Index Structure

{
  "mappings": {
    "properties": {
      "entity_id": { "type": "integer" },
      "sku": { "type": "keyword" },
      "name": { 
        "type": "text",
        "analyzer": "standard",
        "fields": {
          "keyword": { "type": "keyword" },
          "autocomplete": { 
            "type": "text",
            "analyzer": "autocomplete"
          }
        }
      },
      "description": { "type": "text", "analyzer": "html_strip" },
      "price": { "type": "float" },
      "qty": { "type": "integer" },
      "category_ids": { "type": "keyword" },
      "in_stock": { "type": "boolean" },
      "created_at": { "type": "date" },
      "updated_at": { "type": "date" },
      "attributes": {
        "type": "object",
        "dynamic": true
      }
    }
  }
}

Indexing Strategies

Full Reindex

// Full catalog reindex
public function reindexAll()
{
    $products = $this->productCollection->create()
        ->addAttributeToSelect('*')
        ->load();
    
    $batch = [];
    foreach ($products as $product) {
        $batch[] = $this->prepareIndexData($product);
        
        if (count($batch) >= 1000) {
            $this->bulkIndex($batch);
            $batch = [];
        }
    }
    
    if (!empty($batch)) {
        $this->bulkIndex($batch);
    }
}

private function prepareIndexData($product)
{
    return [
        'entity_id' => $product->getId(),
        'sku' => $product->getSku(),
        'name' => $product->getName(),
        'description' => strip_tags($product->getDescription()),
        'price' => (float) $product->getPrice(),
        'qty' => (int) $product->getStockItem()->getQty(),
        'category_ids' => $product->getCategoryIds(),
        'in_stock' => $product->isSalable(),
        'created_at' => $product->getCreatedAt(),
        'updated_at' => $product->getUpdatedAt(),
    ];
}

Incremental Indexing

// On product save
public function onProductSave($observer)
{
    $product = $observer->getEvent()->getProduct();
    $indexData = $this->prepareIndexData($product);
    
    $this->searchClient->index([
        'index' => 'catalog_product',
        'id' => $product->getId(),
        'body' => $indexData
    ]);
}

// On product delete
public function onProductDelete($observer)
{
    $productId = $observer->getEvent()->getProductId();
    
    $this->searchClient->delete([
        'index' => 'catalog_product',
        'id' => $productId
    ]);
}

Index Management

// Index aliases
public function switchIndex($newIndex)
{
    $oldIndex = $this->getIndexAlias('catalog_product');
    
    // Update alias
    $this->searchClient->indices()->updateAliases([
        'body' => [
            'actions' => [
                ['remove' => ['index' => $oldIndex, 'alias' => 'catalog_product']]],
                ['add' => ['index' => $newIndex, 'alias' => 'catalog_product']]
            ]
        ]
    ]);
    
    // Delete old index
    $this->searchClient->indices()->delete(['index' => $oldIndex]);
}

Relevance Tuning

Text Relevance

{
  "query": {
    "bool": {
      "must": [
        {
          "multi_match": {
            "query": "wireless headphones",
            "fields": [
              "name^5",
              "sku^3",
              "description^1",
              "categories^2"
            ],
            "type": "best_fields",
            "fuzziness": "AUTO"
          }
        }
      ],
      "should": [
        {
          "term": {
            "in_stock": {
              "value": true,
              "boost": 10
            }
          }
        },
        {
          "range": {
            "price": {
              "gte": 50,
              "lte": 200,
              "boost": 5
            }
          }
        }
      ]
    }
  }
}

Boosting Strategies

Field boosting:
├── name: ^5 (highest priority)
├── sku: ^3 (exact match)
├── categories: ^2 (context)
└── description: ^1 (full text)

Business rules:
├── In-stock products: +10 boost
├── Sale products: +5 boost
├── New products: +3 boost
└── High-rated products: +2 boost

Personalization:
├── Viewed products: +5 boost
├── Purchased categories: +3 boost
└── Customer segment: +2 boost

Relevance Testing

// Test relevance
public function testRelevance($query, $expectedResults)
{
    $results = $this->searchService->search($query);
    
    $actualIds = array_column($results['hits'], 'entity_id');
    
    $precision = count(array_intersect($actualIds, $expectedResults)) / count($actualIds);
    $recall = count(array_intersect($actualIds, $expectedResults)) / count($expectedResults);
    
    return [
        'precision' => $precision,
        'recall' => $recall,
        'f1' => 2 * ($precision * $recall) / ($precision + $recall)
    ];
}

Autocomplete Design

Autocomplete Query

{
  "suggest": {
    "product-suggest": {
      "prefix": "wire",
      "completion": {
        "field": "name.autocomplete",
        "fuzzy": {
          "fuzziness": "AUTO"
        },
        "size": 10
      }
    }
  }
}

Autocomplete Implementation

// Autocomplete service
public function autocomplete($query, $limit = 10)
{
    $results = $this->searchClient->search([
        'index' => 'catalog_product',
        'body' => [
            'suggest' => [
                'product-suggest' => [
                    'prefix' => $query,
                    'completion' => [
                        'field' => 'name.autocomplete',
                        'size' => $limit,
                        'fuzzy' => ['fuzziness' => 'AUTO']
                    ]
                ]
            ]
        ]
    ]);
    
    $suggestions = [];
    foreach ($results['suggest']['product-suggest'][0]['options'] as $option) {
        $suggestions[] = [
            'text' => $option['text'],
            'score' => $option['_score'],
            'product_id' => $option['_source']['entity_id'],
            'sku' => $option['_source']['sku'],
            'price' => $option['_source']['price']
        ];
    }
    
    return $suggestions;
}

// Search suggestions with highlighting
public function searchSuggestions($query)
{
    $results = $this->searchClient->search([
        'index' => 'catalog_product',
        'body' => [
            'query' => [
                'multi_match' => [
                    'query' => $query,
                    'fields' => ['name^5', 'sku^3'],
                    'type' => 'bool_prefix'
                ]
            ],
            'highlight' => [
                'fields' => [
                    'name' => ['number_of_fragments' => 0]
                ]
            ],
            'size' => 5
        ]
    ]);
    
    return $results;
}

Practice Problems

0 / 1 solved
Search System Design

Design search system for 500K products with autocomplete, faceted search, and personalization.

Solution
// System:
// 1. Index: Full + incremental, batch 1000
// 2. Relevance: name^5, sku^3, business rules
// 3. Facets: Category, price, availability
// 4. Autocomplete: Prefix + fuzzy, cached
// 5. Personalization: Boost viewed/purchased
// 6. Performance: Search cache, result caching

Quiz

1. What is the purpose of field boosting?

Question 1 options

2. What is faceted search?

Question 2 options

3. How does autocomplete work?

Question 3 options

4. What is the benefit of incremental indexing?

Question 4 options

Flashcards

Question

Search field weights?

Answer

name^5, sku^3, categories^2, description^1

Question

Faceted search purpose?

Answer

Filtering with result counts for each option

Question

Autocomplete technique?

Answer

Prefix matching with fuzzy tolerance

Question

Incremental indexing?

Answer

Update only changed products in real-time

Question

Relevance metrics?

Answer

Precision, Recall, F1 score

Revision Notes

Key Takeaways

  • 1. Field boosting: name^5, sku^3, categories^2, description^1
  • 2. Faceted search: Filtering with aggregation counts
  • 3. Autocomplete: Prefix + fuzzy matching for suggestions
  • 4. Incremental indexing: Real-time updates without full reindex
  • 5. Relevance: Measure with precision, recall, F1

Interview Tips

  • Explain relevance tuning strategies
  • Discuss faceted search implementation
  • Know autocomplete techniques
  • Understand indexing strategies

Cheat Sheet

Search System

  • Boost: name^5, sku^3, categories^2, desc^1
  • Facets: Filter + count aggregation
  • Autocomplete: Prefix + fuzzy
  • Indexing: Full + incremental
  • Relevance: Precision, Recall, F1