소개
V01 에서는 각 도움말 문서마다 벡터 하나를 저장했습니다. 실제 문서는 그대로 유지되지 않습니다. 안내가 변경되고 제목이 수정되며 더 이상 사용하지 않는 페이지는 폐기됩니다. 검색 인덱스가 이러한 수명 주기를 반영하지 않으면 원본 웹사이트가 최신 상태여도 오래된 답변을 반환할 수 있습니다.
Cloudflare Vectorize 는 서로 연관된 세 가지 쓰기 작업을 제공합니다.
- insert는 새 벡터 ID 를 추가하며, 기존 ID 를 자동으로 바꾸면 안 됩니다.
- upsert는“업데이트 또는 삽입”을 의미하며 해당 ID 의 벡터와 메타데이터를 교체합니다.
- ID 로 삭제는 전체 인덱스를 다시 만들지 않고 선택한 레코드를 폐기합니다.
세 개의 테스트 문서를 초기 데이터로 넣고, 수정된 비밀번호 문서를 upsert 하며, 폐기된 결제 문서를 삭제합니다. 그런 다음 관련 없는 업로드 문서가 전혀 변경되지 않았음을 확인합니다. 각 쓰기 작업은 비동기 mutation ID 를 반환하므로, 쓰기 요청이 수락되었다고 바로 읽을 수 있다고 가정하지 않고 정확한 상태가 될 때까지 기다립니다.
이 실습은 두 번째 Vectorize 실습입니다. 직접 이 실습에 들어왔다면 먼저 Connect LabEx to Your Cloudflare Account를 완료한 다음 V01 을 완료하세요. 그러면 인덱스 호환성, 문서 ID, mutation 가시성에 익숙해질 수 있습니다.
Vectorize 는 Workers Free 에서 사용할 수 있습니다. 이 실습은 최대 세 개의 작은 384 차원 벡터만 저장하고, 제한된 읽기 작업을 수행하며, AI 모델을 호출하지 않습니다. 따라서 Workers Paid 와 Workers AI Neurons 는 필요하지 않습니다.
설정 과정에서 /home/labex/project/document-lifecycle-index에 Node.js 22.22.0 과 프로젝트 로컬 Wrangler 4.132.0 을 설치합니다. 설정은 읽기 전용 독립 검사를 제공하지만 Wrangler 를 인증하거나, 인덱스를 만들거나, 벡터를 쓰거나, Cloudflare 계정을 변경하지는 않습니다.
새 문서 수명 주기 인덱스 인증
이 단계에서는 새 VM 을 인증하고, 사용할 계정을 기록한 다음, 폐기할 인덱스 하나에 사용할 고유한 로컬 구성을 만듭니다.
준비된 프로젝트 디렉터리로 이동하고 고정된 CLI 버전을 확인합니다.
cd /home/labex/project/document-lifecycle-index
npx wrangler --version
4.132.0이 출력되어야 합니다. V01 에서 사용한 것과 동일한 제한된 계정 및 Workers 리소스 액세스 권한으로 인증합니다.
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 의 인덱스와 독립적입니다. 앞선 실습에서 배운 내용을 재사용하지만, 이전 실습의 VM 이나 클라우드 리소스에 의존하지는 않습니다.
현재 문서 세트 초기화
이 단계에서는 인덱스를 만들고, 수정이나 폐기가 발생하기 전의 현재 도움말 센터를 나타내는 세 레코드를 삽입합니다.
BGE Small 임베딩 모델에서 사용하는 동일한 384 차원 cosine 계약을 만듭니다.
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 만 삽입하고 전체 결과를 저장한 다음, 실제 mutation 이 처리될 때까지 기다립니다.
set -o pipefail
npx wrangler vectorize insert "$RUN" --file=vectors/seed.ndjson 2>&1 | tee .labex/seed-output.txt
Wrangler 가 대기열에 등록된 벡터 세 개와 mutation 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 세 개가 있어야 합니다. 이때는 새 ID 이므로 insert가 적절합니다. 다음 단계에서는 기존 ID 하나를 의도적으로 교체합니다.
수정된 비밀번호 문서 upsert
이 단계에서는 안정적인 ID 를 유지하면서 password-reset의 벡터와 메타데이터를 교체합니다.
upsert는 없는 ID 를 삽입하거나 기존 ID 를 교체합니다. 원본 문서 하나가 변경되었을 때 유용하지만, 원하는 메타데이터 전체를 보내야 한다는 의미이기도 합니다. 새 레코드에서 생략한 필드가 유지된다고 가정해서는 안 됩니다.
유효한 메타데이터 필드는 모두 유지하면서, 다른 결정적 축과 수정된 제목을 사용하는 revision 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
교체 작업을 제출하고 정확한 mutation 을 저장합니다.
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, 수정된 제목, revision 2가 출력되어야 합니다. upsert 는 네 번째 문서를 추가한 것이 아니라 하나의 ID 를 교체했으므로 전체 개수는 세 개로 유지됩니다.
인덱스를 다시 만들지 않고 문서 하나 폐기
이 단계에서는 안정적인 ID 로 폐기된 결제 문서를 삭제하고, 수정된 비밀번호 문서와 변경하지 않은 업로드 문서가 남아 있는지 확인합니다.
ID 로 삭제하는 작업은 인덱스를 삭제하는 것보다 범위가 좁습니다. 인덱스 계약과 관련 없는 모든 레코드는 그대로 유지됩니다. 폐기할 ID 만 제출합니다.
set -o pipefail
npx wrangler vectorize delete-vectors "$RUN" --ids billing-receipt 2>&1 | tee .labex/delete-output.txt
삭제 mutation 이 처리되고 개수가 2 가 될 때까지 기다립니다.
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 revision 2 와 upload-pdf revision 1 만 남아 있어야 합니다. billing-receipt가 사라졌다는 사실은 중요합니다. 동일한 인증된 읽기 작업에서 남아 있어야 하는 두 문서도 함께 반환되었기 때문입니다.
Cloudflare Dashboard 에서 선택한 계정을 열고 AI → Vectorize로 이동한 다음, $RUN에 지정된 이름의 인덱스를 엽니다. 요약에 저장된 벡터가 두 개라고 표시되는지 확인합니다. Stored Vectors 차트에서 보이는 수명 주기를 명령과 연결합니다. 초기 mutation 후 개수는 세 개로 증가하고, 대상 문서를 삭제한 후 두 개로 감소해야 합니다. Dashboard 에서는 어떤 ID 가 삭제되었는지 알 수 없으므로 Wrangler 와 독립적인 API 읽기 결과가 ID 를 확인하는 권위 있는 근거로 남습니다.
요약을 보면 현재 상태를 한눈에 확인할 수 있습니다. 문서 하나를 폐기한 후에도 문서 두 개를 계속 검색할 수 있습니다.

