소개
도움말 센터는 KV 에서 테마와 환영 배너를 읽을 수 있으므로, 편집자는 새 코드를 배포하지 않고도 이 설정을 변경할 수 있습니다. 업데이트 후 서로 다른 위치의 사용자가 잠시 서로 다른 버전을 볼 수 있습니다. 모든 읽기 요청이 항상 최신 설정을 반환한다고 가정하지 말고, 전환 중에도 애플리케이션이 계속 작동하도록 만들어야 합니다.
안전한 기본값을 사용하는 버전이 지정된 구성 리더를 만들고, 오래된 값과 새 값이 의도적으로 섞인 시뮬레이션 시퀀스를 테스트한 다음 실제 클라우드 업데이트를 수행합니다. **버전 (version)**은 설정과 함께 저장되는 레이블이며, 받은 값을 식별하는 데 사용합니다. 버전은 KV 를 강한 일관성을 보장하는 데이터베이스로 바꾸지 않으며, 연속된 요청이 증가하는 버전 번호를 반환한다고 보장하지도 않습니다.
먼저 앞선 KV 실습을 완료합니다. 이 독립 VM 에는 /home/labex/project/delayed-config에 Node.js 22.22.0 과 프로젝트 전용 Wrangler 4.131.1 이 설치되어 있습니다. 계정 읽기, Worker 쓰기, KV 쓰기 권한이 동일하게 부여된 본인의 학습 계정을 사용합니다. 이 실습에서는 폐기 가능한 Worker 와 네임스페이스를 하나씩 만들며, 합성된 표시 설정만 노출합니다. 데이터가 적으므로 유료 업그레이드나 구매한 도메인은 필요하지 않습니다. 이러한 설정은 인증, 결제 또는 즉시 권한이 반영되어야 하는 다른 결정을 제어하지 않습니다.
독립적인 구성 저장소 연결
이 단계에서는 표시 구성 전용의 새 네임스페이스를 연결합니다. CONFIG 바인딩은 표준 Wrangler 구성에 리소스 참조를 저장합니다. 의도적으로 잘못된 설정이 다른 애플리케이션에 영향을 주지 않도록 새 실습 리소스를 사용합니다.
준비된 프로젝트로 이동합니다.
cd /home/labex/project/delayed-config
고유한 이름을 한 번 생성합니다. openssl rand -hex 6은 무작위 접미사를 출력하고, $(...)는 그 값을 이름에 삽입합니다. 셸 변수에 이름을 저장하면 이 터미널에서 이어지는 명령에 사용할 수 있습니다.
WORKER_NAME="labex-config-$(openssl rand -hex 6)"
printf '%s\n' "$WORKER_NAME"
이 VM 을 인증합니다. 계정 ID 를 읽는 권한 외에도 Workers Scripts Write 권한이 있으면 배포와 삭제를 수행할 수 있고, Workers KV Write 권한이 있으면 이 실습의 네임스페이스와 키를 관리할 수 있습니다.
npx wrangler login --device --browser=false --scopes account:read user:read workers_scripts:write workers_kv:write
표시된 디바이스 링크를 브라우저에서 열고 현재 코드를 입력합니다. 요청된 권한과 학습 계정을 확인한 다음 Wrangler 를 승인합니다. 동의 페이지에 백그라운드 액세스 권한이 표시될 수도 있습니다. 터미널로 돌아와 로그인이 완료될 때까지 기다립니다.
이 과정의 앞선 단계에서 부여한 동일한 Worker 쓰기 및 KV 쓰기 권한을 확인하고, 학습 계정도 확인합니다.
npx wrangler whoami --json
loggedIn: true와 학습 계정의 name을 확인합니다. 계정이 하나만 표시되더라도 확인해야 합니다. 해당 계정의 id를 복사합니다. 아래 구성에서 YOUR_ACCOUNT_ID를 복사한 값으로 바꾼 후 명령을 실행합니다. 여기서 cat의 here-document 는 두 JSON 줄 사이의 모든 내용을 파일에 기록하고, >는 기존 파일을 덮어씁니다. 구분자가 따옴표로 묶여 있지 않으므로 셸이 $WORKER_NAME을 실제 값으로 치환합니다.
cat > wrangler.jsonc <<JSON
{
"name": "$WORKER_NAME",
"main": "src/index.js",
"compatibility_date": "2026-07-30",
"account_id": "YOUR_ACCOUNT_ID",
"workers_dev": true
}
JSON
해당 계정에 네임스페이스를 만듭니다. 네임스페이스의 제목에는 Worker 의 고유 이름이 포함되므로 나중에 두 리소스를 쉽게 구분할 수 있습니다. --update-config=false는 파일을 자동으로 수정하지 않고 바인딩 편집 내용을 직접 확인할 수 있게 합니다.
npx wrangler kv namespace create "$WORKER_NAME-config" --update-config=false
출력에 새 네임스페이스 ID 가 포함됩니다. ID 를 복사한 다음, 아래 전체 구성에서 YOUR_ACCOUNT_ID와 YOUR_NAMESPACE_ID를 각각 실제 값으로 바꿉니다. CONFIG 바인딩 이름은 코드에서 사용할 이름이고, ID 는 실제 Cloudflare 리소스를 식별합니다.
cat > wrangler.jsonc <<JSON
{
"name": "$WORKER_NAME",
"main": "src/index.js",
"compatibility_date": "2026-07-30",
"account_id": "YOUR_ACCOUNT_ID",
"workers_dev": true,
"kv_namespaces": [
{ "binding": "CONFIG", "id": "YOUR_NAMESPACE_ID" }
]
}
JSON
npx wrangler kv namespace list
이 실습의 네임스페이스 제목을 찾아 파일에 기록한 ID 와 비교합니다. 다른 네임스페이스가 표시되어도 그대로 둡니다. 이 구성은 이후 명령에서 사용할 계정과 리소스를 지정합니다. 바인딩은 네임스페이스를 가리키는 참조이지, 데이터의 복사본이 아닙니다.
안전한 기본값으로 버전이 지정된 설정 읽기
이 단계에서는 유효한 두 버전 모두 사용 가능한 응답을 반환하도록 만듭니다. 표시 설정이 없거나 손상되면 단순한 밝은 테마와 배너 없음으로 대체합니다. 이렇게 하면 선택적인 표시 설정 때문에 도움말 센터 전체가 중단되는 것을 막을 수 있습니다.
핸들러를 작성합니다. 고정된 defaults 객체에는 요청별 상태가 없으며 수정하지 않습니다. 각 요청은 자체 KV 결과를 읽습니다.
cat > src/index.js <<'JS'
const defaults = { version: 0, theme: "light", banner: "", source: "default" };
export default {
async fetch(request, env) {
const url = new URL(request.url);
if (url.pathname === "/health") return Response.json({ status: "ok" });
const key = url.searchParams.get("key") ?? "config:current";
if (url.pathname !== "/settings" || !/^config:[a-z0-9-]{1,20}$/.test(key)) {
return new Response("Not found", { status: 404 });
}
let value;
try {
value = await env.CONFIG.get(key, { type: "text", cacheTtl: 60 });
} catch {
return Response.json({ error: "Settings temporarily unavailable" }, { status: 503 });
}
if (value === null) return Response.json(defaults);
let settings;
try {
settings = JSON.parse(value);
} catch {
return Response.json(defaults);
}
if (!settings || !Number.isSafeInteger(settings.version) || settings.version < 1 ||
!["light", "dark"].includes(settings.theme) ||
typeof settings.banner !== "string" || settings.banner.length > 80) {
return Response.json(defaults);
}
return Response.json({
version: settings.version, theme: settings.theme,
banner: settings.banner, source: "stored"
});
}
};
JS
KV 요청의 cacheTtl: 60은 초 단위의 읽기 캐시 기간입니다. 이 설정은 저장된 키를 만료시키지 않습니다. 또한 모든 위치가 항상 최신 값을 가져오도록 지시하지도 않습니다. 기존 값과 키가 없을 때의 결과 모두 캐시될 수 있습니다. 쓰기 작업은 자주 수행하지 말고, 애플리케이션이 유효하지만 오래된 구성도 처리하도록 설계합니다.
핸들러는 사용하기 전에 버전, 지원되는 테마, 배너 길이를 검증합니다. /health 경로는 선택적인 설정을 읽지 않고 응답합니다. 저장소 오류가 발생하면 /settings는 명시적으로 503을 반환하므로, 애플리케이션이 저장소에서 기본값을 성공적으로 읽은 것처럼 잘못 보고하지 않습니다.
작은 버전 파일 두 개를 만듭니다. 각 파일에 의도한 값을 저장하면 쓰기 전에 내용을 쉽게 확인할 수 있습니다.
cat > config-v1.json <<'JSON'
{"version":1,"theme":"light","banner":"Welcome"}
JSON
cat > config-v2.json <<'JSON'
{"version":2,"theme":"dark","banner":"New help center"}
JSON
두 파일을 별도의 로컬 fixture 키에 기록하고, 잘못된 값도 하나 기록합니다. 이 키를 사용하면 가능한 입력을 반복해서 재현할 수 있지만, Cloudflare 의 네트워크 타이밍을 시뮬레이션하지는 않습니다.
npx wrangler kv key put config:v1 --path config-v1.json --binding CONFIG --local
npx wrangler kv key put config:v2 --path config-v2.json --binding CONFIG --local
npx wrangler kv key put config:broken broken-json --binding CONFIG --local
npx wrangler dev --local --ip 0.0.0.0 --port 8080 > local.log 2>&1 &
DEV_PID=$!
cat local.log
준비 완료 메시지가 나타날 때까지 기다립니다. 그런 다음 두 키를 명시적으로 선택해 비교합니다.
curl -i 'http://127.0.0.1:8080/settings?key=config:v1'
curl -i 'http://127.0.0.1:8080/settings?key=config:v2'
두 요청 모두 source: "stored"가 포함된 HTTP 200을 반환합니다. 버전 1 은 Welcome이 있는 밝은 테마이고, 버전 2 는 New help center가 있는 어두운 테마입니다. 두 버전 모두 이전 요청의 결과에 의존하지 않습니다.
curl -i 'http://127.0.0.1:8080/settings?key=config:missing'
curl -i 'http://127.0.0.1:8080/settings?key=config:broken'
두 요청 모두 다음을 반환해야 합니다.
{"version":0,"theme":"light","banner":"","source":"default"}
버전 0은 애플리케이션의 기본 레이블이며, KV 에 저장된 revision 이 아닙니다.
curl -i http://127.0.0.1:8080/health
{"status":"ok"}가 반환되는지 확인합니다. 정리 단계가 끝날 때까지 로컬 서버를 실행 상태로 둡니다.
제어된 오래된 읽기 시퀀스 실행
이 단계에서는 오래된 값, 새 값, 다시 오래된 값, 새 값, 누락된 값, 잘못된 값이 차례로 반환되는 예측 가능한 시퀀스로 핸들러를 테스트합니다. 이 시퀀스는 테스트 fixture이며, 예측할 수 없는 상황을 반복 가능하게 만들기 위해 의도적으로 제공한 입력입니다. 실제 Cloudflare 요청이 오래된 값이었다는 증거는 아닙니다.
표준 assertion 라이브러리를 사용해 작은 Node.js 테스트를 만듭니다. 작성한 핸들러를 가져오고, 제어된 반환값을 제공하는 동일한 CONFIG.get() 인터페이스를 사용합니다.
cat > test-config.mjs <<'JS'
import assert from "node:assert/strict";
import worker from "./src/index.js";
const older = JSON.stringify({ version: 1, theme: "light", banner: "Welcome" });
const newer = JSON.stringify({ version: 2, theme: "dark", banner: "New help center" });
// A controlled fixture: these values simulate different reads, not a cloud outage.
const values = [older, newer, older, newer, null, "broken-json"];
const expectedVersions = [1, 2, 1, 2, 0, 0];
for (let i = 0; i < values.length; i += 1) {
const env = { CONFIG: { get: async () => values[i] } };
const response = await worker.fetch(new Request("https://example.test/settings"), env);
assert.equal(response.status, 200);
const body = await response.json();
assert.equal(body.version, expectedVersions[i]);
assert.ok(["light", "dark"].includes(body.theme));
assert.equal(typeof body.banner, "string");
}
console.log("Controlled old/new/missing/invalid reads stayed usable.");
JS
node test-config.mjs
Controlled old/new/missing/invalid reads stayed usable.가 출력되는지 확인합니다. assertion 이 실패하면 명령이 오류와 함께 중단됩니다. 오래된 값이 다시 반환되는 것은 의도된 동작입니다. 이를 숨기기 위해 프로세스 전역 "latest version" 변수를 추가하지 않습니다. Worker 는 서로 다른 인스턴스에서 실행될 수 있으므로 이러한 변수로 계정 전체의 최신 버전을 정할 수 없습니다.
구성 업데이트 중에도 오래된 유효 설정을 읽는 요청이 계속 작동해야 합니다. 이 방식은 배너나 테마에 적합합니다. 하지만 누군가의 접근 권한을 즉시 취소하는 용도로 KV 를 적합하게 만들지는 않습니다. 다음 단계에서 실제 클라우드 테스트를 수행합니다. 첫 요청부터 새 값이 표시될 수도 있으며, 이것도 유효한 결과입니다.
배포하고 첫 번째 클라우드 버전 설정
이 단계에서는 구성을 변경하기 전에 실제 원격 기준값을 설정합니다. 이 실습의 클라우드 네임스페이스에는 현재 구성과 잘못된 값에 대한 fallback fixture 만 저장합니다.
npx wrangler kv key put config:current --path config-v1.json --binding CONFIG --remote
npx wrangler kv key put config:broken broken-json --binding CONFIG --remote
npx wrangler deploy
고유한 Worker 이름과 CONFIG 바인딩을 확인한 다음 실제 공개 URL 을 복사합니다.
WORKER_URL="https://YOUR_WORKER.YOUR_SUBDOMAIN.workers.dev"
curl -i "$WORKER_URL/settings"
버전 1, 테마 light, 배너 Welcome, source: "stored"가 반환되는지 확인합니다. 필요한 경우 호스트 이름이 준비되고 KV 값이 표시될 때까지 기다립니다. 네트워크 오류는 구성 응답이 아닙니다. 버전 1 을 교체하기 전에 이 단계의 독립 확인을 실행합니다. 이 확인은 선택한 계정, 저장된 값, 배포된 바인딩, 기본값 및 health 응답을 검증합니다.
오래된 읽기를 요구하지 않고 실제 업데이트 관찰
이 단계에서는 Worker 코드를 변경하지 않고 저장된 구성을 업데이트합니다. 먼저 새 값이 의도한 내용인지 확인한 다음 동일한 원격 키에 기록합니다.
cat config-v2.json
npx wrangler kv key put config:current --path config-v2.json --binding CONFIG --remote
npx wrangler kv key get config:current --binding CONFIG --remote --text
관리 도구를 통한 읽기 결과에 버전 2가 포함되어야 합니다. 이제 애플리케이션이 확인하는 값을 살펴봅니다.
curl -i "$WORKER_URL/settings"
버전 2 가 즉시 표시될 수도 있고, 읽기 결과가 수렴하는 동안 유효한 이전 버전이 표시될 수도 있습니다. eventual consistency 를 "증명"하기 위해 오래된 응답이 반드시 반환되어야 한다고 요구하지 말고, 오래된 응답을 유도하기 위해 키를 빠르게 반복해서 쓰지도 않습니다. 필요한 경우 최대 5 분 동안 15 초 간격으로 HTTP 요청을 반복합니다. 이는 제한된 실습 관찰 시간이며, 모든 글로벌 위치가 5 분 안에 수렴한다는 보장이 아닙니다.
엔드포인트가 다음을 반환하면 계속 진행합니다.
{"version":2,"theme":"dark","banner":"New help center","source":"stored"}
해당 관찰 시간 안에 수렴하지 않으면 계정과 바인딩을 확인하고 결과를 판정 불가로 보고합니다. 독립 확인은 실제 저장 버전과 실제 응답이 일치해야 통과합니다. 로컬 파일이나 테스트 fixture 만으로는 충족되지 않습니다.
curl -i "$WORKER_URL/settings?key=config:broken"
curl -i "$WORKER_URL/health"
손상된 표시 구성에는 여전히 제어된 기본값이 사용되고, health 응답은 계속 ok여야 합니다. Dashboard 에서 동일한 계정을 선택하고 Storage & databases → Workers KV에서 이 실습의 네임스페이스를 확인합니다. config:current의 값을 파일에 저장된 버전 2 와 비교합니다. 이 읽기 전용 화면은 관리되는 값을 보여 주지만, 모든 원격 위치에 현재 어떤 값이 캐시되어 있는지는 증명할 수 없습니다.
제어된 테스트에서는 오래된 값을 허용하는 동작을 확인했고, 실제 업데이트에서는 배포와 테스트 엔드포인트에서 관찰된 수렴을 확인했습니다. 이 두 결론을 서로 혼동하지 않습니다. 일관성 모델은 KV 작동 방식을 참고합니다.
KV Pairs를 선택한 다음 config:current 옆의 View를 클릭하세요. 목록에 업데이트가 아직 반영되지 않았다면 Refresh를 사용하세요. 아래 예에는 버전 2, 테마 dark, 배너 New help center가 표시됩니다. 생성된 네임스페이스 이름은 실행마다 다릅니다. 이 확인 단계에서는 값을 변경하지 마세요.

