Индексирование и хранение

Рейтинг: 80.6% · 16 голосов
Полный курс об устройстве веб-поиска: обход, индексирование, факторы ранжирования, нейропоиск, поведенческие сигналы, антиспам, SEO. 23 модуля по главам с разбором и обсуждением.
Ответить
Аватара пользователя
kirill_ir
Сообщения: 25
Зарегистрирован: 11 май 2026, 05:31

Индексирование и хранение

Сообщение kirill_ir »

Оглавление курса (23)
  1. Введение
  2. Теоретический фундамент (Information Retrieval)
  3. Краулинг (обход)
  4. Идентичность документа: каноникализация и дубли
  5. Индексирование и хранение (вы здесь)
  6. Обработка и понимание запроса
  7. Текстовая релевантность
  8. Анализ ссылочного графа
  9. Таксономия факторов ранжирования
  10. Машинное обучение ранжированию (Learning-to-Rank)
  11. Нейросетевой поиск (Neural IR)
  12. Поведенческие сигналы и клик-модели
  13. Каскад ранжирования и обслуживание (serving)
  14. Инфраструктура и распределённое обслуживание
  15. Метапоиск и федерация источников
  16. Группировка, схлопывание, разнообразие
  17. Постранжирование, антиспам и качество выдачи
  18. Свежесть и реалтайм
  19. Локализация, гео и персонализация
  20. Измерение качества и эксперименты
  21. SEO: оптимизация под факторы
  22. Этика, приватность и борьба со злоупотреблениями
  23. Capstone: сквозной проект
Часть II · ~10 ч · Сложность: (продвинутый) · Пререквизиты: Модуль 1, Модуль 3
Обзор модуля

К этому моменту в сквозном конвейере «обход → индекс → факторы → ранжирование → выдача → постобработка → измерение» планировщик обхода (Модуль 2) уже добыл документы, а каноникализатор (Модуль 3) свёл дубли к одному представителю и присвоил каждому документу устойчивый идентификатор. Теперь наступает звено индексирования: как из миллиардов разобранных документов построить структуру данных, по которой за единицы миллисекунд можно найти все документы, содержащие слова запроса, и тут же достать признаки для их оценки.

Этот модуль — про офлайн-сторону шва между офлайн и онлайн. Индексатор работает в пакетном (batch) и потоковом (realtime) режимах: он строит обратный индекс, материализует в него предвычисленные признаки документов и хостов и раскладывает всё по сегментам, ярусам и шардам так, чтобы рантайм мог читать это с предсказуемой латентностью. Ключевая идея, к которой мы будем возвращаться: робот материализует статику в индекс, а рантайм читает её как факторы. Граница между «посчитано заранее» и «посчитано на запросе» — это не философия, а инженерное решение про латентность, стоимость и свежесть.

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

Модуль продолжает Часть II и опирается на Модуль 1 (модель IR, термин, документ, токенизация) и Модуль 3 (идентичность документа, docID). Дальше всё это будет читаться: текстовая релевантность (Модуль 6) обращается к постинг-листам и позициям, каскад ранжирования (Модуль 12) забирает статику как факторы, свежесть (Модуль 17) опирается на реалтайм-контур, а инфраструктура (Модуль 13) — на шардирование и сегменты.

Как читать по трекам
  • Студент CS. Главы 4.1 и 4.4 — обязательно и подробно (структуры данных, сжатие, отбор кандидатов — основа всего, что дальше). Главы 4.2 и 4.3 — обязательно для понимания факторов. Глава 4.5 — обязательно концептуально (модель индекса как append-only-сегментов). Все лабы желательно сделать руками.
  • Инженер поиска/ML. Весь модуль — это ваш «домашний» материал. Особое внимание: инженерные заметки про сжатие и латентность (4.1), квоты отбора и тиры (4.4), реалтайм-контур и видимость записей (4.5). Лабы делайте на реальных объёмах данных.
  • SEO-специалист. Обязательно: SEO-врезки во всех главах, особенно про зоны документа (4.2: что весит больше — title, заголовки, якоря), про индексируемость и попадание в высокий ярус (4.4), про задержку индексации realtime vs batch (4.5). Вывод форматов сжатия и алгоритмов — обзорно.
  • Смешанный/руководитель. Прочитайте Обзор, конспекты 4.1 и 4.4 по диагонали, целиком — раздел про шов офлайн/онлайн (4.3) и про realtime vs batch (4.5). Это объясняет, почему «новое» индексируется не мгновенно и почему изменение фактора в индексе стоит дорого.
Карта модуля
  • 4.1. Обратный индекс: постинг-листы, хиты, позиции, частоты, сжатие (varint, delta, PForDelta). (продвинутый)
  • 4.2. Аннотации и зоны документа: title, тело, якорный текст, заголовки. (средний)
  • 4.3. Документная и хостовая статика: предвычисленные признаки и шов офлайн/онлайн. (средний)
  • 4.4. Сегментация, ярусы (тиры) индекса, квоты отбора, шардирование. (продвинутый)
  • 4.5. Реалтайм-контур индекса vs батч-построение. (продвинутый)
Глава 4.1. Обратный индекс: постинг-листы, хиты, позиции, частоты, сжатие (продвинутый)

Цели обучения

После главы студент сможет:
  • объяснить, чем обратный индекс (inverted index) отличается от прямого (forward index) и почему поиск строится на обратном;
  • описать строение постинг-листа (posting list): docID, частота, позиции, хиты;
  • реализовать кодирование разностей (delta/gap encoding) поверх отсортированного списка docID;
  • закодировать целые переменной длины (varint) и оценить число байт на постинг;
  • объяснить идею пакетного кодирования (PForDelta) и компромисс «степень сжатия ↔ скорость декодирования»;
  • оценить, как выбор кодека влияет на латентность чтения постинг-листа.
Конспект

Зачем «обратный» индекс

Прямой индекс отвечает на вопрос «какие слова в документе d?» — это просто список термов документа. Поиск задаёт обратный вопрос: «в каких документах встречается слово t?». Структуру, которая по терму возвращает список документов, и называют обратным индексом.
Интуиция. Это предметный указатель в конце учебника. Вы не листаете все страницы в поисках слова «энтропия» — вы открываете указатель, находите строку энтропия → 17, 142, 143 и идёте сразу на нужные страницы. Обратный индекс — это такой указатель для всего корпуса.
Обратный индекс состоит из двух частей:
  • Словарь (lexicon / term dictionary). Отображение term → указатель на постинг-лист (плюс агрегаты: число документов с термом df, суммарная частота). Словарь должен быть компактным и быстро искаемым (хеш-таблица, отсортированный массив с бинарным поиском, либо префиксное дерево / FST для экономии памяти на общих префиксах).
  • Постинги (postings). Для каждого терма — список вхождений в документы.

Код: Выделить всё

СЛОВАРЬ                         ПОСТИНГИ (постинг-листы)
┌────────────┬────────┐
│ term       │ ptr,df │
├────────────┼────────┤
│ "альфа"    │  •──────────►  [d3:2  d7:1  d11:5  d12:1 ...]
│ "бета"     │  •──────────►  [d1:1  d3:1  d12:3 ...]
│ "гамма"    │  •──────────►  [d7:4  d20:2 ...]
└────────────┴────────┘
Что лежит в одном постинге

Минимальный постинг — это docID. Но для ранжирования (Модуль 6) и фразового поиска нужно больше. Различают три типичных уровня детализации:

Код: Выделить всё

