Граф знанийНастройки

Обновлено: 30 апреля 2026 г. в 07:40

Search System (Google/Elasticsearch)

средний

Классическая задача про поисковую систему: обход веб-страниц, инвертированный индекс, ранжирование через BM25 и PageRank и распределённая обработка запросов.

Поисковая система ломается не в момент записи документа, а там, где встречаются индексация, ранжирование, свежесть данных и пользовательский путь запроса.

Глава помогает разложить конвейер приёма документов, инвертированный индекс, маршрутизацию по шардам, каскад ранжирования, автодополнение и окно согласованности между записью и поисковой выдачей.

В интервью и инженерных обсуждениях этот кейс полезен тем, что заставляет одновременно держать в голове релевантность, задержки и эксплуатационную стоимость, а не только хранение.

Pipeline Thinking

Критичны ingestion, partitioning, deduplication и задержки между стадиями обработки.

Слой сервинга

Решения по индексам, cache locality и query path определяют пользовательскую задержку.

Consistency Window

Явно фиксируйте, где допустима eventual consistency и где требуется stronger guarantees.

Cost vs Freshness

Балансируйте скорость обновления данных и стоимость вычислений/хранения.

Почему поиск сложен

Проектирование поисковой системы — одна из самых насыщенных задач на интервью по системному дизайну. Здесь в одной архитектуре встречаются , построение индекса, обработка запроса, ранжирование результатов и постоянная борьба за свежесть данных при огромном масштабе.

Ключевые подсистемы

Краулер

Собирает страницы и планирует повторный обход

Индексатор

Преобразует документы в инвертированный индекс

Ранжирование

Отбирает и упорядочивает лучшие результаты

Функциональные требования

Базовые возможности

  • Полнотекстовый поиск по миллиардам документов
  • Автодополнение и подсказки запросов
  • Фасетный поиск и фильтрация
  • Релевантное ранжирование результатов
  • Исправление опечаток и обработка синонимов

Дополнительные возможности

  • Поиск по изображениям и видео
  • Персонализация результатов
  • Локализация и языковые модели
  • Почти мгновенная индексация новых страниц

Нефункциональные требования

Пользователь воспринимает поиск как быстрый, если система удерживает ответ в рамках и выдерживает сотни тысяч без заметной деградации релевантности.

<200ms
Бюджет ответа для хвоста распределения
10B+
Проиндексированных документов
100K QPS
Пиковая нагрузка
99.99%
Доступность

Обход и обновление веб-страниц

Связь

Alex Xu: Web Crawler

Детальный разбор архитектуры краулера в книге System Design Interview

Читать обзор

Краулер решает сразу несколько задач: что обходить в первую очередь, как часто возвращаться к документу и как не перегружать чужие сайты. Для масштаба Google это означает обработку миллиардов URL каждый день.

Архитектура краулера

Выберите этап, чтобы подсветить ключевые компоненты

Приоритетная очередь
Менеджер вежливости
Оценка свежести
Пул воркеров
DNS-резолвер
Кэш robots.txt
HTML-парсер
Детектор дубликатов
Извлечение URL

Очередь URL и политика обхода

Очередь URL управляет порядком обхода и нагрузкой на домены:

  • Приоритетная очередь — важные страницы идут первыми
  • Вежливость — ограничение запросов к одному домену
  • Свежесть — частота повторного обхода страниц

Поиск дубликатов

Чтобы не тратить ресурсы на одинаковые документы, система использует для URL и приближённые методы вроде для похожего содержимого:

  • Дедупликация URL — быстрый отсев уже встреченных адресов
  • Хеш содержимого — SimHash и MinHash для похожих страниц
  • Почти дубликаты — LSH для схожих документов

💡 Вежливый обход и robots.txt

Каждый краулер должен соблюдать robots.txt и не перегружать серверы. Google использует отдельные очереди для каждого домена с ограничением частоты запросов. Типичный интервал между запросами к одному домену — 1-10 секунд.

Инвертированный индекс

Теория

Database Internals

Глубокое погружение в структуры данных для индексации

Читать обзор

Инвертированный индекс — базовая структура данных для полнотекстового поиска. Вместо связи «документ → слова» он хранит обратную связь «слово → документы», что позволяет быстро находить кандидатов по термам запроса.

Как устроен инвертированный индекс

Прямой индекс (традиционный):
┌─────────────┬──────────────────────────────────────┐
│   ID док.   │             Термы                    │
├─────────────┼──────────────────────────────────────┤
│   doc_1     │  "system", "design", "interview"     │
│   doc_2     │  "design", "patterns", "book"        │
│   doc_3     │  "system", "architecture", "book"    │
└─────────────┴──────────────────────────────────────┘

