Deno Deploy를 중심으로 서버리스 및 엣지 배포 전략을 다룹니다. 엣지 컴퓨팅 개념, 콜드 스타트 성능, Deno KV를 활용한 엣지 상태 관리, Cloudflare Workers 호환성을 분석합니다.
전통적인 서버 배포 모델에서는 애플리케이션이 특정 리전(Region)의 서버에서 실행됩니다. 서울에 서버를 두면 한국 사용자에게는 빠르지만, 미국이나 유럽 사용자에게는 수백 밀리초의 네트워크 지연이 발생합니다.
엣지 컴퓨팅(Edge Computing)은 이 문제를 해결합니다. 코드를 전 세계 수십 개 지점(엣지 노드)에 분산 배포하여, 사용자와 가장 가까운 서버에서 요청을 처리합니다.
Deno는 엣지 컴퓨팅에 최적화된 런타임입니다. 빠른 시작 시간, 작은 메모리 풋프린트, 웹 표준 API 기반 설계가 엣지 환경의 요구사항과 정확히 맞아떨어집니다.
Deno Deploy는 Deno 팀이 운영하는 공식 서버리스 엣지 플랫폼입니다. 전 세계 35개 이상의 리전에서 Deno 코드를 실행할 수 있으며, GitHub 저장소와 연동하여 자동 배포를 지원합니다.
- 전 세계 35+ 리전에 자동 분산 배포
- V8 Isolate 기반 실행 (가상 머신이 아님)
- 콜드 스타트 없음에 가까운 시작 시간
- 웹 표준 API 완벽 지원
- Deno KV 전역 분산 데이터베이스 내장
- GitHub 연동 자동 배포 (프리뷰 배포 포함)
- 무료 티어 포함Deno Deploy로의 배포는 매우 간단합니다.
# deployctl 설치
deno install -gArf jsr:@deno/deployctl
# 프로젝트 배포
deployctl deploy --project=my-app src/main.ts
# 프로덕션 배포 (고정 URL)
deployctl deploy --project=my-app --prod src/main.ts// main.ts - Deno Deploy에서 실행되는 코드
Deno.serve(async (req: Request) => {
const url = new URL(req.url);
// 라우팅
if (url.pathname === "/") {
return new Response("Deno Deploy에서 실행 중입니다", {
headers: { "Content-Type": "text/plain; charset=utf-8" },
});
}
if (url.pathname === "/api/time") {
return new Response(
JSON.stringify({
timestamp: new Date().toISOString(),
region: Deno.env.get("DENO_REGION") ?? "unknown",
}),
{
headers: { "Content-Type": "application/json" },
},
);
}
return new Response("Not Found", { status: 404 });
});Deno Deploy에서는 DENO_REGION 환경 변수를 통해 현재 요청을 처리하는 엣지 노드의 위치를 확인할 수 있습니다. 이를 활용하면 리전별 로직 분기나 지연 시간 모니터링이 가능합니다.
1. Deno Deploy 대시보드에서 GitHub 저장소 연결
2. 엔트리포인트 파일 지정 (예: src/main.ts)
3. main 브랜치에 push하면 자동 프로덕션 배포
4. PR을 열면 프리뷰 URL 자동 생성
5. 프리뷰 URL로 변경사항 검증 후 머지이 워크플로우는 Vercel이나 Netlify와 유사하지만, Deno 런타임에 최적화된 환경을 제공합니다.
서버리스 플랫폼에서 가장 큰 성능 과제는 콜드 스타트(Cold Start)입니다. 요청이 들어왔을 때 실행 환경이 준비되지 않은 상태라면, 환경을 초기화하는 데 걸리는 시간이 응답 지연으로 직결됩니다.
Deno Deploy는 V8 아이솔레이트(Isolate) 기반으로 실행됩니다. 전통적인 컨테이너 기반 서버리스(AWS Lambda 등)와 달리, 아이솔레이트는 밀리초 단위로 생성됩니다.
Deno Deploy (V8 Isolate)
- 콜드 스타트: ~10ms
- 웜 스타트: ~0ms
AWS Lambda (Node.js, 컨테이너)
- 콜드 스타트: ~200-500ms
- 웜 스타트: ~1-5ms
AWS Lambda (Node.js, SnapStart)
- 콜드 스타트: ~100-200ms
- 웜 스타트: ~1-5ms
Cloudflare Workers (V8 Isolate)
- 콜드 스타트: ~5ms
- 웜 스타트: ~0msV8 Isolate 기반 실행의 핵심 장점은 "0ms에 가까운 콜드 스타트"입니다. 컨테이너를 부팅하는 것이 아니라 V8 엔진 내에 격리된 실행 환경을 생성하는 것이므로, 오버헤드가 극히 적습니다. 이것이 Deno Deploy와 Cloudflare Workers가 엣지 환경에서 뛰어난 성능을 보이는 근본적인 이유입니다.
Isolate 기반 실행에는 몇 가지 제약이 있습니다.
실행 시간 제한
- 요청당 최대 실행 시간: 50초 (기본), 150초 (유료)
- 장기 실행 작업에는 적합하지 않음
메모리 제한
- Isolate당 메모리 제한 (약 512MB)
- 대용량 데이터 처리에 제약
파일 시스템
- 읽기 전용 파일 시스템 (정적 파일만 접근)
- 동적 파일 생성/수정 불가
네이티브 모듈
- C/C++ 네이티브 모듈 사용 불가
- Wasm(WebAssembly)으로 대체 가능엣지 환경에서 가장 어려운 과제 중 하나는 상태(State) 관리입니다. 요청이 전 세계 어느 엣지 노드에서든 처리될 수 있으므로, 데이터의 일관성과 접근성이 중요합니다.
Deno KV는 이 문제를 해결하는 Deno Deploy의 내장 분산 데이터베이스입니다.
const kv = await Deno.openKv();
interface Session {
userId: string;
email: string;
createdAt: string;
expiresAt: string;
}
async function createSession(userId: string, email: string): Promise<string> {
const sessionId = crypto.randomUUID();
const now = new Date();
const expires = new Date(now.getTime() + 24 * 60 * 60 * 1000); // 24시간
const session: Session = {
userId,
email,
createdAt: now.toISOString(),
expiresAt: expires.toISOString(),
};
await kv.set(["sessions", sessionId], session, {
expireIn: 24 * 60 * 60 * 1000, // 자동 만료
});
return sessionId;
}
async function getSession(sessionId: string): Promise<Session | null> {
const result = await kv.get<Session>(["sessions", sessionId]);
return result.value;
}
async function deleteSession(sessionId: string): Promise<void> {
await kv.delete(["sessions", sessionId]);
}
// HTTP 서버에서 활용
Deno.serve(async (req) => {
const url = new URL(req.url);
if (url.pathname === "/api/login" && req.method === "POST") {
const { userId, email } = await req.json();
const sessionId = await createSession(userId, email);
return new Response(JSON.stringify({ ok: true }), {
headers: {
"Content-Type": "application/json",
"Set-Cookie": `session=${sessionId}; HttpOnly; Secure; SameSite=Strict; Path=/`,
},
});
}
if (url.pathname === "/api/me") {
const cookie = req.headers.get("Cookie") ?? "";
const sessionId = cookie.match(/session=([^;]+)/)?.[1];
if (!sessionId) {
return new Response("인증 필요", { status: 401 });
}
const session = await getSession(sessionId);
if (!session) {
return new Response("세션 만료", { status: 401 });
}
return new Response(JSON.stringify(session), {
headers: { "Content-Type": "application/json" },
});
}
return new Response("Not Found", { status: 404 });
});const kv = await Deno.openKv();
async function incrementPageView(path: string): Promise<number> {
// 원자적 증가 연산
let retries = 0;
while (retries < 5) {
const current = await kv.get<number>(["pageviews", path]);
const newCount = (current.value ?? 0) + 1;
const result = await kv.atomic()
.check(current) // 낙관적 잠금
.set(["pageviews", path], newCount)
.commit();
if (result.ok) {
return newCount;
}
retries++;
}
throw new Error("카운터 업데이트 실패 (동시성 충돌)");
}
Deno.serve(async (req) => {
const url = new URL(req.url);
// 페이지 방문 시 카운터 증가
const views = await incrementPageView(url.pathname);
return new Response(
JSON.stringify({
path: url.pathname,
views,
region: Deno.env.get("DENO_REGION"),
}),
{
headers: { "Content-Type": "application/json" },
},
);
});Deno KV의 전역 분산 모드에서는 최종 일관성(Eventual Consistency) 모델을 사용합니다. 한 리전에서 쓴 데이터가 다른 리전에 반영되기까지 약간의 지연이 있을 수 있습니다. 강한 일관성(Strong Consistency)이 필요한 경우에는 특정 리전을 지정하여 읽기/쓰기를 수행하거나, 별도의 데이터베이스를 사용해야 합니다.
Deno와 Cloudflare Workers는 모두 WinterCG 표준을 지향하므로, 핵심 코드의 상당 부분이 양쪽에서 호환됩니다.
// 이 코드는 Deno Deploy와 Cloudflare Workers 모두에서 동작합니다
async function handleRequest(request: Request): Promise<Response> {
const url = new URL(request.url);
switch (url.pathname) {
case "/": {
return new Response("환영합니다", {
headers: { "Content-Type": "text/plain; charset=utf-8" },
});
}
case "/api/data": {
const data = { timestamp: Date.now(), message: "엣지에서 응답" };
return new Response(JSON.stringify(data), {
headers: { "Content-Type": "application/json" },
});
}
case "/api/hash": {
const body = await request.text();
const encoder = new TextEncoder();
const hash = await crypto.subtle.digest("SHA-256", encoder.encode(body));
const hex = Array.from(new Uint8Array(hash))
.map((b) => b.toString(16).padStart(2, "0"))
.join("");
return new Response(JSON.stringify({ hash: hex }), {
headers: { "Content-Type": "application/json" },
});
}
default:
return new Response("Not Found", { status: 404 });
}
}
// Deno Deploy용 엔트리포인트
Deno.serve(handleRequest);// Cloudflare Workers에서 동일한 handleRequest 사용
export default {
async fetch(request: Request): Promise<Response> {
return handleRequest(request);
},
};Deno Deploy 전용
- Deno.openKv() - 내장 KV 저장소
- Deno.cron() - 내장 크론 스케줄러
- Deno.env.get("DENO_REGION") - 리전 정보
Cloudflare Workers 전용
- KV (Cloudflare KV)
- R2 (오브젝트 스토리지)
- D1 (SQLite 기반 DB)
- Durable Objects (상태 관리)
공통 (WinterCG 호환)
- fetch, Request, Response
- URL, URLSearchParams
- crypto.subtle
- TextEncoder, TextDecoder
- Headers, AbortController
- ReadableStream, WritableStream코드의 이식성을 극대화하려면, 핵심 비즈니스 로직은 웹 표준 API만 사용하여 작성하고, 플랫폼 전용 API(Deno KV, Cloudflare KV 등)는 어댑터 패턴으로 분리하는 것을 권장합니다. 이렇게 하면 플랫폼을 변경할 때 어댑터만 교체하면 됩니다.
가장 일반적인 엣지 배포 패턴입니다.
사용자 요청
|
v
엣지 노드 (CDN)
|-- 정적 파일 (HTML, CSS, JS, 이미지) --> 즉시 응답
|-- API 요청 (/api/*) --> Deno Deploy 엣지 함수
|
v
Deno KV / 외부 DBFresh 프레임워크와 Deno Deploy의 조합은 가장 자연스러운 Deno 풀스택 배포 패턴입니다.
// fresh.config.ts
import { defineConfig } from "fresh";
export default defineConfig({
// Deno Deploy에서 자동으로 최적화됨
});
// routes/index.tsx - SSR이 엣지에서 수행
import { page } from "fresh";
export default page(function Home() {
return (
<div>
<h1>엣지에서 렌더링된 페이지</h1>
<p>
이 페이지는 사용자와 가장 가까운 엣지 노드에서 서버 사이드 렌더링됩니다.
</p>
</div>
);
});Deno Deploy는 내장 크론 스케줄러도 제공합니다.
// 매 시간 실행되는 크론 작업
Deno.cron("hourly-cleanup", "0 * * * *", async () => {
const kv = await Deno.openKv();
// 만료된 세션 정리
const iter = kv.list({ prefix: ["sessions"] });
const now = Date.now();
for await (const entry of iter) {
const session = entry.value as { expiresAt: string };
if (new Date(session.expiresAt).getTime() < now) {
await kv.delete(entry.key);
}
}
console.log("만료 세션 정리 완료");
});
// 매일 자정에 실행
Deno.cron("daily-stats", "0 0 * * *", async () => {
const kv = await Deno.openKv();
// 일일 통계 집계
const stats = { date: new Date().toISOString().split("T")[0], views: 0 };
const iter = kv.list<number>({ prefix: ["pageviews"] });
for await (const entry of iter) {
stats.views += entry.value;
}
await kv.set(["daily-stats", stats.date], stats);
console.log(`일일 통계 저장: ${stats.views} views`);
});성능 최적화
[ ] 응답에 적절한 Cache-Control 헤더 설정
[ ] 정적 자산은 CDN에서 서빙
[ ] KV 조회 결과를 메모리에 캐싱 (짧은 TTL)
[ ] 불필요한 외부 API 호출 최소화
보안
[ ] 환경 변수로 시크릿 관리 (하드코딩 금지)
[ ] CORS 헤더 적절히 설정
[ ] Rate limiting 구현
[ ] 입력 값 검증 철저히
모니터링
[ ] 에러 로깅 설정
[ ] 응답 시간 모니터링
[ ] KV 사용량 추적
[ ] 리전별 트래픽 분석엣지 환경에서는 외부 데이터베이스 호출이 병목이 될 수 있습니다. 가능하면 Deno KV로 자주 접근하는 데이터를 캐시하고, 외부 DB는 비동기 업데이트로 동기화하는 CQRS 패턴을 고려하세요.
다음 장에서는 Deno로 REST API를 구축하는 실습을 진행합니다. Deno.serve()부터 프레임워크 활용, 데이터베이스 연동, 테스트, 배포까지 전 과정을 다룹니다.
이 글이 도움이 되셨나요?
Deno의 공식 웹 프레임워크 Fresh를 심층 분석합니다. Islands Architecture, Preact 기반 컴포넌트, 라우팅, 미들웨어, 데이터 페칭 등 핵심 기능을 다룹니다.
Deno.serve()를 활용한 HTTP 서버 구축부터 Hono 프레임워크, PostgreSQL 및 Deno KV 데이터베이스 연동, 테스트 작성, 배포까지 REST API 개발 전 과정을 다룹니다.
Fresh 프론트엔드와 Deno.serve API, Deno KV를 결합한 풀스택 할 일 관리 애플리케이션을 구축합니다. 인증, 배포, 모니터링까지 실전 프로젝트의 전체 과정을 다룹니다.