Уровень         |  Содержимое постинга        |  Что позволяет
----------------+-----------------------------+-------------------------------------
doc-level       |  docID                      |  булев поиск, проверка наличия терма
freq-level      |  docID, частота tf          |  скоринг BM25/TF-IDF без фраз
position-level  |  docID, tf, список позиций  |  фразовый поиск, близость, зоны
Позиции (positions) — порядковые номера слова в документе (например, слово стоит на 5-й, 41-й, 88-й позициях). Они нужны, чтобы отличить документ, где «красная шапочка» идёт подряд, от документа, где «красная» в начале, а «шапочка» — в конце.

Хит (hit) — это обобщённая запись об одном вхождении терма. Помимо позиции, хит часто упаковывает зону (где встретилось слово — в title, теле, якоре; см. главу 4.2) и иногда признаки оформления. Компактный хит — это битовое поле: несколько бит на тип зоны, остальное — на позицию.
Пример. Постинг терма «альфа» в документе d11 уровня position может выглядеть так: docID=11, tf=5, positions=[2, 17, 18, 90, 240], где позиция 2 помечена зоной title. То, что два вхождения (17, 18) стоят рядом, позволит обработать фразу «альфа-альфа» как точное совпадение.
Почему постинг-лист отсортирован по docID

Внутри постинг-листа docID хранятся в возрастающем порядке. Это даёт два больших выигрыша:
  1. Пересечение списков (AND-запрос) делается слиянием за линейное время без сортировки: идём двумя указателями по двум отсортированным спискам.
  2. Сжатие разностями (см. ниже) становится эффективным.
Интуиция. Запрос «альфа И бета» — это пересечение двух множеств документов. Если оба списка отсортированы, пересечение находится «застёжкой-молнией»: сравниваем головы двух списков, меньшую двигаем вперёд, совпадения выписываем.
Сжатие 1: кодирование разностей (delta / gap encoding)

docID могут быть большими числами (миллиарды документов → ~31–34 бита на число). Но список отсортирован, значит вместо самих чисел можно хранить разности между соседними (gaps):

Код: Выделить всё

docID:   [824,  831,  833,  900,  905, 1207]
gaps:    [824,    7,    2,   67,    5,  302]
Разности — маленькие числа. Маленькие числа можно закодировать малым числом бит. Чем плотнее терм встречается (чем короче «дыры»), тем мельче gaps и тем сильнее сжатие.
Интуиция. Хранить «адреса домов» дорого. Хранить «сколько шагов до следующего дома» — дёшево, потому что шаги маленькие. Восстановить адреса легко: накопительная сумма.
Сжатие 2: целые переменной длины (varint)

Varint (variable-length integer, он же VByte) кодирует целое побайтно: в каждом байте 7 бит данных и 1 бит-продолжение (continuation bit). Если бит продолжения = 1 — есть ещё байт; = 0 — это последний байт числа.

Код: Выделить всё

число 5      →  0000_0101                              (1 байт)
число 300    →  1010_1100  0000_0010                   (2 байта)
              старший бит=1 (есть продолжение)  │старший бит=0 (конец)
Стоимость: 1 байт на числа < 128, 2 байта на < 16384, и т.д. Для мелких gaps это в среднем 1–2 байта вместо 4 — экономия в 2–4 раза, при этом декодирование простое.
Инженерная заметка. Главная боль varint — ветвление на каждом байте (проверка бита продолжения), что плохо ложится на конвейер процессора и предсказатель переходов. На горячем пути декодирования постинг-листов это заметно. Поэтому в высоконагруженных индексах varint часто уступает место пакетным (block-based) кодекам, декодирующим сразу группу чисел без побайтового ветвления, нередко с использованием SIMD.
Сжатие 3: пакетные кодеки и PForDelta

Идея блочных кодеков: разбить поток gaps на блоки фиксированного размера (например, 128 значений) и кодировать каждый блок одинаковой битовой шириной.

Bit-packing (frame of reference). Находим максимальное число в блоке, берём b = ceil(log2(max+1)) бит и упаковываем все 128 чисел по b бит подряд. Декодирование — массовое и без ветвлений.

Проблема bit-packing: одно большое число в блоке («выброс», outlier) поднимает b для всех 128 значений и убивает сжатие.

PForDelta (Patched Frame Of Reference). Решает проблему выбросов так:
  1. Выбираем b так, чтобы под него помещалось, скажем, 90% значений блока.
  2. Эти значения упаковываем по b бит.
  3. Редкие выбросы (10%) кодируем отдельно как исключения (patches): храним их полные значения и позиции в отдельной области, а в основном потоке на их месте оставляем «заглушку».
  4. При декодировании сначала распаковываем основной поток bit-packing'ом, затем «накладываем заплатки» — подставляем исключения на их позиции.

Код: Выделить всё

Блок gaps: [3, 5, 2, 7, 9001, 4, 6, 2, ...]
                            ▲ выброс
b выбран под 90% значений (например, b=4 → значения 0..15)
основной поток: [3,5,2,7, ?,4,6,2,...]  упаковано по 4 бита
исключения:     позиция 4 → значение 9001  (хранится отдельно, полной шириной)
Интуиция. Не порть сжатие всего блока из-за одного редкого «гиганта». Упакуй массу плотно, а гигантов вынеси в отдельный список и «залатай» на выходе.
Пример (порядок величин). Постинг-лист частого слова на 10 млн документов: «сырьём» это ~40 МБ (4 байта × 10 млн). После delta + varint — единицы МБ. После блочного PForDelta — сопоставимый размер, но декодируется в разы быстрее за счёт отсутствия побайтового ветвления.
Позиции и частоты тоже сжимают, причём отдельными потоками: частоты — своим кодеком, позиции внутри документа — теми же gap+varint/bit-packing (позиции внутри документа тоже монотонно растут). Разделение потоков («docID отдельно, частоты отдельно, позиции отдельно») позволяет читать только то, что нужно: булев AND может вообще не трогать позиции.

Skip-списки: быстрое продвижение по постинг-листу

При пересечении длинного и короткого списков нет смысла декодировать длинный список целиком. Skip-указатели (skip pointers) — это разрежённый «оглавление» постинг-листа: через каждые k постингов хранится (docID, смещение в байтах), позволяя «перепрыгнуть» вперёд к нужному docID, не декодируя промежуток.
Интуиция. Метод advance(target) («доведи указатель до первого docID ≥ target») — рабочая лошадь пересечений. Skip-список превращает его из линейного прохода в почти логарифмический прыжок.
Где это «шьётся» с рантаймом

Обратный индекс — это офлайн-артефакт: его строит индексатор, а на запросе движок только читает. Латентность чтения постинг-листов — нижняя граница времени отбора кандидатов в каскаде (Модуль 12). Поэтому выбор кодека — это прямой инженерный рычаг: сильнее сжал → меньше дискового/сетевого трафика и больше влезает в память, но медленнее декодируешь.
Инженерная заметка (латентность). Правило большого пальца: на горячем пути отбора кандидатов скорость декодирования важнее предельной степени сжатия. Лишний мегабайт памяти дешевле лишней миллисекунды на запросе, умноженной на миллиарды запросов. Поэтому: блочные кодеки на горячих списках; агрессивное сжатие — на холодных данных и редких термах; позиции — отдельным потоком, чтобы не платить за них на запросах, где они не нужны.
Частые заблуждения
Заблуждение. «Сжатие индекса нужно, чтобы сэкономить диск.» Не только и не столько. Главный выигрыш — меньше байт читать и держать в кеше/памяти: сжатый постинг-лист и читается с диска быстрее, и целиком помещается в RAM, что радикально снижает латентность. Диск сегодня дёшев — дорога латентность.
Заблуждение. «Чем сильнее сжатие, тем лучше.» Нет: за пределами некоторой точки рост степени сжатия оплачивается ростом времени декодирования. На горячем пути это чистый проигрыш. Кодек выбирают под нагрузку, а не «самый плотный».
Заблуждение. «Позиции — это то же, что частота.» Частота — сколько раз слово встретилось; позиции — где именно. Частоты достаточно для BM25; для фраз и близости нужны позиции, и они стоят отдельных байт.
Лаба / практика