Инвертированный индекс:
┌─────────────────┬─────────────────────────────────────────────┐
│      Терм       │         Список документов                   │
├─────────────────┼─────────────────────────────────────────────┤
│  "system"       │  [(doc_1, pos:[0]), (doc_3, pos:[0])]       │
│  "design"       │  [(doc_1, pos:[1]), (doc_2, pos:[0])]       │
│  "interview"    │  [(doc_1, pos:[2])]                         │
│  "patterns"     │  [(doc_2, pos:[1])]                         │
│  "book"         │  [(doc_2, pos:[2]), (doc_3, pos:[2])]       │
│  "architecture" │  [(doc_3, pos:[1])]                         │
└─────────────────┴─────────────────────────────────────────────┘

Запись списка документов:
┌──────────────────────────────────────────────────────────────┐
│  doc_id: uint64                                              │
│  term_frequency: uint16                                      │
│  positions: [uint32, ...]  // для запросов по фразе          │
│  field_lengths: {title: 5, body: 1000, ...}                  │
└──────────────────────────────────────────────────────────────┘

Компоненты индекса

  • Словарь терминов (lexicon)

    Словарь всех уникальных термов, который обычно держат в памяти

  • Списки документов

    Списки документов для каждого терма, которые обычно лежат на диске

  • Списки пропусков

    Ускоряют пересечение длинных списков документов

Оптимизации хранения

  • Дельта-кодирование

    Хранение разниц `doc_id` вместо абсолютных значений

  • Вариабельное байтовое кодирование

    Сжатие маленьких чисел в меньшее количество байт

  • Блочное сжатие

    PFOR, Simple9, Roaring Bitmaps

Конвейер обработки текста

Выберите этап, чтобы посмотреть результат трансформации

Исходный текст

"The System Design Interview is AMAZING!"

Результат

["The","System","Design","Interview","is","AMAZING!"]

Смысл этапа

Разбиваем текст на отдельные токены

Обработка поискового запроса

После поступления запроса система должна разобрать его, расширить, получить списки кандидатов и быстро сократить их до небольшого набора документов для ранжирования.

Обработка поискового запроса

Переключайте этапы, чтобы увидеть детали обработки

Текущий этап

Разбор запроса

Токены, операторы и поля запроса

Пример

Запрос: "system design patterns"Токены: ["system","design","patterns"]Оператор: ANDПоля: title?, body?

Бюджет задержки

Разбор и расширение: < 20 мс, получение данных: < 50 мс, ранжирование: < 100 мс

⚡ Оптимизация: алгоритм WAND

WAND (Weighted AND) — алгоритм для быстрого отбора Top-K документов без полного сканирования всей коллекции:

  • Для каждого терма хранится верхняя граница возможной оценки
  • Документы, которые точно не попадут в Top-K, пропускаются заранее
  • На практике это даёт многократное ускорение по сравнению с наивным проходом

Ранжирование и качество выдачи

Качество поиска определяется не только полнотой индекса, но и тем, как система расставляет документы. На первом слое обычно работают и , затем подключаются графовые сигналы вроде и более тяжёлые модели наподобие .

Формула BM25

Основная формула для текстового ранжирования:

BM25(D, Q) = Σ IDF(qi) × 
  (f(qi, D) × (k1 + 1)) / 
  (f(qi, D) + k1 × (1 - b + b × |D|/avgdl))

Где:
  f(qi, D) = частота терма в документе
  |D| = длина документа
  avgdl = средняя длина документа
  k1 = 1.2 (сатурация терма)
  b = 0.75 (нормализация длины)

BM25 — улучшенная версия TF-IDF с сатурацией и нормализацией длины

PageRank (упрощённо)

Оценка авторитетности страницы по входящим ссылкам:

PR(A) = (1-d) + d × Σ PR(Ti)/C(Ti)

Где:
  d = коэффициент затухания (0.85)
  Ti = страницы, которые ссылаются на A
  C(Ti) = число исходящих ссылок у Ti

Итеративное вычисление:
  1. Инициализировать все PR = 1/N
  2. Повторять до сходимости
  3. Нормализовать результаты

Классический алгоритм Google для оценки ссылочного авторитета страницы

Многоэтапное ранжирование

Переключайте этапы, чтобы увидеть подробности ранжирования

Текущий этап

Этап 1: Генерация кандидатов

Поиск по индексу и базовая оценка BM25

Выход

≈ 10 000 кандидатов

Бюджет задержки

≈ 50 мс

Ключевые сигналы

  • Оценка BM25
  • Извлечение Top-10K
  • Низкая задержка
100+
сигналов ранжирования в Google
BERT
нейронные модели ранжирования
LTR
подход Learning to Rank

Распределённая архитектура поиска

Теория

DDIA: Partitioning

Партиционирование данных для масштабирования

Читать обзор

Поисковая система масштаба Google требует распределённой архитектуры, где индекс разбит на , а стратегия напрямую влияет и на масштабирование, и на стоимость каждого запроса.

Стратегии шардирования

Шардирование по документам
Шард 1: документы 1-1M
Шард 2: документы 1M-2M
Шард 3: документы 2M-3M
...

