임시 공지 만료 처리

CloudflareBeginner
지금 연습하기

소개

유지 관리 배너는 공지 기간이 끝나면 사라져야 합니다. 배너가 계속 표시되면 방문자는 오래된 장애가 아직 진행 중이라고 생각할 수 있습니다. 이 실습에서는 Worker 가 KV 에서 공지를 읽고, 계속 표시할지 판단하도록 구성합니다.

기한은 두 가지입니다. 애플리케이션 기한은 코드가 메시지 표시를 중단할 시점을 지정합니다. KV 만료는 스토리지 서비스가 항목을 삭제할 시점을 지정합니다. 일부러 오래된 참조 레코드를 스토리지에 남겨 두어, 데이터가 아직 존재해도 애플리케이션이 만료된 콘텐츠를 숨길 수 있음을 확인합니다. 그런 다음 두 번째 레코드가 클라우드 KV 에서 자동으로 만료되는 모습을 관찰합니다.

먼저 Serve Account Preferences 를 완료합니다. 이 독립 VM 에는 Node.js 22.22.0 과 프로젝트 로컬 Wrangler 4.131.1 이 /home/labex/project/temporary-notices에 설치되어 있습니다. 자신의 학습 계정과 동일한 account-read, Worker-write, KV-write 권한을 사용합니다. 일회용 Worker 와 네임스페이스를 하나씩 만들고, 합성 메시지만 사용한 다음 로그아웃하기 전에 정리합니다. 이 짧은 실습에는 구매한 도메인이나 유료 업그레이드가 필요하지 않습니다. 핸들러를 작성하고 테스트하는 시간 외에 시간 제한 관찰을 위해 약 5 분이 필요합니다.

공지 네임스페이스 연결

이 단계에서는 임시 공지용 새 네임스페이스를 연결합니다. 별도의 네임스페이스와 고유한 Worker 이름을 사용하여 만료를 실험하는 동안 다른 애플리케이션의 데이터가 삭제되지 않도록 합니다. NOTICES 바인딩이 핸들러를 이 리소스에 연결합니다.

준비된 프로젝트로 이동합니다.

cd /home/labex/project/temporary-notices

고유한 이름을 한 번 생성합니다. openssl rand -hex 6은 임의의 접미사를 출력하고, $(...)은 그 값을 이름에 삽입합니다. 셸 변수에 이름을 저장하면 이 터미널에서 이어지는 명령에 해당 이름을 사용할 수 있습니다.

WORKER_NAME="labex-notices-$(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 를 승인합니다. 동의 페이지에 백그라운드 액세스 권한이 표시될 수도 있습니다. 터미널로 돌아와 로그인이 완료될 때까지 기다립니다.

Create a Feature Flag Store 에서 사용한 것과 동일한 Worker 및 KV 쓰기 권한을 다시 확인합니다. 승인하기 전에 학습 계정이 맞는지 확인합니다.

npx wrangler whoami --json

loggedIn: true와 학습 계정의 name을 확인합니다. 계정이 하나만 표시되더라도 확인해야 합니다. 해당 계정의 id를 복사합니다. 아래 설정에서 YOUR_ACCOUNT_ID를 복사한 값으로 바꾼 후 명령을 실행합니다. 여기의 cat heredoc 은 두 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-notices" --update-config=false

출력에 새 네임스페이스 ID 가 포함됩니다. ID 를 복사한 다음, 완성된 아래 설정에서 YOUR_ACCOUNT_IDYOUR_NAMESPACE_ID를 각각 실제 값으로 바꿉니다. NOTICES 바인딩 이름은 코드에서 사용할 이름이고, 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": "NOTICES", "id": "YOUR_NAMESPACE_ID" }
  ]
}
JSON
npx wrangler kv namespace list

이 실습의 네임스페이스 제목을 찾아 파일에 기록된 ID 와 비교합니다. 다른 네임스페이스가 있어도 그대로 둡니다. 이 설정은 이후 명령이 사용할 계정과 리소스를 지정합니다. 바인딩은 네임스페이스에 대한 참조이지 데이터의 복사본이 아닙니다.

데이터를 삭제하기 전에 오래된 공지 숨기기

이 단계에서는 표시 동작과 스토리지 정리를 분리합니다. 타임스탬프는 특정 시점을 나타내는 숫자입니다. 여기서 displayUntil은 UTC 기준 1970 년 시작부터 계산한 Unix 초를 사용합니다. Date.now()는 밀리초를 반환하므로 핸들러는 비교하기 전에 1000으로 나눕니다. 같은 단위로 비교하면 흔한 기한 계산 오류를 피할 수 있습니다.

다음 인용 heredoc 으로 핸들러를 작성합니다.