Задача. Реализовать кодирование/декодирование постинг-листа.

Вход. Отсортированный список docID одного терма, например: [3, 5, 6, 9, 17, 18, 19, 40, 312, 7001].

Шаги.
  1. Постройте gaps (разности соседних, первый элемент — как есть).
  2. Реализуйте varint_encode(n) и varint_decode(bytes) (7 бит данных + бит продолжения).
  3. Закодируйте gaps в байтовый поток, посчитайте суммарный размер в байтах и сравните с «сырым» хранением (4 байта на число).
  4. Реализуйте обратное декодирование: varint → gaps → накопительная сумма → исходные docID. Проверьте, что восстановили исходный список.
  5. (Со звёздочкой) Реализуйте advance(target) со skip-указателями через каждые 4 постинга и сравните число декодированных постингов с линейным проходом.
Ожидаемый результат. Восстановленный список совпадает с исходным; размер varint-потока существенно меньше 40 байт.

Критерий «сделано». Round-trip без потерь; вы можете назвать число байт на ваш список и объяснить, на каком gap'е varint переходит на 2 байта (на gap ≥ 128). Время ~60 мин.

Контрольные вопросы
  1. Почему docID в постинг-листе хранят отсортированными, а не в порядке появления?
  2. Что произойдёт со степенью сжатия gap-кодирования, если терм встречается почти в каждом документе? А если в очень редких?
  3. Сколько байт займёт число 200 в varint и почему?
  4. Какую проблему bit-packing решает PForDelta и какой ценой?
  5. Почему частоты и позиции выгодно хранить отдельными потоками от docID?
  6. В каком случае агрессивное сжатие постинг-листа ухудшает латентность запроса?
  7. Зачем нужны skip-указатели и как они меняют сложность операции advance?
Глава 4.2. Аннотации и зоны документа: title, тело, якоря, заголовки (средний)

Цели обучения

После главы студент сможет:
  • объяснить, что такое зоны (zones / fields) документа и зачем хит несёт метку зоны;
  • перечислить ключевые зоны: title, тело, заголовки, якорный текст (anchor text), метаданные, URL-токены;
  • объяснить, почему якорный текст хранится в индексе документа-цели, а не документа-источника;
  • описать модель взвешенных зон (weighted-zone / fielded scoring) на уровне принципа;
  • оценить, как разбиение на зоны влияет на формирование сниппета и на ранжирование.
Конспект

Документ — это не плоский мешок слов

В Модуле 1 документ моделировался как «мешок слов» (bag of words). Для реального индекса этого мало: слово в title и слово в подвале страницы — это разный сигнал. Поэтому документ разбивают на зоны (zones) — именованные области с разным семантическим весом, и каждый хит помечается зоной, в которой он встретился.
Интуиция. Одно и то же слово «договор» в заголовке страницы кричит «страница про договоры», а в куске юридического дисклеймера внизу — почти шум. Индекс обязан различать эти два вхождения, иначе он не отличит документ «про X» от документа, где «X» упомянут вскользь.
Ключевые зоны

Код: Выделить всё

Зона           |  Что это                             |  Почему важна
---------------+--------------------------------------+--------------------------------------------------------------------------
title          |  заголовок документа (<title>)       |  сильнейший внутренний текстовый сигнал темы; источник заголовка сниппета
Заголовки      |  H1…H6, выделенные заголовки секций  |  структура и подтемы документа
Тело           |  основной текст                      |  масса контента, база для BM25
Якорный текст  |  текст ссылок, ведущих на документ   |  внешнее описание документа чужими словами
URL-токены     |  слова, разобранные из URL/пути      |  слабый, но дешёвый тематический сигнал
Метаданные     |  meta-описания, атрибуты, подписи    |  вспомогательные сигналы и сниппет
Якорный текст — особый случай

Якорный текст (anchor text) — это видимый текст гиперссылки. Если множество страниц ссылается на документ D словами «годовой отчёт компании», то «годовой отчёт» — сильное описание D, данное со стороны.

Принципиальный момент хранения: якорный текст индексируется в зоне документа-цели, а не источника. То есть при разборе страницы A с ссылкой A → D[текст: "годовой отчёт"] строка «годовой отчёт» отправляется в индексную запись документа D как его аннотация.
Интуиция. Якорный текст — это «как тебя называют другие». Часто люди описывают страницу лаконичнее и точнее, чем её собственный title. Поэтому внешние якоря — мощный сигнал, и собирают их именно в карточку цели.
Внимание. Якорный текст — двусторонний меч. Он легко накручивается (массовая простановка ссылок с одинаковым текстом). Поэтому сырой якорный сигнал агрегируют, нормируют по числу источников и доменов и фильтруют (связь с антиспамом — Модуль 16). В индексе хранят не «сколько раз», а взвешенно-агрегированную аннотацию.
Модель взвешенных зон

Простейшая схема скоринга по зонам — взвешенная сумма: итоговый текстовый вклад терма есть сумма его вкладов по зонам с весами зон:

Код: Выделить всё

score_text(d, t) = w_title · s_title(d,t)
                  + w_body  · s_body(d,t)
                  + w_anchor· s_anchor(d,t)
                  + w_head  · s_head(d,t) + ...
где s_zone(d,t) — частотный вклад терма в зоне (например, BM25, посчитанный по этой зоне), а веса w_zone либо заданы, либо обучены (Модуль 9). Существует и более тонкий вариант — BM25F, где частоты зон сначала складываются с весами в «виртуальную» частоту, и только потом применяется насыщение (это материал Модуля 6; здесь важно лишь, что зоны — вход для таких моделей).
Заблуждение. «Веса зон — это константы, прошитые навсегда.» На практике это входные признаки модели ранжирования и предмет обучения и A/B-проверок (Модуль 19). Зоны — это разметка; как их взвешивать, решает ранжирование.
Зоны и сниппет

Разбиение на зоны нужно не только ранжированию, но и формированию сниппета (Модуль 14): заголовок сниппета берут обычно из title (или из якоря/заголовка, если они точнее отвечают запросу), а тело сниппета — из фрагментов тела с подсветкой вхождений. Без хранения зон и позиций сниппет построить нечем.

Как это материализуется в индекс

