Введение
В V01 для каждой справочной статьи сохранялся один вектор. В реальных статьях содержимое не остаётся неизменным: инструкции обновляются, заголовки исправляются, а устаревшие страницы выводятся из использования. Поисковый индекс должен учитывать этот жизненный цикл. Иначе он может возвращать устаревшие ответы, даже если исходный веб-сайт уже обновлён.
Cloudflare Vectorize предоставляет три связанные операции записи:
- insert добавляет новый ID вектора и не должен незаметно заменять уже существующий ID;
- upsert означает «обновить или вставить» и заменяет вектор и метаданные для указанного ID;
- delete by ID выводит выбранные записи из использования без полного пересоздания индекса.
Вы создадите три синтетических документа, замените обновлённую статью о пароле с помощью upsert, удалите устаревшую статью о счёте и убедитесь, что несвязанная статья о загрузке файла не изменилась. Каждая операция записи возвращает асинхронный ID мутации, поэтому вы дождётесь точного состояния, а не будете считать, что принятая запись уже доступна для чтения.
Это вторая лабораторная работа по Vectorize. Если вы открыли её напрямую, сначала выполните Подключение LabEx к вашей учётной записи Cloudflare, а затем V01, чтобы освоить совместимость индекса, ID документов и видимость мутаций.
Vectorize доступен в Workers Free. В этой лабораторной работе хранятся не более трёх небольших векторов размерности 384, выполняются ограниченные чтения и не вызывается модель ИИ, поэтому Workers Paid и Workers AI Neurons не требуются.
В процессе настройки устанавливаются Node.js 22.22.0 и локальная версия Wrangler 4.132.0 в /home/labex/project/document-lifecycle-index. Настройка предоставляет независимые проверки только для чтения, но не авторизует Wrangler, не создаёт индекс, не записывает векторы и не изменяет вашу учётную запись Cloudflare.
Авторизация нового индекса жизненного цикла документов
На этом шаге вы авторизуете новую виртуальную машину, зафиксируете используемую учётную запись и создадите уникальную локальную конфигурацию для одного временного индекса.
Перейдите в подготовленный проект и проверьте зафиксированную версию CLI:
cd /home/labex/project/document-lifecycle-index
npx wrangler --version
Ожидаемый результат — 4.132.0. Авторизуйте ту же ограниченную учётную запись и доступ к ресурсам Workers, которые использовались в V01:
npx wrangler login --device --browser=false --scopes account:read user:read workers:write
npx wrangler whoami --json
Убедитесь, что указано loggedIn: true, определите используемую учебную учётную запись и сгенерируйте уникальное имя:
RUN="labex-c08-v02-$(openssl rand -hex 6)"
printf '%s\n' "$RUN"
Замените YOUR_ACCOUNT_ID фактическим ID этой учётной записи:
cat > wrangler.jsonc <<JSON
{
"\$schema": "./node_modules/wrangler/config-schema.json",
"name": "$RUN-tools",
"account_id": "YOUR_ACCOUNT_ID",
"compatibility_date": "2026-09-16",
"vectorize": [
{ "binding": "DOCUMENTS", "index_name": "$RUN", "remote": true }
]
}
JSON
Новый индекс не зависит от V01. Повторное использование полученных знаний не означает зависимости от виртуальной машины или облачного ресурса предыдущей лабораторной работы.
Создание текущего набора документов
На этом шаге вы создадите индекс и вставите три записи, представляющие текущий центр справки до изменения или вывода документов из использования.
Создайте тот же контракт для косинусного сходства с размерностью 384, который используется моделью эмбеддингов BGE Small:
npx wrangler vectorize create "$RUN" --dimensions=384 --metric=cosine --update-config=false
Создайте повторно используемый ограниченный скрипт ожидания. Три совпадающих чтения защищают видимый для учащегося результат от кратковременно устаревшей реплики:
cat > scripts/wait-for-vectorize.mjs <<'JS'
import { execFileSync } from "node:child_process";
const [indexName, mutationId, expectedCountText] = process.argv.slice(2);
const expectedCount = Number(expectedCountText);
const wrangler = "./node_modules/wrangler/bin/wrangler.js";
let consecutiveMatches = 0;
for (let attempt = 1; attempt <= 120; attempt += 1) {
const output = execFileSync(process.execPath, [wrangler, "vectorize", "info", indexName, "--json"], { encoding: "utf8" });
const info = JSON.parse(output);
if (info.processedUpToMutation === mutationId && info.vectorCount === expectedCount) {
consecutiveMatches += 1;
} else {
consecutiveMatches = 0;
}
if (consecutiveMatches === 3) {
console.log(`mutation ${mutationId} is consistently readable with ${expectedCount} vectors`);
console.log(JSON.stringify(info, null, 2));
process.exit(0);
}
await new Promise((resolve) => setTimeout(resolve, 2000));
}
throw new Error(`mutation ${mutationId} was not readable within four minutes`);
JS
Сгенерируйте три детерминированных вектора документов. Поле revision позволяет легко распознать последующую замену, а полный объект метаданных представляет данные, которые понадобились бы поисковому приложению после обновления исходного документа.
cat > scripts/create-seed.mjs <<'JS'
import { writeFileSync } from "node:fs";
const DIMENSIONS = 384;
const documents = [
{ id: "password-reset", axis: 0, category: "account", title: "Reset your password" },
{ id: "upload-pdf", axis: 1, category: "files", title: "Upload a PDF" },
{ id: "billing-receipt", axis: 2, category: "billing", title: "Download a billing receipt" }
];
const rows = documents.map((document) => {
const values = Array(DIMENSIONS).fill(0);
values[document.axis] = 1;
return {
id: document.id,
values,
metadata: {
category: document.category,
published: true,
title: document.title,
revision: 1,
model: "@cf/baai/bge-small-en-v1.5",
pooling: "cls"
}
};
});
writeFileSync("vectors/seed.ndjson", rows.map(JSON.stringify).join("\n") + "\n");
console.log(`prepared ${rows.length} current documents`);
JS
node scripts/create-seed.mjs
Вставьте только новые ID, сохраните полный результат и дождитесь фактического применения мутации:
set -o pipefail
npx wrangler vectorize insert "$RUN" --file=vectors/seed.ndjson 2>&1 | tee .labex/seed-output.txt
Продолжайте только после того, как Wrangler сообщит о постановке в очередь трёх векторов и вернёт ID мутации. Ошибка аутентификации или сети не даёт окончательного результата — исправьте её до ожидания.
SEED_MUTATION_ID=$(sed -nE 's/.*Mutation changeset identifier: ([0-9a-f-]{36}).*/\1/p' .labex/seed-output.txt | tail -n 1)
if [ -z "$SEED_MUTATION_ID" ]; then
printf '%s\n' 'No seed mutation ID was returned; fix the insert error before waiting.' >&2
else
node scripts/wait-for-vectorize.mjs "$RUN" "$SEED_MUTATION_ID" 3
fi
npx wrangler vectorize list-vectors "$RUN" --count=10
В списке должны присутствовать три стабильных ID приложений. Здесь подходит insert, потому что это новые записи. На следующем шаге вы намеренно замените один существующий ID.
Обновление статьи о сбросе пароля с помощью upsert
На этом шаге вы замените вектор и метаданные для password-reset, сохранив его стабильный ID.
Операция upsert вставляет отсутствующий ID или заменяет существующий. Замена полезна, когда изменился исходный документ, но при этом необходимо отправить полный набор требуемых метаданных. Не следует предполагать, что поля, отсутствующие в новой записи, сохранятся.
Создайте редакцию 2 с другой детерминированной осью и обновлённым заголовком, сохранив все актуальные поля метаданных:
cat > scripts/create-update.mjs <<'JS'
import { writeFileSync } from "node:fs";
const values = Array(384).fill(0);
values[3] = 1;
const updated = {
id: "password-reset",
values,
metadata: {
category: "account",
published: true,
title: "Reset an expired password",
revision: 2,
model: "@cf/baai/bge-small-en-v1.5",
pooling: "cls"
}
};
writeFileSync("vectors/password-update.ndjson", JSON.stringify(updated) + "\n");
console.log("prepared password-reset revision 2");
JS
node scripts/create-update.mjs
Отправьте замену и сохраните точный ID мутации:
set -o pipefail
npx wrangler vectorize upsert "$RUN" --file=vectors/password-update.ndjson 2>&1 | tee .labex/upsert-output.txt
UPSERT_MUTATION_ID=$(sed -nE 's/.*Mutation changeset identifier: ([0-9a-f-]{36}).*/\1/p' .labex/upsert-output.txt | tail -n 1)
if [ -z "$UPSERT_MUTATION_ID" ]; then
printf '%s\n' 'No upsert mutation ID was returned; fix the write error before waiting.' >&2
else
node scripts/wait-for-vectorize.mjs "$RUN" "$UPSERT_MUTATION_ID" 3
fi
npx wrangler vectorize get-vectors "$RUN" --ids password-reset > .labex/password-after-upsert.txt
node - <<'JS'
const text = require("fs").readFileSync(".labex/password-after-upsert.txt", "utf8");
const [row] = JSON.parse(text.slice(text.indexOf("[")));
console.table([{ id: row.id, dimensions: row.values.length, changedAxis: row.values[3], title: row.metadata.title, revision: row.metadata.revision }]);
JS
Ожидайте тот же ID, размерность 384, значение оси 3, равное 1, обновлённый заголовок и редакцию 2. Общее количество остаётся равным трём, потому что upsert заменил одну запись, а не добавил четвёртый документ.
Вывод одного документа из использования без пересоздания индекса
На этом шаге вы удалите устаревшую статью о счёте по стабильному ID и убедитесь, что обновлённая статья о пароле и неизменённая статья о загрузке файла сохранились.
Удаление по ID точнее, чем удаление индекса целиком: контракт индекса и все несвязанные записи остаются на месте. Отправьте только ID устаревшей записи:
set -o pipefail
npx wrangler vectorize delete-vectors "$RUN" --ids billing-receipt 2>&1 | tee .labex/delete-output.txt
Дождитесь мутации удаления и количества, равного двум:
DELETE_MUTATION_ID=$(sed -nE 's/.*Mutation changeset identifier: ([0-9a-f-]{36}).*/\1/p' .labex/delete-output.txt | tail -n 1)
if [ -z "$DELETE_MUTATION_ID" ]; then
printf '%s\n' 'No delete mutation ID was returned; fix the write error before waiting.' >&2
else
node scripts/wait-for-vectorize.mjs "$RUN" "$DELETE_MUTATION_ID" 2
fi
npx wrangler vectorize list-vectors "$RUN" --count=10
npx wrangler vectorize get-vectors "$RUN" --ids password-reset upload-pdf billing-receipt > .labex/documents-after-retirement.txt
node - <<'JS'
const text = require("fs").readFileSync(".labex/documents-after-retirement.txt", "utf8");
const rows = JSON.parse(text.slice(text.indexOf("[")));
console.table(rows.map((row) => ({ id: row.id, title: row.metadata.title, revision: row.metadata.revision })));
JS
Остаться должны только password-reset с редакцией 2 и upload-pdf с редакцией 1. Отсутствие billing-receipt имеет значение, поскольку то же аутентифицированное чтение вернуло две записи, которые должны сохраниться.
Откройте выбранную учётную запись в Cloudflare Dashboard и перейдите в AI → Vectorize, затем откройте индекс с именем из $RUN. Убедитесь, что в сводке указано два сохранённых вектора. На диаграмме Stored Vectors сопоставьте видимый жизненный цикл с выполненными командами: после мутации начального заполнения количество увеличивается до трёх, а после целевого удаления уменьшается до двух. Dashboard не показывает, какой именно ID был удалён, поэтому источниками достоверных сведений об идентификаторах остаются Wrangler и независимые чтения через API.
Сводка позволяет сразу увидеть текущее состояние: после вывода одной записи из использования доступны для поиска ещё два документа.

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

