Cloudflare Workers KV로 전역 키-값 저장소 사용하기

Cloudflare Workers KV로 전역 키-값 저장소 사용하기

Cloudflare Workers로 애플리케이션을 만들다 보면 어딘가에 데이터를 저장해야 할 때가 옵니다. 설정값을 관리하거나 API 응답을 캐싱하거나 세션 토큰을 저장하는 등 간단한 키-값 패턴이 필요한 경우가 많죠.

Workers KV는 이런 용도에 딱 맞는 서버리스 키-값 저장소입니다. Cloudflare의 전역 네트워크에 데이터가 분산 저장되어 있어서 세계 어디서든 빠르게 읽을 수 있고 Workers에서 바인딩 하나로 바로 접근할 수 있어요.

Workers KV란

Workers KV는 Cloudflare의 엣지 네트워크 전체에 데이터를 분산 저장하는 키-값 저장소입니다. “KV”는 Key-Value의 약자예요.

가장 큰 특징은 읽기에 최적화된 구조라는 점입니다. 데이터가 전 세계 엣지 로케이션에 캐싱되어 있어서 읽기 지연 시간이 매우 짧아요. 반면 쓰기는 최종 일관성(eventual consistency) 모델을 따릅니다. 데이터를 쓰면 전 세계에 전파되기까지 시간이 걸릴 수 있어요.

이 특성 때문에 자주 읽고 가끔 쓰는 패턴에 적합합니다. 사이트 설정, 기능 플래그, API 키 조회, CDN 설정 같은 용도에 잘 맞고 실시간 채팅이나 카운터처럼 쓰기가 빈번한 경우에는 적합하지 않아요.

네임스페이스 만들기

KV에서 데이터를 저장하려면 먼저 네임스페이스를 만들어야 합니다. 네임스페이스는 하나의 독립된 키-값 공간이에요.

npx wrangler kv namespace create MY_CACHE
결과
Resource location: remote

🌀 Creating namespace with title "MY_CACHE"
 Success!
To access your new KV Namespace in your Worker, add the following snippet to your configuration file:
{
  "kv_namespaces": [
    {
      "binding": "MY_CACHE",
      "id": "a1b2c3d4..."
    }
  ]
}
? Would you like Wrangler to add it on your behalf?

출력된 설정을 wrangler.jsonc에 넣으면 바인딩이 완료됩니다.

wrangler.jsonc
{
  "name": "my-worker",
  "main": "src/index.ts",
  "compatibility_date": "2025-01-01",
  "kv_namespaces": [
    {
      "binding": "MY_CACHE",
      "id": "a1b2c3d4...",
    },
  ],
}

binding에 지정한 이름이 Worker 코드에서 env.MY_CACHE로 접근할 때 쓰는 이름이 됩니다.

Workers에서 KV 사용하기

바인딩이 설정되면 Worker 코드에서 env.MY_CACHE로 데이터를 읽고 쓸 수 있습니다.

src/index.ts
export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const { pathname } = new URL(request.url);

    if (pathname === "/api/config") {
      const theme = await env.MY_CACHE.get("config:theme");
      const lang = await env.MY_CACHE.get("config:lang");
      return Response.json({ theme, lang });
    }

    return new Response("Not Found", { status: 404 });
  },
};

get()으로 값을 읽고 put()으로 값을 쓰는 게 기본이에요. 데이터베이스 드라이버를 설치하거나 연결 문자열을 설정할 필요 없이 바인딩만으로 바로 사용할 수 있습니다.

읽기와 쓰기

KV의 핵심 API는 네 가지입니다.

// 쓰기
await env.MY_CACHE.put(
  "user:123",
  JSON.stringify({ name: "Dale", role: "admin" }),
);

// 읽기
const value = await env.MY_CACHE.get("user:123");
const user = JSON.parse(value); // { name: "Dale", role: "admin" }

// 삭제
await env.MY_CACHE.delete("user:123");