Индексатор при разборе документа: токенизирует каждую зону, для каждого вхождения пишет хит с меткой зоны и позицией, отдельно накапливает входящие якоря (из обхода ссылочного графа). На выходе — постинги, где зона закодирована в хите (несколько бит), и отдельная зональная статистика (длины зон для нормировки). Рантайм затем читает зоны как признаки.
SEO-врезка. Практические следствия. (1) title и H1 — самые «дешёвые» места, где попадание слов запроса даёт сильный текстовый сигнал; они же формируют заголовок сниппета — пишите их под человека и под запрос одновременно. (2) Якорный текст входящих ссылок описывает вашу страницу чужими словами — релевантные, естественные анкоры полезны, однотипные накрученные — обнуляются или штрафуются. (3) Текст, спрятанный в подвал/«простыни» внизу, имеет малый вес зоны — ключевые формулировки выносите в тело и заголовки. (4) Слова в URL — слабый бонус, не самоцель.
Частые заблуждения
Заблуждение. «Якорный текст хранится у страницы, которая ставит ссылку.» Наоборот: он приписывается цели ссылки — это её описание глазами внешних авторов.
Заблуждение. «Чем больше повторить ключевое слово в title, тем лучше.» Насыщение (Модуль 6) гасит выгоду повторов, а аномальная плотность ловится антиспамом (Модуль 16). Зона title сильна, но не безнаказанна.
Заблуждение. «Зоны — это только про ранжирование.» Зоны и позиции одновременно обслуживают и ранжирование, и сниппеты, и фразовый/зональный поиск (intitle:-подобные операторы).
Лаба / практика

Задача. Построить зональный индекс на мини-корпусе и сравнить ранжирование с/без весов зон.

Вход. 5 коротких HTML-документов с заполненными <title>, H1, телом; и таблица входящих якорей вида target_docID → anchor_text (3–4 якоря).

Шаги.
  1. Токенизируйте каждую зону отдельно; постройте постинги с меткой зоны в хите.
  2. Сложите входящие якоря в зону anchor целевых документов.
  3. Для запроса из 2 слов посчитайте: (a) скор только по телу; (b) взвешенную сумму зон с весами title=3, head=2, body=1, anchor=2.
  4. Сравните два ранжирования, объясните перестановки.
Ожидаемый результат. Документ, где слова запроса в title/якоре, поднимается во взвешенной модели относительно «телесного» скоринга.

Критерий «сделано». Вы показываете, какой документ и почему сместился, и какой вес зоны это вызвал. Время ~50 мин.

Контрольные вопросы
  1. Что такое зона документа и зачем хит несёт её метку?
  2. В чьей индексной записи хранится якорный текст ссылки и почему?
  3. Приведите две роли разбиения на зоны помимо ранжирования.
  4. Почему якорный сигнал нельзя брать «сырым»?
  5. Откуда берётся заголовок сниппета и почему он не всегда равен title?
  6. Являются ли веса зон константами? Что определяет их значения?
Глава 4.3. Документная и хостовая статика: предвычисленные признаки и шов офлайн/онлайн (средний)

Цели обучения

После главы студент сможет:
  • определить понятие «статика» (static / query-independent features) и отличить её от запросозависимых признаков;
  • привести примеры документной и хостовой статики;
  • объяснить главный тезис модуля: робот материализует статику в индекс, а рантайм читает её как факторы — и почему это «шов» офлайн/онлайн;
  • сформулировать критерий «считать офлайн или онлайн» (латентность, стоимость, свежесть, потребность в данных запроса);
  • описать, как статика хранится в индексе (колонки/прямой индекс) и читается каскадом.
Конспект

Что такое статика

Статика (static features) — это признаки документа или хоста, не зависящие от запроса. Их можно вычислить один раз заранее и положить в индекс. Противоположность — запросозависимые (dynamic / query-dependent) признаки: BM25, близость терминов, совпадение зон — их нельзя посчитать без запроса.
Интуиция. Статика — это «паспорт документа», заполненный до того, как кто-то что-то спросил: насколько он авторитетный, свежий, длинный, на каком языке, не спам ли. Когда придёт запрос, рантайму останется только прочитать паспорт — не пересчитывать.
Документная статика (примеры)
  • Ссылочный авторитет документа (PageRank-подобная мера, Модуль 7).
  • Длина документа и длины зон (для нормировки BM25).
  • Язык и регион документа, кодировка.
  • Возраст/дата обновления, частота изменений (для свежести, Модуль 17).
  • Качество/читабельность контента, доля «boilerplate», флаги спама.
  • Тип/жанр документа (форум, новость, товар), наличие структурированных данных.
  • Поведенческая статика, агрегированная офлайн (Модуль 11) — например, сглаженная популярность.
Хостовая статика (примеры)
  • Авторитет хоста/домена, его «надёжность» и история спама.
  • Агрегированные характеристики хоста: средняя свежесть, доля качественного контента, гео-принадлежность.
  • Технические сигналы хоста: доступность, скорость, поддержка безопасного соединения.
Интуиция. Хостовая статика — это «общая репутация издателя», которую наследует каждая его страница, особенно пока у самой страницы мало собственных сигналов (новая страница на авторитетном хосте стартует не с нуля).
Главный тезис: материализация статики — это шов офлайн/онлайн

Код: Выделить всё

            ОФЛАЙН (индексатор, робот)                 ОНЛАЙН (рантайм)
   ┌──────────────────────────────────────┐   шов   ┌──────────────────────────┐
   │ обход → каноник. → разбор →           │  ═════► │ читает постинги +         │
   │ ВЫЧИСЛЯЕТ статику (PageRank, язык,    │ ИНДЕКС  │ читает статику как ФАКТОРЫ │
   │ свежесть, качество, хостовые меры) →  │ (запись)│ (без пересчёта) →         │
   │ МАТЕРИАЛИЗУЕТ её в индекс             │ (чтение)│ каскад L0–L3 ранжирует    │
   └──────────────────────────────────────┘         └──────────────────────────┘
Это центральная идея модуля. Робот (офлайн-конвейер) вычисляет статику и записывает (материализует) её прямо в индекс рядом с документом. Рантайм при обработке запроса не считает её заново — он её читает как готовые факторы и подаёт в каскад ранжирования (Модуль 12). Граница, на которой «посчитанное заранее» превращается в «прочитанное на запросе», и есть шов между офлайн и онлайн.
Пример. PageRank документа считается офлайн на всём ссылочном графе (это глобальная итеративная процедура, Модуль 7) — на запросе её посчитать невозможно (нет времени и нет смысла, она от запроса не зависит). Поэтому её значение материализуется в индекс. На запросе «купить велосипед» рантайм для каждого кандидата просто читает его сохранённый авторитет и складывает с BM25 — секунды офлайн-счёта превращаются в наносекунды чтения.
Критерий «офлайн или онлайн»

Считать признак заранее (материализовать) выгодно, если:
  1. он не зависит от запроса (иначе нельзя предвычислить в принципе);
  2. его дорого считать на запросе (глобальные графовые меры, тяжёлые агрегаты);
  3. он меняется медленнее, чем мы готовы терпеть устаревание (иначе придётся часто перестраивать — конфликт со свежестью, Модуль 17).
Считать на лету (онлайн) приходится, если признак зависит от запроса (BM25, близость) или от контекста сессии/пользователя/гео (часть персонализации, Модуль 18), либо если он обязан быть строго свежим.
Внимание. Материализация — это компромисс со свежестью. Любая статика в индексе — это «снимок» на момент построения сегмента. PageRank в индексе может отставать от реального графа на часы/дни. Поэтому статику пересчитывают и переливают в индекс с тем темпом, который оправдан её скоростью изменения и стоимостью пересборки (см. 4.4 про сегменты и 4.5 про realtime).
Как статика хранится и читается