Запрос → все шарды → слияние результатов

Плюсы:
+ Простота
+ Удобное добавление документов
Минусы:
- Каждый запрос идёт во все шарды
Шардирование по термам
Шард 1: термы A-F
Шард 2: термы G-M
Шард 3: термы N-Z
...

Запрос → только нужные шарды

Плюсы:
+ Меньше шардов на запрос
Минусы:
- Перегрев на популярных термах
- Сложнее добавлять документы

Распределённая обработка запроса

Подсветите маршрутизацию, работу шардов или финальное слияние

Балансировщик

Распределяет входящий трафик

Маршрутизатор запроса

Разбирает запрос и рассылает его по шардам

Агрегатор

Сливает Top-K и выполняет финальное ранжирование

Шард 1

Реплики A/B/C

Шард 2

Реплики A/B/C

Шард 3

Реплики A/B/C

1. Маршрутизатор разбирает запрос
2. Параллельная рассылка по всем шардам
3. Слияние Top-K и финальная дооценка

Бюджет задержки

Маршрутизатор: < 10 мсЗапрос к шардам: < 100 мсСлияние: < 20 мс

💡 Архитектура Elasticsearch

Elasticsearch использует шардирование по документам с автоматической маршрутизацией:

shard = hash(document_id) % number_of_primary_shards

• Первичные шарды — запись и чтение
• Шарды-реплики — чтение и резервирование
• Типичная конфигурация: 5 первичных × 2 реплики = 15 шардов

Быстрое обновление индекса

Для новостей и социальных сетей критично, чтобы документ попадал в выдачу почти сразу после публикации. Поэтому архитектура индексации строится как компромисс между скоростью обновления, стоимостью refresh и долговечностью записи.

Многоуровневая архитектура индекса

Переключайте слой, чтобы увидеть его роль и характеристики

Активный слой

Слой мгновенной индексации

Индекс в памяти для горячих данных

Хранилище

RAM

Свежесть

Секунды

Назначение

Запись и свежесть

Задержка

< 1 с до появления в поиске

Параллельный запрос ко всем слоям

Запрос → все слои параллельно → слияние результатов → удаление дублей → ответ

Путь записи

  • 1.Документ поступает в Kafka
  • 2.Потребитель индексатора его обрабатывает
  • 3.Документ попадает во временный буфер в памяти
  • 4.После refresh документ становится доступным для поиска
  • 5.После flush запись фиксируется на диске

Как работает refresh в Elasticsearch

  • refresh_interval: 1s (по умолчанию)
  • Создаёт новый сегмент в памяти
  • Даёт поиск с почти мгновенным обновлением
  • Компромисс между ценой refresh и свежестью индекса

Автодополнение и подсказки

Автодополнение должно отвечать ещё быстрее, чем основной поиск, поэтому обычно опирается на , предвычисленные популярные запросы и отдельный бюджет задержки.

Автодополнение на trie

Выберите префикс, чтобы посмотреть путь в trie и подсказки

Путь в trie

rootsys
Длина префикса

3 символов

Целевая задержка

< 50 мс

Примечание

Топ-результаты хранятся в узле префикса

Лучшие подсказки

  • system design
  • system
  • system administrator
  • system metrics

Кэш Top-K

На узлах trie хранится список подсказок (Top-10), чтобы не обходить все поддерево.

Оптимизации

  • Кэш префиксов — хранение самых популярных начал запросов
  • Сжатый trie — объединение цепочек однотипных узлов
  • Предвычисление Top-K — хранение лучших подсказок прямо в узлах

Сбор сигналов

  • Логи запросов с агрегацией по времени
  • Фильтрация оскорбительных запросов
  • Персонализация (история пользователя)

Что проговорить на интервью

Что обсудить обязательно

  • Инвертированный индекс против прямого индекса и их компромиссы
  • Формулы BM25 и TF-IDF и понимание того, откуда берётся оценка
  • Стратегия шардирования: по документам или по термам
  • Распределение бюджета задержки между этапами
  • Почти мгновенная индексация против пакетного обновления

Частые уточняющие вопросы

  • Как обрабатывать запросы по точной фразе?
  • Как реализовать нечёткий поиск?
  • Как добавить персонализацию?
  • Как бороться со спамом в результатах?
  • Как измерять качество поиска?

Ключевые выводы

1

Инвертированный индекс

Базовая структура данных для быстрого полнотекстового поиска

2

Каскадное ранжирование

Путь от дешёвых формул к тяжёлым ML-моделям для баланса качества и задержки

3

Шардирование по документам

На практике чаще всего это самый понятный и удобный путь масштабирования

4

Многоуровневое хранение

Разные слои индекса позволяют сочетать свежесть новых данных и эффективность старых

5

Вежливый обход

Поиск начинается с уважения к robots.txt, лимитам домена и качеству входных данных

Связанные главы

Чтобы отмечать прохождение, включи трекинг в Настройки