Обновление поискового индекса
Поисковый индекс описывает конкретную версию библиотеки. Если статья изменилась, новый HTML ещё не означает, что браузер уже получил обновлённые поисковые данные. При удалении материала старая карточка может продолжать появляться, а при добавлении нового урока он может отсутствовать.
В этом уроке разделим формирование новой версии и её выбор читателем. Для статического варианта подготовим файл с именем по содержимому и небольшой manifest. В одной открытой поисковой сессии будем использовать согласованный снимок; обновление сессии загрузит актуальный указатель.
Какие изменения различать
Добавление создаёт новый ID, изменение сохраняет существующий, удаление убирает запись. Смена заголовка или рубрики не требует нового ID и публичного URL. Настоящий переезд адреса имеет отдельную карту соответствий и не должен происходить автоматически из-за поискового экспорта.
Черновик исключается до формирования данных. Если ранее опубликованная статья стала черновиком, новая публичная версия индекса не содержит её текст. Однако уже скачанные старые данные нельзя отозвать из памяти посетителя простым скрытием карточки.
Это ещё раз показывает, почему поисковый JSON не подходит для хранения закрытых материалов. Наш индекс содержит публичную библиотеку, а режим draft регулирует выпуск будущих текстов. Он не является механизмом контроля доступа к уже раскрытой информации.
Полная пересборка для небольшого корпуса проще инкрементальной: новый файл отражает текущее множество опубликованных документов. Не нужно угадывать, какие строки следует удалить из прежнего массива.
Файл с версией содержимого
Экспортёр уже вычисляет хеш упорядоченных документов. Используем его в имени файла. Тогда одно имя обозначает одно содержимое, а небольшой manifest указывает, какую версию выбирать новой сессии.
В export.py замените запись documents.json в конце функции следующим фрагментом. Переменные version и payload определены в предыдущем коде.
public = ROOT / "public"
public.mkdir(parents=True, exist_ok=True)
filename = f"documents-{version}.json"
target = public / filename
target.write_text(
json.dumps(payload, ensure_ascii=False, indent=2),
encoding="utf-8",
)
pointer = {"schemaVersion": 1, "version": version, "file": filename}
temporary = public / "manifest.tmp"
temporary.write_text(json.dumps(pointer), encoding="utf-8")
temporary.replace(public / "manifest.json")
Сначала записывается файл данных, затем переключается указатель. Для локальной файловой системы замена указателя уменьшает промежуточные состояния. Публикация на удалённый хостинг требует такого же порядка на принимающей стороне; последовательная загрузка файлов не становится атомарной только из-за этой программы.
Не удаляйте старую версию до того, как завершился переход новых запросов. Посетитель мог уже получить прежний manifest и ещё не скачать соответствующий файл. Для хранения старых версий нужна ограниченная политика, связанная с кешированием и сроком сессий.
Файл с хешем можно долго кешировать, если его содержимое действительно неизменно. Manifest, наоборот, должен проверяться на актуальность. Конкретные HTTP-заголовки зависят от сервера; название файла не устанавливает их автоматически.
Чтение manifest
В загрузчике Worker вместо прямого обращения к documents.json сначала получите указатель. Ниже полный вспомогательный загрузчик данных; построение индекса выполняется после его результата.
async function loadPayload() {
const pointerResponse = await fetch("./manifest.json", { cache: "no-cache" });
if (!pointerResponse.ok) throw new Error("Нет manifest");
const pointer = await pointerResponse.json();
if (pointer.schemaVersion !== 1 ||
!/^documents-[a-f0-9]{16}\.json$/.test(pointer.file)) {
throw new Error("Неверный manifest");
}
const response = await fetch("./" + pointer.file);
if (!response.ok) throw new Error("Нет данных версии");
const payload = await response.json();
if (payload.schemaVersion !== 1 || payload.version !== pointer.version ||
!Array.isArray(payload.documents)) {
throw new Error("Версии не согласованы");
}
return payload;
}
Значение file проверяется по ожидаемому шаблону. Указатель не получает возможности загрузить произвольный внешний URL. Дополнительно версия внутри файла сравнивается с manifest.
cache: "no-cache" означает режим повторной проверки HTTP-кеша, а не обязательное отключение любого хранения. Его поведение описано в справке Request.cache. Настройки сервера остаются частью общего договора.
В учебной версии Worker закрепляет полученный индекс до перезагрузки страницы. Поэтому две страницы одной выдачи используют один корпус. Если требуется автоматическое обновление долгой сессии, нужно явно сообщить о новой версии и сбросить пагинацию.
Удаление и переименование
Для наблюдения уберите опубликованный документ из исходников и сформируйте новую версию. В новой сессии его ID должен отсутствовать. Если карточка остаётся, сравните manifest, загруженный файл и версию в ответе Worker.
Изменение заголовка сохраняет ID и URL. Новая карточка показывает новое название, а контрольный запрос по прежнему может вести к тому же материалу, если текст и правила это позволяют. Не нужно удалять и создавать документ только ради изменения одного поля.
При переезде публичного пути обновляется URL записи, а сайт сохраняет переход с прежнего адреса по принятой политике. Поисковый индекс не заменяет серверный редирект. Он лишь начинает выдавать предпочитаемую новую цель.
Для большого корпуса инкрементальное обновление может быть полезным, но оно усложняет удаление и согласованность. Нужны журнал изменений, проверка версии и возможность пересобрать всё из источника. Простая последовательность «дописать новые записи» не убирает исчезнувшие документы.
Наблюдение и возврат
Сохраните идентификатор прежней версии и причину новой. Если новая выгрузка повреждена, указатель можно вернуть на сохранённый согласованный файл. Это возврат поисковых данных, а не автоматическое возвращение всех страниц сайта.
Техническая проверка версии не доказывает релевантность. JSON может быть согласованным, но содержать ошибочные рубрики или потерянные слова. Поэтому после обновления сохраняются контрольные запросы предыдущего урока.
При публикации сначала должны стать доступными сами новые материалы. Карточка, ведущая к ещё отсутствующей статье, создаёт видимую ошибку даже при правильном индексе. Для статического сайта нужен согласованный выпуск страниц и поисковых данных.
Не выставляйте текущую дату всем документам только потому, что индекс пересобран. Если позже появится поле даты, определите, обозначает ли оно изменение содержания или технический выпуск. Эти события имеют разный смысл для сортировки.
Рассмотрим две открытые вкладки. Первая уже построила индекс версии A. После выпуска версии B вторая получает новый manifest и строит B. Это допустимое состояние выбранной модели сессий: каждая вкладка работает со своим согласованным снимком. Ошибка возникла бы, если в первой вкладке число страниц вычислялось по A, а карточки следующей страницы выбирались из B.
Для диагностики достаточно сопоставить версию ответа с именем загруженного файла. Не делайте вывод о свежести только по дате загрузки app.js: программный код и содержимое библиотеки обновляются независимо. Также возврат manifest на A не меняет уже построенный B в памяти второй вкладки. Политика перезагрузки остаётся отдельным решением интерфейса.
Теперь обновление описано как переход между снимками, а не как случайная перезапись одного большого файла. Следующий урок использует размер, задержки и требования к свежести, чтобы решить, достаточно ли браузерной реализации.