Статика — это, по сути, прямой индекс признаков: «по docID → вектор/набор признаков». Хранят её обычно по колонкам (columnar): отдельная колонка на признак, выровненная по docID. Колоночное хранение даёт две вещи: (1) дешёвую дозагрузку только нужных факторов; (2) хорошее сжатие однородных колонок (числа близких порядков). На запросе ранний ярус каскада (L0/L1) читает дешёвую статику для грубой отсечки, поздние ярусы (L2/L3) дочитывают дорогие факторы только для выживших кандидатов (Модуль 12).
Инженерная заметка (латентность). Статику кладут «близко» к постингам, чтобы после отбора кандидатов её чтение было локальным (тот же шард/сегмент, желательно тот же узел и горячий кеш). Разъезд постингов и статики по разным хранилищам = дополнительный сетевой round-trip на каждый кандидат = смерть для латентности. Принцип: факторы живут там же, где документы, которые они описывают.
SEO-врезка. «Статика» — это бо́льшая часть того, что в SEO называют «факторами уровня страницы/сайта»: авторитет, качество, скорость, свежесть, язык/гео, репутация домена. Ключевое следствие: эти сигналы вычисляются офлайн и обновляются с задержкой. Поэтому эффект от работ над авторитетом/качеством виден не мгновенно — он проявляется после очередной материализации статики в индекс. Новой странице помогает хостовая статика (репутация домена), пока не накопятся собственные сигналы.
Частые заблуждения
Заблуждение. «Авторитет считается на запросе.» Нет: запросонезависимая статика (авторитет, качество, язык) считается офлайн и читается на запросе. На запросе считается только то, что без запроса посчитать нельзя.
Заблуждение. «Раз признак в индексе — значит, он всегда актуален.» Любая статика — это снимок на момент построения сегмента; между пересборками она устаревает. Свежесть статики — отдельная инженерная забота.
Заблуждение. «Чем больше факторов материализовать, тем лучше.» Каждый материализованный фактор — это место в индексе, трафик чтения и обязательство его пересчитывать. Материализуют то, что дорого считать и стабильно во времени; остальное считают на лету.
Лаба / практика

Задача. Спроектировать раскладку статики и смоделировать шов офлайн/онлайн.

Вход. Список из 8 признаков (например: BM25, близость, авторитет документа, авторитет хоста, длина, язык, возраст, флаг спама).

Шаги.
  1. Разметьте каждый признак как статический (материализуется) или запросозависимый (считается онлайн); обоснуйте.
  2. Для статических укажите ожидаемый темп обновления (минуты/часы/сутки) и почему.
  3. Спроектируйте колоночную раскладку статики по docID; прикиньте, какие колонки читает дешёвый ярус (L0/L1), а какие — дорогой (L2/L3).
  4. Напишите псевдокод обработки запроса: отбор кандидатов → чтение дешёвой статики → отсечка → дочитывание дорогой статики для выживших.
Ожидаемый результат. Корректная классификация (BM25 и близость — онлайн; авторитеты, язык, длина, возраст, спам — офлайн) и слой-распределение чтения.

Критерий «сделано». Для каждого признака сказано, где он считается и почему; псевдокод не читает дорогие факторы для отсечённых кандидатов. Время ~45 мин.

Контрольные вопросы
  1. Дайте определение статики. Почему BM25 не статика, а авторитет — статика?
  2. Сформулируйте тезис «робот материализует, рантайм читает». Где проходит шов офлайн/онлайн?
  3. Назовите три условия, при которых признак выгодно предвычислять офлайн.
  4. Чем материализация статики «платит» за скорость на запросе?
  5. Почему статику хранят колоночно и рядом с постингами?
  6. Что наследует новая страница от хоста и зачем это нужно?
Глава 4.4. Сегментация, ярусы (тиры) индекса, квоты отбора, шардирование (продвинутый)

Цели обучения

После главы студент сможет:
  • объяснить, почему индекс строят из неизменяемых сегментов (immutable segments) и как работают слияния (merges) и удаления;
  • описать ярусную (tiered) организацию индекса и зачем разделять «хороший» и «хвостовой» контент;
  • объяснить квоты отбора (selection quotas) и каскадный обход ярусов до набора достаточного числа кандидатов;
  • сравнить шардирование по документам (document partitioning) и по термам (term partitioning);
  • оценить влияние топологии индекса на латентность, полноту и стоимость.
Конспект

Сегменты: индекс строят из неизменяемых кусков

Гигантский индекс нельзя строить и обновлять как единый монолит. Его собирают из сегментов (segments) — самодостаточных мини-индексов (свой словарь + постинги + статика по подмножеству документов). Ключевое свойство сегмента — неизменяемость (immutability): записанный сегмент не редактируют.

Почему immutable:
  • запись «один раз, последовательно» (append-only) дёшева и не требует блокировок;
  • неизменяемый сегмент тривиально кешируется и реплицируется (он никогда не «протухает» по содержимому);
  • параллельное чтение не конфликтует с записью.
Тогда как же обновлять и удалять?
  • Добавление документа — новый маленький сегмент.
  • Удаление/обновление — не правка на месте, а запись docID в список удалённых (delete list / tombstones); старая версия логически «погашена», физически исчезает при слиянии.
  • Слияние (merge / compaction) — фоновый процесс: несколько мелких сегментов переписываются в один крупный, при этом погашенные документы выбрасываются, постинг-листы пересжимаются.

Код: Выделить всё

поток новых документов
      │  append-only
      ▼
[seg1][seg2][seg3][seg4][seg5] ...   + delete-list (tombstones)
              │   слияние (merge)
              ▼
        [  крупный seg  ]   ← мусор (удалённые) физически выкинут
Интуиция. Это как журнал бухгалтерии: вы не затираете старую запись, вы пишете новую («сторно»). Раз в период журнал «сводят» (merge): итог переписывают начисто, отменённые записи выкидывают.
Инженерная заметка. Слияния — это компромисс. Мелких сегментов много → быстрая индексация, но медленный поиск (на запросе надо опросить много сегментов и объединить результаты) и много места под мусор. Крупных сегментов мало → быстрый поиск, но дорогие слияния и большая задержка появления изменений. Политика слияний (когда и что сливать) — отдельный объект тюнинга.
Ярусы (тиры) индекса

Не весь контент равноценен. Разумно разложить документы по ярусам / тирам (tiers) по ожидаемой ценности:

Код: Выделить всё

Ярус            |  Что внутри                                              |  Свойства
----------------+----------------------------------------------------------+-----------------------------------------------
Верхний (head)  |  авторитетный, качественный, частозапрашиваемый контент  |  малый, горячий, в памяти, опрашивается первым
Средний         |  основная масса нормального контента                     |  крупнее, частично на диске
Нижний (tail)   |  редкий, низкоавторитетный, «хвостовой» контент          |  огромный, холодный, опрашивается редко
Идея: сначала спросить верхний ярус. Для большинства запросов хороший ответ находится там — и тогда нижние ярусы вообще не трогают. Это экономит латентность и вычисления на абсолютном большинстве запросов.
Интуиция. Сначала ищем в «отделе бестселлеров» — там почти всегда есть, что показать. В «архив редкостей» спускаемся, только если в бестселлерах не набралось нужного.
Квоты отбора и каскадный обход ярусов

Квота отбора (selection quota) — целевое число кандидатов, которое отбор должен набрать, прежде чем передать их выше по каскаду ранжирования (Модуль 12). Обход ярусов идёт каскадно:

Код: Выделить всё

нужно набрать ≥ N кандидатов:
  спросить ВЕРХНИЙ ярус → набралось ≥ N и качество достаточно? → СТОП
  иначе → спросить СРЕДНИЙ → добрать
  иначе → спросить НИЖНИЙ → добрать до N (или сколько есть)