차트는 문서 수명 주기를 시각적으로 보여 줍니다. 선이 여러 1 분 샘플을 포함하므로 평균값에 잠시 소수점이 표시될 수 있습니다. 중요한 점은 저장된 벡터 수가 세 개에서 두 개로 변경되는 것이 눈에 보인다는 것입니다.

수명 주기 인덱스 삭제 및 로그아웃
이 단계에서는 대상 문서의 수명 주기를 확인한 후에만 폐기할 전체 인덱스를 삭제합니다.
이전 단계에서 벡터 하나를 삭제해도 인덱스는 유지되었습니다. 다음 마지막 명령은 실습에서 만든 리소스 전체를 의도적으로 삭제합니다.
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 인증을 유지하세요. 먼저 로그아웃하면 인증 오류와 삭제가 성공했다는 결과를 구분할 수 없습니다.
그런 다음 이 VM 의 인증을 제거하고 구조화된 결과를 확인합니다.
npx wrangler logout
npx wrangler whoami --json
loggedIn: false가 출력되어야 합니다. 별도의 Dashboard 브라우저 세션은 학습용 계정에서 계속 사용할 수 있습니다.
요약
현재 문서 ID 세 개로 시작해 upsert로 수정된 문서 하나의 전체 벡터와 메타데이터를 교체하고, 대상 삭제로 더 이상 사용하지 않는 문서 하나를 폐기했습니다. 정확한 mutation ID 와 연속적인 제한 읽기를 사용해 수락된 쓰기와 실제로 읽을 수 있는 상태를 구분했습니다.
또한 실제 인덱싱 파이프라인에서 중요한 두 가지 안전성을 확인했습니다. 업데이트가 중복 ID 를 만들지 않았고, 문서 폐기가 관련 없는 문서를 삭제하지 않았습니다. 마지막으로 두 문서가 남은 상태를 Dashboard 에서 확인하고, 폐기할 인덱스를 삭제한 뒤, 인증된 상태에서 인덱스가 사라졌는지 확인하고 새 VM 에서 로그아웃했습니다.
V03 에서는 동일한 모델 계약으로 실시간 쿼리 임베딩을 생성하고, 유지 관리한 인덱스를 사용해 유사한 도움말 문서를 검색합니다.