폐기 가능한 클라우드 리소스 삭제
이 단계에서는 Wrangler 가 아직 인증된 상태에서 두 리소스를 모두 삭제합니다. 네임스페이스는 Worker 보다 오래 남을 수 있으므로 애플리케이션만 삭제해서는 데이터가 정리되지 않습니다.
이 터미널에서 시작한 로컬 개발 프로세스를 중지합니다.
kill "$DEV_PID"
삭제하기 전에 저장된 리소스 참조를 확인합니다.
cat wrangler.jsonc
labex-config-... Worker 이름과 CONFIG 네임스페이스 ID 를 확인합니다. 이 구성으로 선택되는 Worker 를 삭제합니다.
npx wrangler delete
확인 메시지가 나타나면 표시된 이름이 이 실습의 Worker 와 일치하는지 확인하고 y를 입력합니다. 그런 다음 CONFIG가 참조하는 네임스페이스만 삭제합니다.
npx wrangler kv namespace delete --binding CONFIG
확인 메시지에서 삭제 대상 네임스페이스를 검토한 후 승인합니다. 독립 확인에서 삭제되어야 할 리소스를 식별할 수 있도록 wrangler.jsonc는 그대로 둡니다.
npx wrangler kv namespace list
이 실습의 네임스페이스가 목록에서 사라지고, 관련 없는 네임스페이스는 남아 있어야 합니다. Dashboard 의 목록을 새로 고쳐 이 실습의 Worker 와 네임스페이스가 사라졌는지 확인합니다. 요청 실패나 로그인 만료만으로는 삭제가 완료되었다고 판단할 수 없습니다. 로그아웃하기 전에 이 단계의 확인을 실행하여 인증된 리소스 목록을 검사하도록 합니다.
VM 인증 종료
이 단계에서는 정리 확인이 통과한 후 Wrangler 연결을 끊습니다. 로그아웃하면 이 VM 에 저장된 Wrangler 인증이 종료됩니다. 클라우드 리소스가 삭제되거나 일반 Dashboard 브라우저 세션에서 로그아웃되는 것은 아닙니다.
npx wrangler logout
npx wrangler whoami --json
구조화된 결과에 "loggedIn": false가 표시되는지 확인합니다. 인증되지 않은 이 명령은 0 이 아닌 종료 상태로 끝날 수 있으며, 여기서는 정상입니다. 명시적인 인증 상태가 표시되지 않고 연결 오류만 발생하면 연결이 정상일 때 다시 시도합니다.
남아 있는 로컬 파일과 로컬 KV 상태는 이 폐기 가능한 VM 에 속합니다. 이미 삭제한 클라우드 리소스와는 별개입니다. 이제 실습을 마칠 수 있습니다.
요약
오래된 유효 설정과 새 유효 설정을 모두 허용하고, 값이 없거나 잘못된 경우 안전한 기본값을 사용하며, 선택적인 KV 데이터와 독립적으로 health 엔드포인트를 유지하는 구성 리더를 만들었습니다. 제어된 오래된 읽기 시퀀스를 실행한 후 실제 원격 키를 변경하고 배포된 응답이 수렴하는 것을 관찰했습니다.
버전 레이블은 반환된 데이터를 설명하며, 읽기 캐시 기간, 만료, 강한 일관성은 서로 다른 개념이라는 점을 배웠습니다. 마지막으로 폐기 가능한 리소스를 삭제하고 로그아웃했습니다. 과정의 과제에서는 올바른 네임스페이스 바인딩과 안전한 공지 처리를 함께 다룹니다.