Интуиция. Квота — это «сколько кандидатов достаточно, чтобы дальше было из чего выбирать». Набрали из верхнего яруса — отлично, дешёвый запрос. Не набрали (редкий запрос, узкая тема) — спускаемся ниже, платя латентностью за полноту.
Внутри яруса и внутри постинг-листа тоже действуют ограничения: длинные постинг-листы частых слов усекают (берут не весь список, а самые перспективные по статике начала) — это связано с упорядочиванием постингов и ранним завершением (early termination) обхода. Грубо: «не читай миллион документов с частым словом, прочти лучшие десятки тысяч и остановись, если выживших уже хватает».
Внимание. Квоты и усечение — это управляемый компромисс между полнотой (recall) и латентностью/стоимостью. Слишком жёсткие квоты → можно не дотянуться до редкого, но точного ответа (потеря recall на хвостовых запросах). Слишком щедрые → лишняя латентность и вычисления на каждом запросе. Это настраивают и проверяют на A/B (Модуль 19).
Шардирование: как резать индекс между машинами

Один сервер весь индекс не вместит. Его шардируют (shard) — режут на части по машинам. Две базовые стратегии:

1. По документам (document partitioning, локальный индекс). Каждый шард держит полный обратный индекс по своему подмножеству документов. Запрос рассылается на все шарды (scatter), каждый ищет у себя свой топ-k, координатор сливает (gather).

Код: Выделить всё

запрос ──► [шард 1: docs 0..N]   ─┐
       ──► [шард 2: docs N..2N]  ─┼─► координатор: merge top-k
       ──► [шард 3: docs 2N..3N] ─┘
  • Плюсы: простое добавление документов (новый документ → в свой шард), естественная балансировка, отказ шарда = частичная потеря, а не полная.
  • Минусы: каждый запрос идёт на все шарды → латентность определяется самым медленным («хвост латентности», tail latency); глобальные статистики (df, idf) надо согласовывать между шардами.
2. По термам (term partitioning, глобальный индекс). Каждый шард держит постинг-листы для своего подмножества термов (целиком, по всем документам). Запрос идёт только на те шарды, где лежат его термы.
  • Плюсы: запрос трогает мало шардов; целые постинг-листы локальны.
  • Минусы: «горячие» частые слова создают перекос нагрузки; многословные запросы требуют пересылать длинные списки между шардами для пересечения; обновление документа задевает много шардов.
Инженерная заметка. На практике для веб-поиска доминирует шардирование по документам (с репликацией каждого шарда ради пропускной способности и отказоустойчивости) — именно потому, что добавление документов локально, а отказ одного шарда не валит всю систему. Партиционирование по термам встречается реже и в нишах. Подробнее распределённое обслуживание — в Модуле 13.
Внимание (хвост латентности). При scatter-gather на тысячи шардов время ответа диктует самый медленный шард, а не средний. Поэтому реальная борьба идёт не за средний, а за 99-й перцентиль латентности: реплики, хеджированные запросы (послать дубль и взять первый ответ), бюджеты времени с досрочной отсечкой отстающих.
Как всё связано

Сегменты (immutability) дают дешёвую индексацию и кеширование; ярусы и квоты режут стоимость отбора; шардирование даёт масштаб и отказоустойчивость. Вместе они образуют топологию, которую читает рантайм на каждом запросе. Это прямое продолжение шва из 4.3: статика, материализованная офлайн, помогает раскладывать документы по ярусам и упорядочивать постинги для раннего завершения.
SEO-врезка. Практические следствия. (1) Попадание в верхний ярус (авторитетный/качественный контент) означает, что вашу страницу находят дёшево и на широких запросах; слабый «хвостовой» контент достаётся реже и медленнее. (2) Усечение длинных постинг-листов и квоты означают: по очень частым словам в выборку попадают «лучшие по статике» — без авторитета/качества в гипер-конкурентной выдаче можно не дойти до этапа честного ранжирования. (3) Низкая видимость может быть следствием не «плохого текста», а попадания в холодный ярус из-за хостовой/документной статики.
Частые заблуждения
Заблуждение. «Документы в индексе обновляются на месте.» Нет: сегменты неизменяемы; обновление = новый сегмент + tombstone старого; физическая чистка — при слиянии.
Заблуждение. «Индекс ищет по всем документам на каждом запросе.» Обычно нет: ярусы и квоты позволяют остановиться, набрав достаточно кандидатов из верхних ярусов, не трогая хвост.
Заблуждение. «При шардировании по документам запрос быстр, потому что идёт в один шард.» Наоборот — он идёт во все шарды (scatter-gather), и латентность определяет самый медленный из них.
Заблуждение. «Усечение постинг-листа теряет релевантные документы случайно.» Усечение опирается на упорядочивание по статике: отсекается «менее перспективный хвост», а не случайные документы — но риск потери recall на узких запросах реален и его измеряют.
Лаба / практика

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

Вход. Поток операций: add(docID, terms), delete(docID); и набор документов с проставленной «ценностью» (имитация статики), запрос из 1–2 слов, квота N=20.

Шаги.
  1. Реализуйте добавление как новый сегмент (список постинг-листов) и удаление как tombstone.
  2. Реализуйте merge(): слить 2+ сегмента в один, выкинув документы из delete-list.
  3. Разложите документы по 2–3 ярусам по «ценности».
  4. Реализуйте отбор с квотой: опрашивайте ярусы сверху вниз, пока не набрали N кандидатов; считайте, сколько ярусов реально пришлось тронуть.
  5. Сравните «дешёвый» запрос (хватило верхнего яруса) и «дорогой» (пришлось спуститься в хвост).
Ожидаемый результат. После merge удалённые документы не находятся; на «лёгком» запросе тронут только верхний ярус, на «тяжёлом» — все.

Критерий «сделано». Tombstone'нутые документы исчезают после merge; журнал показывает, сколько ярусов опрошено на каждом запросе и почему остановились. Время ~70 мин.

Контрольные вопросы
  1. Почему сегменты делают неизменяемыми? Как тогда удаляют документ?
  2. Что делает слияние (merge) и какой компромисс задаёт политика слияний?
  3. Зачем нужны ярусы индекса и в каком порядке их опрашивают?
  4. Что такое квота отбора и что регулирует её величина?
  5. Сравните шардирование по документам и по термам: плюсы, минусы, когда что.
  6. Почему при scatter-gather важен 99-й перцентиль латентности, а не средний?
  7. Какой риск несёт усечение длинных постинг-листов и как его контролируют?
Глава 4.5. Реалтайм-контур индекса vs батч-построение (продвинутый)

Цели обучения

После главы студент сможет:
  • различать пакетное (batch) построение индекса и реалтайм-контур (realtime / near-real-time indexing);
  • объяснить архитектуру «горячий писчий сегмент + холодные неизменяемые сегменты» и роль журнала (write-ahead log);
  • объяснить понятие задержки индексации (indexing latency) и видимости документа (visibility) в поиске;
  • описать схему, где быстрый realtime-контур и медленный, но качественный batch-контур работают вместе;
  • оценить компромисс «свежесть ↔ полнота статики/качество ранжирования».
Конспект

Два контура построения индекса

Индекс строят в двух режимах одновременно:

Батч-построение (batch). Большие порции документов перерабатываются периодически: пересчитывается дорогая статика (PageRank на всём графе, агрегаты качества), пересжимаются крупные сегменты, выполняются глубокие слияния. Это медленно, но качественно и дёшево на документ (амортизация на огромных объёмах).

