
Cloudflare Workers: 엣지에서 코드 실행하기
서버가 미국에 있어서 한국 사용자 응답이 느렸는데, Cloudflare Workers로 전 세계 엣지에서 코드를 실행하니 응답 시간이 극적으로 줄었다.

서버가 미국에 있어서 한국 사용자 응답이 느렸는데, Cloudflare Workers로 전 세계 엣지에서 코드를 실행하니 응답 시간이 극적으로 줄었다.
새벽엔 낭비하고 점심엔 터지는 서버 문제 해결기. '택시 배차'와 '피자 배달' 비유로 알아보는 오토 스케일링과 서버리스의 차이, 그리고 Spot Instance를 활용한 비용 절감 꿀팁.

느리다고 느껴서 감으로 최적화했는데 오히려 더 느려졌다. 프로파일러로 병목을 정확히 찾는 법을 배운 이야기.

텍스트에서 바이너리로(HTTP/2), TCP에서 UDP로(HTTP/3). 한 줄로서기 대신 병렬처리 가능해진 웹의 진화. 구글이 주도한 QUIC 프로토콜 이야기.

HTML 파싱부터 DOM, CSSOM 생성, 렌더 트리, 레이아웃(Reflow), 페인트(Repaint), 그리고 합성(Composite)까지. 브라우저가 화면을 그리는 6단계 과정과 치명적인 렌더링 성능 최적화(CRP) 가이드.

