Все углублённые блоки
Углублённые блоки
Блок 14 / Углублённый разбор

Поиск и лента: производные представления данных

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

Организатор сделал встречу приватной. Из основной базы она уже исчезла для посторонних, но поисковый индекс ещё содержит старый документ. Можно ли просто ждать обновления индекса? Если название и фрагмент описания доступны без проверки разрешений, приложение продолжает раскрывать данные. Задержка индекса и задержка отзыва доступа требуют разных договоров.

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

Источник истины и индекс отвечают на разные вопросы

В основной базе хранится событие: id, tenant_id, название, видимость, версия и признак удаления. Поиск хранит удобную для чтения копию. Эта копия может не содержать всех полей и может отставать. Она помогает найти кандидатов, но не обязана определять, можно ли показать их текущему пользователю.

Для точного поиска слов нужен обратный индекс. Вместо «документ → слова» храним «слово → идентификаторы документов». Запрос go сети пересекает два множества; запрос с логическим OR объединяет их. Регистр приводим к выбранной нормальной форме, текст делим на токены. Для русского языка этого мало: словоформы, дефисы и неоднозначные слова требуют отдельного решения. Нельзя обещать морфологический поиск, реализовав только lower().

Далее ранжируем кандидатов, например по числу совпадений и свежести. Это учебная функция, а не доказанная релевантность. Показатель качества задаём набором запросов с ручными оценками результатов. Время ответа и релевантность — разные измерения: быстрый пустой ответ может быть бесполезен.

Для автодополнения важна другая модель доступа: префикс → ограниченный список предложений. Trie, FST или таблица префиксов дают разные расходы памяти и обновления. Популярность не должна раскрывать приватные запросы малой группы. Объединять результаты по tenant можно только при договоре общедоступности.

Обновление проходит по протоколу

Пусть событие меняется с v7 на v8. В той же транзакции сохраняем исходящую запись. Потребитель получает {id, version:8, operation:update} и обновляет индекс. Если позже придёт v7, он не должен вернуть старое название.

Вариант с полным документом: применяем событие только при версии выше текущей. Одинаковая версия означает повтор; разные payload при одной версии означают нарушение протокола, которое нельзя молча скрыть. Вариант с уведомлением: потребитель по id читает актуальное состояние источника. Тогда исчезновение строки и ошибка чтения должны различаться; временный сбой не является командой удаления.

Удаление требует версии или устойчивого tombstone — отметки удаления. Иначе поздняя v7 воскресит документ, который v8 уже удалил. Срок хранения tombstone зависит от максимальной задержки повторной доставки и процедуры полной переиндексации. Если такой верхней границы нет, нужен протокол, который умеет сверяться с актуальным источником.

Подтверждение записи в журнал индекса не обязательно означает видимость в следующем поиске. Например, Elastic отдельно описывает near-real-time видимость. Для собственного продукта сформулируйте обещание: «обычное обновление появляется в поиске за 5 секунд с заданной долей успеха». Отзыв доступа может требовать немедленной проверки через актуальный источник, даже когда остальные поля остаются устаревшими.

Разрешения проверяем до выдачи чувствительных данных

Поиск возвращает список id. Сервер группой читает доступность объектов и только затем формирует результат. Если авторизационный источник недоступен, приватный документ не показываем. Этот отказ ухудшает доступность поиска, но сохраняет выбранное правило конфиденциальности. Если права кэшируются, нужно явно определить максимальную задержку отзыва, версию политики и способ её смены.

Сниппет, счётчик результатов и подсказки тоже могут раскрывать существование объекта. Проверка лишь при открытии полной карточки не защищает уже выданный текст. Для больших выборок фильтрация после top-k может вернуть мало результатов: понадобится повторная выборка кандидатов с ограничением работы или индексирование разрешений с проверяемой свежестью. Не отправляйте браузеру запрещённые документы «для скрытия на клиенте».

Лента: выполнять работу при записи или при чтении

В варианте fan-out on write новый пост заранее попадает в списки получателей. Чтение дешёвое, но запись популярного автора запускает много работы. В варианте fan-out on read сервер при открытии ленты собирает публикации авторов, на которых подписан читатель. Запись проще, а чтение объединяет несколько источников.

Возьмём учебные числа. Обычные авторы публикуют суммарно 200 постов/с, среднее число получателей — 80. Получаем 16 000 вставок/с в производные списки, без учёта индексов, повторов и реплик. Автор с 2 миллионами подписчиков создаёт один пост: при мощности 50 000 вставок/с идеальный нижний предел рассылки — 40 секунд. Очередь не отменяет эту работу.

Гибрид заранее доставляет обычные публикации, а редкие публикации очень популярных авторов добавляет при чтении. Порог выбираем по распределению подписчиков, частоте публикаций и бюджету чтения. Фиксированное «больше 10 000 — знаменитость» без измерений не обосновано. Отписка, блокировка и удаление должны учитываться при выдаче старых элементов; принадлежность id производному списку не означает разрешение на чтение.

Переиндексация без потери обновлений

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

Курсор выдачи должен определять порядок и границу. При сортировке по времени добавляем уникальный id как вторичный ключ. Обещание «все записи ровно один раз за несколько страниц» требует стабильного снимка или дополнительных ограничений: изменяющаяся лента с курсором сама по себе этого не гарантирует.

Практика и проверка

Создайте таблицу событий для документа 42: публичная v1, переименование v2, приватная v3, удаление v4. Доставьте их в порядке 2, 1, 2, 4, 3. Отдельно задайте актуальное разрешение после v3. Запишите состояние индекса и ответ поиска после каждого шага.

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

В бесплатном архиве лабораторий есть cases.py: команда python3 -m unittest -v test_cases проверяет переупорядочивание, удаление и фильтрацию доступа на небольшой модели. Это проверка протокола модели, а не производительности промышленного поискового движка.

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

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

Проверьте себя

Событие стало приватным в источнике, а поисковый индекс ещё содержит публичную v7. Доступ нужно отозвать сразу. Где проверить право до выдачи сниппета?

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