Реалтайм-контур (realtime / near-real-time, NRT). Новый или изменившийся документ должен стать находимым за секунды-минуты, а не за часы следующего батча. Для этого есть отдельный быстрый путь, добавляющий документ в индекс почти сразу.
Интуиция. Батч — это «переиздание энциклопедии раз в период»: тщательно, выверенно, но редко. Реалтайм — это «вклейка свежих листов» между переизданиями: быстро и находимо сейчас, пусть и без полной выверки. Новость о событии должна быть находимой через минуты, а не ждать ночной пересборки.
Архитектура: горячий писчий сегмент + журнал

Реалтайм-контур обычно устроен так:

Код: Выделить всё

запись документа
     │
     ▼
[Write-Ahead Log] ──(durability: переживём перезапуск)
     │
     ▼
[ГОРЯЧИЙ in-memory сегмент]  ← сюда мгновенно добавляются постинги, документ становится находимым
     │  периодический flush
     ▼
[маленький неизменяемый on-disk сегмент] ─┐
[другие холодные сегменты] ───────────────┼─► фоновые слияния → крупные сегменты
                                          ┘
  • Журнал (write-ahead log, WAL). Документ сперва пишется в журнал (для надёжности: если узел упадёт, горячий сегмент восстановят из журнала).
  • Горячий сегмент (in-memory). Изменяемая буферная структура в памяти, куда мгновенно попадают новые постинги. Поиск опрашивает её вместе с холодными сегментами, поэтому новый документ находится почти сразу.
  • Flush. Периодически горячий сегмент «застывает» в маленький неизменяемый on-disk сегмент (см. 4.4) и обнуляется.
  • Merge. Маленькие сегменты фоном сливаются в крупные (compaction), там же документу досчитывается дорогая статика.
Инженерная заметка. Поиск по горячему in-memory сегменту и по сжатым холодным сегментам — это два разных пути чтения, которые надо объединить в одном ответе. Часто горячий сегмент хранит постинги в менее сжатом, но быстро-вставляемом виде; холодные — в плотном PForDelta (глава 4.1). Цена realtime — поддержка двух представлений и постоянные flush/merge в фоне.
Задержка индексации и видимость

Задержка индексации (indexing latency) — время от «документ добыт/изменён» до «документ находится по запросу». Видимость (visibility) — момент, с которого документ участвует в поиске.

Тонкий момент — момент видимости относительно flush/commit: документ должен стать видимым атомарно (либо находится со всеми своими термами, либо ещё нет — не «половина термов»). Поэтому видимость привязывают к точке коммита горячего сегмента, а удаление — к применению tombstone (глава 4.4): пока tombstone не виден, старая версия ещё находится.
Внимание. Реалтайм-контур ускоряет видимость, но жертвует качеством сигналов. Только что добавленный документ ещё не имеет полной статики: его ссылочный авторитет не пересчитан на свежем графе, поведенческие сигналы не накоплены, агрегаты качества грубые. То есть он находится, но ранжируется по неполному набору факторов, пока следующий батч/merge не «дообогатит» его статикой. Это прямое следствие шва из 4.3: realtime материализует то, что можно посчитать быстро, а дорогую статику доливает батч.
Как два контура работают вместе

Код: Выделить всё

            БЫСТРО, неполные сигналы          МЕДЛЕННО, полная статика
   ┌───────────────────────────────┐     ┌──────────────────────────────┐
   │ REALTIME: документ виден за    │     │ BATCH: пересчёт PageRank,     │
   │ секунды-минуты, базовая статика│ ──► │ качества, поведения; глубокие │
   │ (язык, длина, дешёвые флаги)   │     │ слияния; полная материализация│
   └───────────────────────────────┘     └──────────────────────────────┘
        видимость СЕЙЧАС                       качество ПОТОМ
Документ сначала входит через realtime-контур (находится почти сразу, ранжируется по дешёвым факторам), а затем батч-контур постепенно «дообогащает» его дорогой статикой и переселяет в правильный ярус (глава 4.4). С точки зрения свежести выдачи (Модуль 17) это и есть механизм, который позволяет показывать свежее, не дожидаясь полной офлайн-пересборки.
Заблуждение. «Realtime-индекс заменяет батч.» Нет: они дополняют друг друга. Realtime даёт свежесть и видимость; батч даёт качество статики, плотное сжатие и дешёвую амортизированную стоимость. Без батча realtime-сегменты не сольются и не получат полную статику; без realtime свежее ждало бы следующей пересборки.
SEO-врезка. Почему «проиндексировалось» и «хорошо ранжируется» — это разные события, разнесённые во времени. Новая страница может стать находимой быстро (realtime-контур), но её сильные сигналы — авторитет, качество, поведение — материализуются позже, в батч-цикле. Отсюда типичная картина: страница «появилась в индексе», но позиции «дозревают» дни/недели по мере дообогащения статикой и пересчёта ссылочного графа. Частые правки страницы перед каждым flush не ускоряют «дозревание» дорогих сигналов.
Частые заблуждения
Заблуждение. «Документ индексируется мгновенно и сразу ранжируется в полную силу.» Видимость может быть быстрой (realtime), но полнота сигналов приходит с батч-циклом — это разные моменты времени.
Заблуждение. «Если документ виден, значит, все его термы уже в индексе.» Видимость атомарна по коммиту: либо документ со всеми термами виден, либо ещё нет; «полудокументов» в корректной системе быть не должно.
Заблуждение. «Realtime-контур не нуждается в надёжном хранении — это же временный буфер.» Нет: до flush'а данные горячего сегмента защищает журнал (WAL), иначе перезапуск узла терял бы свежие документы.
Лаба / практика

Задача. Смоделировать realtime-контур поверх сегментной модели из 4.4.

Вход. Поток событий: add(doc), update(doc), delete(docID), flush(), merge(), search(query); у каждого документа — «дешёвая статика» (есть сразу) и «дорогая статика» (появляется только после merge).

Шаги.
  1. Реализуйте горячий in-memory сегмент: add/update/delete меняют его мгновенно; search опрашивает горячий + холодные сегменты.
  2. По flush() застывайте горячий сегмент в неизменяемый и заводите новый пустой; фиксируйте видимость по коммиту.
  3. По merge() сливайте сегменты, применяйте tombstones и досчитывайте дорогую статику.
  4. Измерьте задержку индексации: сколько событий между add(doc) и первым search, который его находит.
  5. Покажите документ, который находится сразу после add, но получает дорогую статику (и поднимается в ранжировании) только после merge.
Ожидаемый результат. Документ находится сразу после add (через горячий сегмент); его ранжирование улучшается после merge, когда дорогая статика материализована.

Критерий «сделано». Вы демонстрируете разрыв во времени между «стал виден» и «получил полную статику» и объясняете его через шов офлайн/онлайн. Время ~70 мин.

Контрольные вопросы
  1. Чем отличаются batch- и realtime-контуры по скорости и качеству сигналов?
  2. Зачем нужен горячий in-memory сегмент и почему поиск опрашивает его вместе с холодными?
  3. Какова роль журнала (WAL) в реалтайм-контуре?
  4. Что такое задержка индексации и видимость? Почему видимость должна быть атомарной?
  5. Почему свежий документ «находится», но ранжируется по неполным факторам?
  6. Почему realtime не отменяет batch, а дополняет его?