cat > src/index.js <<'JS'
export default {
  async fetch(request, env) {
    const url = new URL(request.url);
    const key = url.searchParams.get("key") ?? "notice:maintenance";
    if (url.pathname !== "/notice" || !/^notice:[a-z]{1,20}$/.test(key)) {
      return new Response("Not found", { status: 404 });
    }
    let entry;
    try {
      entry = await env.NOTICES.getWithMetadata(key, "text");
    } catch {
      return Response.json({ error: "Notice storage unavailable" }, { status: 503 });
    }
    if (entry.value === null) {
      return Response.json({ visible: false, reason: "missing" });
    }
    let notice;
    try {
      notice = JSON.parse(entry.value);
    } catch {
      return Response.json({ visible: false, reason: "invalid" });
    }
    if (!notice || typeof notice.message !== "string" || !notice.message.trim() ||
        !Number.isSafeInteger(notice.displayUntil) || notice.displayUntil <= 0) {
      return Response.json({ visible: false, reason: "invalid" });
    }
    if (Math.floor(Date.now() / 1000) >= notice.displayUntil) {
      return Response.json({ visible: false, reason: "expired" });
    }
    return Response.json({
      visible: true, message: notice.message,
      kind: entry.metadata?.kind === "maintenance" ? "maintenance" : "general"
    });
  }
};
JS

key 쿼리 매개변수는 합성 공지를 선택합니다. 지정하지 않으면 핸들러는 notice:maintenance를 사용합니다. 애플리케이션은 누락되었거나 형식이 잘못되었거나 만료된 공지를 이유가 포함된 JSON 응답으로 안전하게 숨깁니다. KV 읽기에 실패하면 공지가 누락된 것처럼 처리하지 않고 503을 반환합니다. 메타데이터의 kind 필드는 공지 유형을 표시합니다. 메타데이터가 없거나 예상과 다르면 general로 처리합니다.

기한 비교에는 >=를 사용하므로 공지는 기한에 도달하는 즉시 숨겨지고 1 초 뒤까지 표시되지 않습니다. 이 검사는 요청마다 실행됩니다. 이미 배너를 표시한 웹 페이지도 자체 타이머로 새로 고치거나 배너를 제거해야 합니다. Worker 응답만으로 이미 렌더링된 페이지를 변경할 수는 없습니다.

애플리케이션 관점에서 이미 만료된 오래된 참조 공지를 로컬에 저장합니다. 기한 1은 1970 년의 특정 시점을 나타내므로 이 레코드는 이미 만료되었습니다. 검사할 수 있도록 KV 만료는 일부러 지정하지 않습니다.

npx wrangler kv key put notice:reference '{"message":"Old maintenance notice","displayUntil":1}' --binding NOTICES --local --metadata '{"kind":"maintenance"}'
npx wrangler dev --local --ip 0.0.0.0 --port 8080 > local.log 2>&1 &
DEV_PID=$!
cat local.log

백그라운드 서버는 출력을 local.log에 기록합니다. 포트 8080 에서 준비되었다는 메시지가 표시될 때까지 로그 명령을 반복합니다. 이제 오래된 참조 공지를 요청합니다.

curl -i 'http://127.0.0.1:8080/notice?key=notice:reference'

HTTP 200{"visible":false,"reason":"expired"}가 반환되어야 합니다. URL 을 따옴표로 감싸면 물음표가 셸의 파일 이름 구문으로 처리되지 않습니다. 항목이 여전히 존재하는지 확인합니다.

npx wrangler kv key get notice:reference --binding NOTICES --local --text

JSON 이 여전히 존재해야 합니다. 자동 삭제가 아니라 코드가 오래된 공지를 표시하지 않도록 막은 것입니다. 없는 키도 안전하게 처리되어야 합니다.

curl -i 'http://127.0.0.1:8080/notice?key=notice:missing'

{"visible":false,"reason":"missing"}이 반환되어야 합니다. 정리할 때까지 로컬 서버를 실행한 상태로 둡니다.

두 가지 기한이 있는 공지 게시

이 단계에서는 먼저 Worker 를 게시한 다음 짧은 클라우드 공지 표시 기간을 시작합니다. 타이머를 시작하기 전에 엔드포인트를 준비하면 현재 결과를 확인할 시간이 생깁니다.

원격 네임스페이스에 동일한 만료되지 않는 참조 공지를 생성합니다.

npx wrangler kv key put notice:reference '{"message":"Old maintenance notice","displayUntil":1}' --binding NOTICES --remote --metadata '{"kind":"maintenance"}'
npx wrangler deploy

생성된 Worker 이름과 NOTICES 바인딩을 확인한 다음, 출력에 표시된 실제 공개 URL 을 저장합니다.