// 키 목록 조회
const list = await env.MY_CACHE.list({ prefix: "user:" });
console.log(list.keys); // [{ name: "user:123" }, { name: "user:456" }, ...]

put()의 값은 문자열, ArrayBuffer, ReadableStream을 받습니다. JSON 데이터를 저장하려면 JSON.stringify()로 직렬화해야 해요. 읽을 때도 마찬가지로 JSON.parse()가 필요합니다.

get()type 옵션을 주면 자동으로 파싱해줘서 편합니다.

// JSON으로 자동 파싱
const user = await env.MY_CACHE.get("user:123", { type: "json" });

// ArrayBuffer로 읽기 (바이너리 데이터)
const data = await env.MY_CACHE.get("binary-key", { type: "arrayBuffer" });

// ReadableStream으로 읽기 (큰 데이터)
const stream = await env.MY_CACHE.get("large-key", { type: "stream" });

list()는 네임스페이스의 키를 조회합니다. prefix로 특정 접두사를 가진 키만 필터링할 수 있어요. 한 번에 최대 1,000개를 반환합니다. 더 많은 키가 있으면 list_completefalse로 오니까 커서 기반으로 페이지네이션하면 돼요.

let cursor;
let allKeys = [];

do {
  const result = await env.MY_CACHE.list({ prefix: "user:", cursor });
  allKeys.push(...result.keys);
  cursor = result.list_complete ? undefined : result.cursor;
} while (cursor);

TTL로 만료 시간 설정

KV에 데이터를 저장할 때 TTL(Time To Live)을 설정하면 지정한 시간이 지난 뒤 자동으로 삭제됩니다. 캐시 데이터나 임시 토큰에 유용해요.

// 1시간 뒤 만료 (초 단위)
await env.MY_CACHE.put("session:abc", tokenData, {
  expirationTtl: 3600,
});

// 특정 시점에 만료 (Unix 타임스탬프)
await env.MY_CACHE.put("promo:summer", promoData, {
  expiration: Math.floor(Date.now() / 1000) + 86400, // 24시간 뒤
});

expirationTtl은 “지금부터 N초 뒤”를 지정하고 expiration은 정확한 Unix 타임스탬프를 지정합니다. TTL에는 최솟값이 정해져 있어서 아주 짧은 만료 시간은 설정할 수 없어요.

TTL이 설정된 키를 get()으로 읽으면 만료 전까지는 정상적으로 값이 반환되고 만료 후에는 null이 반환됩니다. 명시적으로 delete()를 호출할 필요가 없어서 세션 관리나 캐시 무효화에 편리하죠.

메타데이터

KV는 값과 함께 메타데이터를 저장할 수 있습니다. 메타데이터는 값을 읽지 않고도 조회할 수 있어서 값이 큰 경우에 유용해요.

// 메타데이터와 함께 저장
await env.MY_CACHE.put("file:report.pdf", fileBuffer, {
  metadata: {
    contentType: "application/pdf",
    uploadedBy: "dale",
    size: 1024000,
  },
});

// 메타데이터만 읽기 (값은 읽지 않음)
const { metadata } = await env.MY_CACHE.getWithMetadata("file:report.pdf");
console.log(metadata.contentType); // "application/pdf"
console.log(metadata.uploadedBy); // "dale"

getWithMetadata()는 값과 메타데이터를 함께 반환합니다. 메타데이터만 필요한 경우에도 이 메서드를 쓰면 됩니다. 값 본문을 읽고 싶지 않다면 type: "stream"으로 요청한 뒤 스트림을 소비하지 않는 방법이 있어요.

list()로 키 목록을 조회할 때도 메타데이터가 함께 반환됩니다. 파일 목록을 보여주면서 각 파일의 크기나 타입을 메타데이터로 확인하는 패턴이 흔하죠.

키 설계

KV는 SQL 같은 쿼리를 지원하지 않기 때문에 키 이름을 잘 설계하는 게 중요합니다. list()prefix 필터가 유일한 조회 수단이거든요.

콜론(:)이나 슬래시(/)로 계층을 표현하는 패턴이 일반적입니다.