작은 SaaS를 만들어서 런칭했다. 서버는 AWS 버지니아 리전에 있었다. 미국 사용자들은 만족했지만, 한국 친구가 써보더니 "왜 이렇게 느려?"라고 물었다. 당연했다. 서울에서 버지니아까지 왕복 200ms 이상 걸렸으니까.
전통적인 해결책은 리전을 여러 개 띄우는 것이다. 서울 리전, 도쿄 리전, 프랑크푸르트 리전... 하지만 각 리전마다 서버를 관리하고, 데이터베이스를 동기화하고, 배포 파이프라인을 구축해야 했다. 소규모 프로젝트에는 과한 솔루션이었다.
그때 Cloudflare Workers를 알게 됐다. "엣지에서 코드를 실행한다"는 개념이었다. 처음엔 추상적으로 들렸는데, 써보고 나니 완전히 이해했다. 결국 이거였다. 사용자와 가장 가까운 곳에서 코드가 실행되는 것.
엣지 컴퓨팅을 이해하는 가장 좋은 비유는 커피 프랜차이즈다.
전통적인 서버는 본사 하나만 있는 것과 같다. 서울 고객이 주문하려면 미국 본사까지 전화해야 한다. 왕복 시간이 오래 걸린다.
엣지 컴퓨팅은 전 세계 300개 이상의 지점이 있는 프랜차이즈다. 서울 고객은 강남점에서, 도쿄 고객은 시부야점에서 주문한다. 각 지점은 같은 메뉴(코드)를 제공하지만, 물리적으로 가까워서 응답이 빠르다.
Cloudflare는 이미 CDN으로 전 세계 데이터센터를 운영하고 있었다. Workers는 그 인프라 위에서 정적 파일뿐만 아니라 동적 코드도 실행할 수 있게 한 것이다.
AWS Lambda도 서버리스다. 왜 Workers가 특별할까? 실행 환경이 근본적으로 다르기 때문이다.
Lambda는 컨테이너 기반이다. 요청이 오면 컨테이너를 띄우고, Node.js 런타임을 초기화하고, 코드를 로드한다. 콜드 스타트 때 수백 ms가 걸린다. 따뜻한 상태(warm state)를 유지하려면 지속적인 트래픽이 필요하다.
Workers는 V8 Isolate를 사용한다. Chrome 브라우저가 탭마다 격리된 JavaScript 환경을 만드는 것처럼, Workers도 각 요청을 격리된 V8 컨텍스트에서 실행한다. 컨테이너보다 훨씬 가볍다. 콜드 스타트가 5ms 이하다.
이 차이가 와닿았던 순간이 있다. Lambda로 만든 API는 가끔 첫 요청이 1초 넘게 걸렸다. Workers로 옮기니 항상 50ms 이내였다. 사용자 경험이 완전히 달라졌다.
// Workers의 기본 구조
export default {
async fetch(request, env, ctx) {
return new Response('Hello from the edge!', {
headers: { 'Content-Type': 'text/plain' },
});
},
};
이게 전부다. fetch 핸들러만 있으면 된다. Express나 프레임워크 없이 Web Standard API만 사용한다.
Workers API는 웹 표준을 따른다. fetch, Request, Response, URL 같은 브라우저 API와 동일하다. Node.js에서 벗어나서 처음엔 불편했지만, 금방 이점을 깨달았다.
env 객체로 전달된다.
export default {
async fetch(request, env, ctx) {
const apiKey = env.API_KEY; // wrangler.toml에서 정의한 변수
const db = env.DB; // D1 데이터베이스 바인딩
const kv = env.CACHE; // KV 네임스페이스 바인딩
return new Response(`API Key: ${apiKey}`);
},
};
비동기 처리는 ctx.waitUntil()로 한다. 응답을 보낸 후에도 백그라운드 작업을 실행할 수 있다. 로깅이나 분석 데이터 전송에 유용하다.
export default {
async fetch(request, env, ctx) {
// 즉시 응답 반환
const response = new Response('Done!');
// 응답 후에도 비동기 작업 실행
ctx.waitUntil(
fetch('https://analytics.example.com/log', {
method: 'POST',
body: JSON.stringify({ path: request.url }),
})
);
return response;
},
};
Workers만으로는 상태(state)를 저장할 수 없다. 각 요청은 격리되어 있으니까. 그래서 Cloudflare는 KV(Key-Value) 스토리지를 제공한다.
KV의 특징은 최종 일관성(eventual consistency)이다. 데이터를 쓰면 전 세계 엣지로 복제되는데, 60초 정도 걸릴 수 있다. 즉시 일관성이 필요한 데이터에는 맞지 않지만, 자주 읽고 드물게 쓰는 데이터에는 완벽하다.
내 경우엔 API 응답 캐싱에 썼다. 외부 API를 호출하는 대신, KV에서 캐시된 값을 읽으니 응답이 10배 빨라졌다.
export default {
async fetch(request, env, ctx) {
const cache = env.CACHE; // KV 네임스페이스
const cacheKey = new URL(request.url).pathname;
// 캐시 확인
let data = await cache.get(cacheKey, { type: 'json' });
if (!data) {
// 캐시 미스: 외부 API 호출
const response = await fetch('https://api.example.com/data');
data = await response.json();
// 캐시 저장 (1시간 TTL)
ctx.waitUntil(
cache.put(cacheKey, JSON.stringify(data), {
expirationTtl: 3600,
})
);
}
return new Response(JSON.stringify(data), {
headers: { 'Content-Type': 'application/json' },
});
},
};
KV는 간단한 키-밸류 저장에는 좋지만, 관계형 데이터에는 부족하다. 그래서 Cloudflare는 D1을 만들었다. SQLite를 엣지에서 실행하는 것이다.
D1은 중앙 데이터베이스를 두고, 각 엣지에서 읽기 전용 복제본을 유지한다. 쓰기는 중앙으로 가지만, 읽기는 가까운 엣지에서 처리된다. 읽기가 많은 애플리케이션에 적합하다.
export default {
async fetch(request, env, ctx) {
const db = env.DB; // D1 바인딩
// SQL 쿼리 실행
const { results } = await db.prepare(
'SELECT * FROM users WHERE email = ?'
).bind('user@example.com').all();
return new Response(JSON.stringify(results), {
headers: { 'Content-Type': 'application/json' },
});
},
};
내 프로젝트에서는 사용자 설정을 D1에 저장했다. 전 세계 어디서든 빠르게 읽을 수 있었다.
AWS S3는 훌륭하지만, 이그레스(egress) 비용이 문제다. 데이터를 내보낼 때마다 GB당 요금을 낸다. 트래픽이 많으면 비용이 폭발한다.
Cloudflare R2는 S3 호환 API를 제공하면서, 이그레스 비용이 0원이다. 저장 비용만 내면 된다. 이미지나 비디오를 많이 서빙하는 서비스에 게임 체인저였다.
export default {
async fetch(request, env, ctx) {
const bucket = env.MY_BUCKET; // R2 바인딩
const url = new URL(request.url);
const key = url.pathname.slice(1); // '/image.png' -> 'image.png'
// R2에서 객체 가져오기
const object = await bucket.get(key);
if (!object) {
return new Response('Not Found', { status: 404 });
}
return new Response(object.body, {
headers: {
'Content-Type': object.httpMetadata.contentType,
'Cache-Control': 'public, max-age=31536000',
},
});
},
};
Workers를 개발하려면 Wrangler CLI가 필요하다. 로컬 개발 서버, 배포, 환경 변수 관리를 모두 처리한다.
# 새 프로젝트 생성
npm create cloudflare@latest my-worker
# 로컬 개발 서버 실행
npx wrangler dev
# 배포
npx wrangler deploy
wrangler.toml 파일에서 모든 설정을 관리한다.
name = "my-worker"
main = "src/index.js"
compatibility_date = "2026-01-29"
# KV 바인딩
[[kv_namespaces]]
binding = "CACHE"
id = "abcd1234"
# D1 바인딩
[[d1_databases]]
binding = "DB"
database_name = "my-database"
database_id = "xyz789"
# R2 바인딩
[[r2_buckets]]
binding = "MY_BUCKET"
bucket_name = "my-bucket"
# 환경 변수
[vars]
ENVIRONMENT = "production"
로컬 개발 경험이 훌륭하다. wrangler dev로 실행하면 실제 엣지 환경과 거의 동일하게 작동한다.
Workers를 쓰면서 발견한 킬러 유스케이스들이 있다.
1. API 프록시와 캐싱외부 API를 Workers로 감싸면, 지역별 레이턴시를 줄이고 응답을 캐싱할 수 있다. 느린 API도 빠르게 만든다.
2. A/B 테스팅엣지에서 사용자를 그룹으로 나누고, 다른 페이지나 API 응답을 제공한다. 서버 코드 수정 없이 가능하다.
export default {
async fetch(request, env, ctx) {
const variant = Math.random() < 0.5 ? 'A' : 'B';
return new Response(`You're in variant ${variant}`, {
headers: {
'Set-Cookie': `variant=${variant}; Path=/; Max-Age=86400`,
},
});
},
};
3. 인증 게이트웨이
모든 요청을 엣지에서 먼저 검증한다. 유효하지 않은 요청은 오리진 서버까지 가지 않는다. 보안과 성능 모두 개선된다.
4. 이미지 최적화업로드된 이미지를 Workers에서 리사이징, WebP 변환, 압축한다. Cloudflare Images와 조합하면 강력하다.
5. 리다이렉트와 URL 재작성짧은 URL 서비스, 레거시 URL 리다이렉트, 국가별 콘텐츠 라우팅 같은 걸 엣지에서 처리한다.
Workers는 만능이 아니다. 제약사항을 알고 써야 한다.
CPU 시간 제한: 무료 플랜은 요청당 10ms, 유료는 50ms까지만 CPU를 쓸 수 있다. 무거운 연산(이미지 처리, 암호화)은 부담스럽다.
메모리 제한: 128MB까지만 쓸 수 있다. 큰 파일을 메모리에 로드하면 실패한다.
Node.js 호환성: Node.js API를 완전히 지원하지 않는다. fs, child_process 같은 모듈은 못 쓴다. 웹 표준 API만 사용해야 한다.
콜드 스타트는 빠르지만 제로는 아니다: V8 Isolate가 컨테이너보다 빠르지만, 여전히 몇 ms는 걸린다. 마이크로초 단위가 중요하면 부족할 수 있다.
디버깅의 어려움: 엣지 환경이라 로컬과 완전히 같지 않다. 프로덕션에서만 나타나는 버그를 재현하기 어렵다.
이런 제약 때문에 Workers는 경량 작업(라우팅, 캐싱, 인증)에 적합하다. 복잡한 비즈니스 로직은 여전히 전통적인 서버가 낫다.
Workers의 가격 모델은 관대하다.
무료 플랜:개인 프로젝트나 사이드 프로젝트는 무료로 충분하다. 내 블로그 API는 무료 플랜으로 6개월째 돌아간다.
유료 플랜 ($5/월):트래픽이 늘어나도 가격이 합리적이다. Lambda보다 훨씬 저렴하다.
Workers를 쓰기 전에는 "엣지 컴퓨팅"이 고급 최적화 기법이라고 생각했다. 대규모 서비스에나 필요한 것처럼 느껴졌다.
하지만 써보니 달랐다. 엣지는 복잡한 게 아니라 더 간단한 방식이었다. 리전을 고민할 필요 없고, 로드 밸런서 설정할 필요 없고, 스케일링 걱정할 필요 없다. 코드를 배포하면 끝이다. 전 세계 사용자가 빠른 응답을 받는다.
특히 소규모 팀에게 엣지는 게임 체인저다. 인프라 운영에 시간을 뺏기지 않고 제품에 집중할 수 있다. 나 혼자 만든 SaaS가 전 세계 사용자에게 일관되게 빠른 이유는 Workers 덕분이다.
Cloudflare Workers는 서버리스의 다음 단계다. Lambda가 "서버 관리 없음"을 가져왔다면, Workers는 "지역 고민 없음"을 가져왔다. 코드를 쓰고, 배포하고, 전 세계가 쓰게 한다. 결국 이게 미래다.