WORKER_URL="https://YOUR_WORKER.YOUR_SUBDOMAIN.workers.dev"
curl -i "$WORKER_URL/notice?key=notice:reference"

오래된 참조 공지가 expired 이유로 숨겨져야 합니다. 호스트 이름이 아직 준비되지 않았다면 기다렸다가 다시 시도한 후 시간 제한 단계로 진행합니다. 아직 기본 유지 관리 키를 요청하지 마세요. 없는 KV 키를 읽은 결과도 캐시될 수 있습니다.

다음 명령을 실행하기 전에 남은 지침을 읽습니다. date +%s는 VM 의 현재 Unix 시간을 반환하고, $((...))는 셸 산술을 수행합니다. 3 분 후 공지 표시를 중단하고, 1 분 뒤 KV 가 공지를 삭제하도록 설정합니다.

DISPLAY_UNTIL=$(($(date +%s) + 180))
KV_EXPIRES=$((DISPLAY_UNTIL + 60))

실제 애플리케이션 페이로드를 작성합니다. 따옴표가 없는 JSON 구분자가 숫자 기한을 파일에 삽입합니다.

cat > notice.json <<JSON
{"message":"Maintenance starts soon","displayUntil":$DISPLAY_UNTIL}
JSON

--path는 해당 파일에서 값을 읽습니다. --expiration은 Unix 초 단위의 절대 KV 만료 시점을 설정하고, --metadata는 값과 함께 공지 유형을 추가합니다.

npx wrangler kv key put notice:maintenance --path notice.json --binding NOTICES --remote --expiration "$KV_EXPIRES" --metadata '{"kind":"maintenance"}'

KV 는 기록 시점부터 계산한 초 단위의 상대 TTL(time to live) 도 지원합니다. Wrangler 에서는 이 옵션을 --ttl이라고 하고, 바인딩 API 에서는 expirationTtl이라고 합니다. 상대 만료와 절대 만료 모두 최소 60 초 이후의 시점이어야 합니다. 여기서는 두 기한을 직접 비교할 수 있도록 절대 만료를 사용합니다. 자세한 내용은 KV expiration options을 참조합니다.

npx wrangler kv key list --binding NOTICES --remote

notice:maintenance와 해당 expiration, kind 메타데이터를 찾습니다. 참조 공지에는 KV 만료가 없습니다. 이제 현재 공지 메시지를 읽습니다.

curl -i "$WORKER_URL/notice"

HTTP 200{"visible":true,"message":"Maintenance starts soon","kind":"maintenance"}가 반환되어야 합니다. 표시 기간이 끝나기 전에 지금 이 단계의 검사를 실행합니다. 이 검사는 실제 클라우드 값, 메타데이터, KV 만료, 바인딩, 현재 응답을 확인합니다. 저장한 타임스탬프만으로는 공지가 저장되었다는 증거가 되지 않습니다.

표시 기간을 놓쳤다면 두 시간 할당 명령을 다시 실행하고, notice.json을 다시 작성한 다음, 새로운 기한으로 원격 put 명령을 다시 실행합니다. 쓰기를 빠르게 반복하지 마세요. 이전에 캐시된 읽기 결과가 새 값으로 바뀌는 데 시간이 걸릴 수 있으므로 기다린 후 활성 검사를 반복합니다. 검사가 통과한 뒤에만 계속 진행합니다.

활성 검사가 통과하면 같은 학습 계정의 Dashboard 에서 Storage & databases → Workers KV를 열고 이 실습의 네임스페이스를 선택합니다. KV Pairs에서 notice:maintenance 옆의 View를 클릭하여 메시지와 displayUntil을 확인합니다. KV 만료 시간과 메타데이터는 CLI 키 목록에서 확인하세요. 이 화면은 저장된 값을 보여 줍니다. 읽기만 수행하세요. 확인 중에도 시간은 계속 흐릅니다. 키가 이미 만료되었다면 화면을 보기 위해 다시 만들지 말고 다음 단계로 진행하세요. 스크린샷의 이름과 타임스탬프는 예시이므로 복사하지 마세요.

Temporary notice value and display deadline

숨겨진 콘텐츠와 자동 만료 관찰

이 단계에서는 유지 관리 키를 수동으로 삭제하지 않고 두 기한을 관찰합니다. 원래 페이로드와 결과를 비교할 수 있도록 notice.json은 변경하지 않습니다.

계획된 두 시각과 현재 시각을 출력합니다.

printf 'displayUntil=%s
KV expiration=%s
now=%s
' "$DISPLAY_UNTIL" "$KV_EXPIRES" "$(date +%s)"

현재 시각이 displayUntil에 도달할 때까지 기다립니다. 다음 명령은 남은 대기 시간만 계산합니다. 기한이 이미 지났으면 조건문이 대기하지 않습니다. sleep은 초 단위 값을 받고, if는 음수 지연 시간이 전달되지 않도록 합니다.