// 사용자 관련
"user:123"; // 사용자 정보
"user:123:settings"; // 사용자 설정
"user:123:sessions"; // 사용자 세션 목록

// 설정 관련
"config:theme"; // 테마 설정
"config:lang"; // 언어 설정

// 캐시 관련
"cache:api:/products"; // API 응답 캐시
"cache:page:/about"; // 페이지 캐시

이렇게 설계하면 list({ prefix: "user:123:" })로 특정 사용자의 모든 데이터를 조회할 수 있고 list({ prefix: "cache:" })로 모든 캐시를 순회할 수 있어요.

키 길이와 값 크기에는 상한이 있습니다. 값이 수십 MB를 넘는 파일이라면 R2를 사용하는 게 맞습니다.

로컬 개발

wrangler dev를 실행하면 KV도 로컬에서 시뮬레이션됩니다.

npx wrangler dev

Miniflare가 로컬 파일 시스템에 KV 데이터를 저장해서 프로덕션과 동일한 API로 동작합니다. .wrangler/state/ 디렉토리에 데이터가 저장되니까 .gitignore에 추가해두세요.

Wrangler CLI로 로컬 KV에 직접 데이터를 넣어볼 수도 있습니다.

npx wrangler kv key put --binding MY_CACHE "config:theme" "dark" --local
npx wrangler kv key get --binding MY_CACHE "config:theme" --local

여러 키-값 쌍을 한꺼번에 넣어야 한다면 bulk put이 편해요.

npx wrangler kv bulk put --binding MY_CACHE data.json --local
data.json
[
  { "key": "config:theme", "value": "dark" },
  { "key": "config:lang", "value": "ko" },
  { "key": "config:timezone", "value": "Asia/Seoul" }
]

용량과 제한

Workers KV는 무료 플랜에서도 넉넉한 읽기/쓰기 한도를 제공하고 유료 플랜에서는 훨씬 여유롭습니다. 네임스페이스당 키 개수에는 제한이 없어요.

쓰기 후 전 세계 전파에 시간이 걸릴 수 있다는 점은 아키텍처 설계 시 꼭 고려해야 합니다. 같은 키를 빠르게 읽고 쓰는 패턴이라면 최신 값이 보장되지 않을 수 있어요. 최신 한도와 제한 사항은 공식 문서에서 확인할 수 있습니다.

어떤 상황에 쓸까

Workers KV가 잘 맞는 경우와 다른 선택지가 나은 경우를 정리하면 이렇습니다.

KV가 적합한 경우는 사이트 설정이나 기능 플래그처럼 자주 읽고 가끔 바꾸는 데이터, API 응답 캐시처럼 TTL로 자동 만료시키는 데이터, 세션 토큰이나 인증 정보 저장 등입니다.

반면 SQL 쿼리가 필요하거나 데이터 간 관계를 표현해야 한다면 D1이 맞습니다. 이미지나 동영상 같은 대용량 파일을 저장하려면 R2가 적합해요. 강한 일관성이 필요한 실시간 카운터나 협업 기능에는 Durable Objects를 고려해보세요.

마치며

Workers KV는 Cloudflare 생태계에서 가장 단순하면서도 쓸모 있는 저장소입니다. 키-값이라는 단순한 모델 덕분에 진입 장벽이 낮고 전 세계 엣지에서 빠르게 읽을 수 있다는 건 다른 저장소에서 쉽게 얻기 어려운 장점이에요.

읽기가 많고 쓰기가 적은 패턴이라면 KV만으로도 꽤 많은 것을 할 수 있습니다. 여기에 D1R2를 조합하면 Cloudflare 위에서 대부분의 데이터 요구사항을 해결할 수 있죠.

더 자세한 내용은 Workers KV 공식 문서를 참고하세요.

This work is licensed under CC BY 4.0 CC BY

개발자를 위한 뉴스레터

달레가 정리한 AI 개발 트렌드와 직접 만든 콘텐츠를 전해드립니다.

Discord