Итоги модуля
  1. Обратный индекс — это словарь term → постинг-лист; постинги хранятся отсортированными по docID и содержат частоты, позиции и хиты (с меткой зоны), что и даёт булев поиск, скоринг и фразы.
  2. Сжатие (delta-кодирование, varint, блочный PForDelta) экономит не столько диск, сколько память и латентность чтения; на горячем пути скорость декодирования важнее предельной степени сжатия.
  3. Зоны документа (title, заголовки, тело, якорный текст, URL) несут разный вес; якорный текст приписывается документу-цели и описывает его «чужими словами». Зоны обслуживают и ранжирование, и сниппеты.
  4. Статика — это запросонезависимые признаки документа и хоста (авторитет, качество, свежесть, язык). Их вычисляют офлайн и материализуют в индекс.
  5. Шов офлайн/онлайн: робот материализует статику в индекс, рантайм читает её как готовые факторы. Материализуют то, что дорого считать и стабильно во времени; запросозависимое (BM25, близость) считают на лету.
  6. Индекс строится из неизменяемых сегментов; обновление = новый сегмент + tombstone, физическая чистка — при слиянии (merge/compaction).
  7. Ярусы и квоты отбора позволяют не опрашивать весь индекс: сначала верхний ярус, спуск в хвост — только при нехватке кандидатов; длинные постинг-листы усекают по статике.
  8. Шардирование по документам (scatter-gather) доминирует в вебе ради локального добавления и отказоустойчивости; цена — хвост латентности (важен 99-й перцентиль).
  9. Realtime-контур (горячий in-memory сегмент + WAL) даёт видимость за секунды; batch-контур даёт полную статику, плотное сжатие и амортизированную стоимость. Видимость и полнота сигналов — разнесённые во времени события.
Глоссарий модуля
  • Обратный индекс (inverted index) — структура term → список документов с термом.
  • Прямой индекс (forward index) — документ → его термы/признаки; так же хранят и статику по docID.
  • Постинг-лист (posting list) — список вхождений терма (отсортирован по docID).
  • Постинг (posting) — одна запись: docID (+ частота, позиции, хиты).
  • Хит (hit) — упакованная запись об одном вхождении терма (позиция + зона + признаки).
  • Зона (zone / field) — именованная область документа (title, тело, заголовки, якорь, URL).
  • Якорный текст (anchor text) — текст входящей ссылки; индексируется у документа-цели.
  • Delta/gap-кодирование — хранение разностей соседних отсортированных значений вместо самих значений.
  • Varint (VByte) — кодирование целого байтами по 7 бит данных + бит продолжения.
  • Bit-packing (frame of reference) — упаковка блока чисел одинаковой битовой шириной.
  • PForDelta — bit-packing с выносом редких выбросов в исключения (patches).
  • Skip-указатели (skip pointers) — разрежённый индекс внутри постинг-листа для быстрого advance.
  • Статика (static / query-independent features) — запросонезависимые признаки (авторитет, качество, свежесть, язык).
  • Материализация статики — запись предвычисленных признаков в индекс на офлайн-стороне.
  • Шов офлайн/онлайн — граница, где предвычисленное офлайн читается рантаймом как факторы.
  • Сегмент (segment) — неизменяемый самодостаточный мини-индекс.
  • Tombstone (delete list) — пометка docID как удалённого без физической правки.
  • Слияние (merge / compaction) — переписывание мелких сегментов в крупный с выбросом мусора.
  • Ярус / тир (tier) — уровень индекса по ценности контента (head / средний / tail).
  • Квота отбора (selection quota) — целевое число кандидатов для передачи в каскад ранжирования.
  • Раннее завершение (early termination) — остановка обхода постингов при достаточном числе кандидатов.
  • Шардирование (sharding) — разрезание индекса между машинами (по документам или по термам).
  • Scatter-gather — рассылка запроса на все шарды и слияние их топ-k.
  • Хвост латентности (tail latency) — медленные перцентили (например, 99-й) времени ответа.
  • Batch-построение — периодическая пакетная пересборка индекса и статики.
  • Realtime/NRT-контур — быстрый путь, делающий документ находимым за секунды-минуты.
  • Журнал (write-ahead log, WAL) — лог для надёжности горячего сегмента.
  • Задержка индексации (indexing latency) — время от добычи/изменения документа до его находимости.
  • Видимость (visibility) — момент, с которого документ участвует в поиске (атомарно по коммиту).
Связи с другими модулями
  • Опирается на: Модуль 1 (термин, документ, токенизация, модель IR), Модуль 3 (идентичность документа, docID, дедупликация — что вообще попадает в индекс).
  • Питает: Модуль 6 (текстовая релевантность читает постинги, частоты, позиции, зоны — основа BM25/BM25F), Модуль 7 (ссылочный авторитет — пример дорогой статики, считаемой офлайн), Модуль 8 (таксономия факторов: статика vs динамика), Модуль 12 (каскад ранжирования читает кандидатов и статику; квоты и ярусы определяют отбор), Модуль 13 (распределённое обслуживание: шарды, реплики, scatter-gather, хвост латентности), Модуль 17 (свежесть и реалтайм: realtime-контур — её механизм).
  • Перекликается с: Модуль 16 (антиспам фильтрует якорный сигнал и аномалии), Модуль 18 (персонализация — пример того, что нельзя материализовать заранее), Модуль 19 (A/B-проверка квот, политик слияний, кодеков).
Материалы для углубления
  • Классические тексты по архитектуре обратного индекса и обработке постинг-листов (булев и ранжированный поиск, пересечение списков, skip-списки).
  • Обзоры и сравнения кодеков сжатия целых для IR (variable-byte, frame-of-reference, PForDelta, современные SIMD-ориентированные схемы) — с замерами «степень сжатия ↔ скорость декодирования».
  • Литература по взвешенным зонам и fielded-ранжированию (модели вида BM25F) — как вход для главы 4.2 и Модуля 6.
  • Материалы по запросонезависимым (static) признакам и упорядочиванию документов для раннего завершения обхода.
  • Работы по сегментным append-only индексам, политикам слияний (compaction) и near-real-time индексации (горячий сегмент + WAL).
  • Обзоры по распределённому поиску: партиционирование по документам vs по термам, scatter-gather, борьба с хвостом латентности (репликация, хеджированные запросы).
👍1 ❤️4 🔥1 😄 🤔3
Аватара пользователя
Spacehead
Сообщения: 1
Зарегистрирован: 13 май 2026, 00:38

Re: Индексирование и хранение

Сообщение Spacehead »

вопрос по хранению постингов - вы храните позиции токенов прямо в инвертированном индексе или отдельным позиционным индексом? у нас фразовые запросы взлетели по размеру индекса раза в три, когда добавили positions
👍2 ❤️ 🔥 😄 🤔
Аватара пользователя
un23661
Сообщения: 1
Зарегистрирован: 12 май 2026, 14:16

Re: Индексирование и хранение

Сообщение un23661 »

наконец дошло почему каноникализация идет до индексирования а не после. если бы дубли попадали в постинг-листы, df у терминов был бы кривой и весь idf поплыл бы. логично что Модуль 3 раньше
👍2 ❤️ 🔥 😄 🤔
Аватара пользователя
dockerops
Сообщения: 1
Зарегистрирован: 22 май 2026, 21:09

Re: Индексирование и хранение

Сообщение dockerops »

по сжатию добавлю из опыта - delta-кодирование docid плюс varbyte дало нам почти 4x усадку постинг-листов, но на коротких списках накладные расходы декодинга съедают часть профита. для редких терминов мы их вообще не жмем
👍 ❤️ 🔥 😄 🤔1
Ответить
← Предыдущая глава
Идентичность документа: каноникализация и дубли
Следующая глава →
Обработка и понимание запроса

Все главы курса «Поисковые системы: индексирование, факторы ранжирования и формирование выдачи»

Поделиться темой: ✈ Telegram VK

Вернуться в «Поисковые системы: индекс, факторы, выдача»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 2 гостя