Удаление индекса жизненного цикла и выход из системы
На этом шаге вы удалите весь временный индекс только после проверки целевого жизненного цикла документов.
На предыдущем шаге удаление одного вектора сохранило индекс. Следующая финальная команда намеренно удаляет весь ресурс лабораторной работы:
npx wrangler vectorize delete "$RUN" --force
npx wrangler vectorize list --json > .labex/indexes-after-cleanup.json
node -e '
const rows = JSON.parse(require("fs").readFileSync(process.argv[1], "utf8"));
if (rows.some((row) => row.name === process.argv[2])) throw new Error("lab index still exists");
console.log("lab index is absent");
' .labex/indexes-after-cleanup.json "$RUN"
Выполните проверку очистки лабораторной работы, пока успешный аутентифицированный список ещё доступен. Не выходите из Wrangler до завершения этой проверки, потому что в противном случае ошибку аутентификации нельзя будет отличить от успешного удаления.
Затем удалите авторизацию этой виртуальной машины и проверьте структурированный результат:
npx wrangler logout
npx wrangler whoami --json
Ожидайте loggedIn: false. Отдельная сессия Dashboard в браузере по-прежнему доступна вашей учебной учётной записи.
Итоги
В начале у вас было три текущих ID документов. С помощью upsert вы заменили полный вектор и метаданные одной обновлённой статьи, а с помощью целевого удаления вывели из использования одну устаревшую статью. Точные ID мутаций и ограниченные последовательные чтения позволили отличить принятую запись от состояния, доступного для чтения.
Вы также проверили два важных свойства безопасности реального конвейера индексации: обновление не создало дубликат записи, а вывод документа из использования не удалил несвязанные документы. В конце вы сопоставили состояние с двумя записями в Dashboard, удалили временный индекс, подтвердили его отсутствие с помощью аутентифицированной проверки и вышли из системы на новой виртуальной машине.
В V03 вы создадите вектор эмбеддинга для текущего запроса с тем же контрактом модели и используете поддерживаемый индекс для поиска похожих справочных статей.