WAIT_SECONDS=$((DISPLAY_UNTIL - $(date +%s) + 1))
if [ "$WAIT_SECONDS" -gt 0 ]; then sleep "$WAIT_SECONDS"; fi
curl -i "$WORKER_URL/notice"

메시지가 더 이상 표시되면 안 됩니다. KV 가 만료되기 전이라면 {"visible":false,"reason":"expired"}가 반환되어야 합니다. KV 가 이미 키를 만료시킨 뒤에 확인하면 reasonmissing일 수도 있습니다. 두 경우 모두 공지를 표시하지 않습니다. 보존된 참조 공지는 애플리케이션 기한 동작을 직접 확인하는 데 사용합니다.

curl -i "$WORKER_URL/notice?key=notice:reference"
npx wrangler kv key get notice:reference --binding NOTICES --remote --text

엔드포인트는 참조 공지를 expired로 숨기지만, KV 읽기에서는 기존 JSON 이 계속 반환되어야 합니다. 저장된 데이터를 계속 사용할 수 있어도 애플리케이션 기한이 유용한 이유를 확인할 수 있습니다.

이제 KV 만료 시점까지 기다립니다.

WAIT_SECONDS=$((KV_EXPIRES - $(date +%s) + 1))
if [ "$WAIT_SECONDS" -gt 0 ]; then sleep "$WAIT_SECONDS"; fi
npx wrangler kv key list --binding NOTICES --remote
curl -i "$WORKER_URL/notice"

목록에는 notice:reference만 남아 있어야 하고, 기본 엔드포인트는 {"visible":false,"reason":"missing"}을 반환해야 합니다. 유지 관리 키에는 delete 명령을 실행하지 마세요. 이 관찰의 목적은 자동 만료를 확인하는 것입니다. 항목이 계속 표시되면 최대 2 분 동안 15 초 간격으로 읽기 전용 검사를 다시 실행합니다. 이는 실습을 위한 관찰 시간이며 정확한 삭제 시점을 보장하지는 않습니다. 상태가 수렴하지 않으면 성공했다고 주장하지 말고 결과를 확인할 수 없었다고 보고합니다. 인증 또는 네트워크 오류는 항목이 없다는 증거가 아닙니다.

KV 만료와 읽기 캐시는 서로 다른 개념입니다. 더 긴 읽기 캐시 기간을 요청했더라도 만료는 적용됩니다. 그러나 저장된 설정 변경 사항은 전파되는 데 시간이 걸릴 수 있으므로, 이전에 읽은 뒤 새 기한을 기록했다고 해서 전 세계적으로 즉시 적용되는 일정이 보장되는 것은 아닙니다. 이 실습에서는 핸들러가 실제로 읽은 레코드에 포함된 기한을 확인합니다.

일회용 클라우드 리소스 삭제

이 단계에서는 Wrangler 의 인증이 유지되는 동안 두 리소스를 모두 삭제합니다. 네임스페이스는 Worker 보다 오래 남을 수 있으므로 애플리케이션만 삭제해서는 데이터가 정리되지 않습니다.

이 터미널에서 실행한 로컬 개발 프로세스를 중지합니다.

kill "$DEV_PID"

삭제하기 전에 저장된 리소스 참조를 확인합니다.

cat wrangler.jsonc

labex-notices-... Worker 이름과 NOTICES 네임스페이스 ID 가 맞는지 확인합니다. 이 설정이 선택한 Worker 를 삭제합니다.

npx wrangler delete

확인 메시지가 표시되면 이름이 이 실습의 Worker 와 일치하는지 확인하고 y를 입력합니다. 그런 다음 NOTICES가 참조하는 네임스페이스만 삭제합니다. 이 작업은 보존된 참조 레코드도 삭제합니다.

npx wrangler kv namespace delete --binding NOTICES

확인 메시지에서 네임스페이스를 다시 검토한 후 승인합니다. 독립 검사가 삭제되어야 할 리소스를 식별할 수 있도록 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 메타데이터에서 공지 유형을 읽는 공지 리더를 구축했습니다. 보존된 오래된 레코드를 통해 콘텐츠를 숨기기 위해 먼저 삭제할 필요가 없음을 확인했습니다. 두 번째 레코드에서는 절대 타임스탬프를 사용한 KV 만료와 별도의 자동 삭제 확인을 수행했습니다.

표시 기한, 스토리지 만료, 읽기 캐시 동작을 구분한 다음 일회용 리소스를 삭제하고 로그아웃했습니다. 다음 단계에서는 KV 에 작은 리디렉션 카탈로그를 가져와 관리합니다.