작년 11월, 우리 팀이 운영하는 SaaS 제품에서 이상한 일이 생겼다. 서울에 있는 사용자가 “왜 이렇게 느려요?”라는 문의를 보내왔는데, 직접 확인해보니 API 응답 시간이 평균 480ms였다. 우리는 AWS us-east-1에 단일 배포로 운영 중이었고, 한국 사용자 입장에서는 태평양을 두 번 건너는 셈이었다.
그때부터 엣지 컴퓨팅을 본격적으로 파고들기 시작했다. 2주 동안 Cloudflare Workers, Fly.io, Deno Deploy를 직접 써보고, 서비스 일부를 실제로 마이그레이션했다. 결론부터 말하면 — 생각보다 효과적이었지만, 생각보다 덜 간단했다.
단일 리전 배포가 한계를 드러내는 순간
솔직히 말하면, 나는 꽤 오랫동안 “us-east-1이면 충분하다”는 생각으로 살았다. 스타트업 특성상 인프라보다 기능 개발에 집중해야 한다는 논리가 있었고, 사실 틀린 말도 아니었다. 3명짜리 팀에서 멀티리전 인프라를 운영하는 건 오버엔지니어링이 맞다.
그런데, 사용자 기반이 바뀌면 이야기가 달라진다.
우리 서비스는 원래 미국 사용자 위주였는데, 어느 순간 아시아 사용자가 전체의 38%를 차지하게 됐다. CDN으로 정적 에셋은 해결했지만 API 레이턴시는 CDN으로 어떻게 할 수 있는 문제가 아니다. 서버에서 DB를 조회하고 응답을 만들어야 하는 로직은 결국 물리적인 거리 문제다. 서울에서 버지니아까지 빛이 달려가는 시간 자체를 압축할 수는 없다.
2026년 들어 엣지가 진짜 실용적인 선택지가 된 건, 이 물리적 한계를 예전 같은 추가 비용 없이도 해결할 수 있게 됐기 때문이다 — 적어도 내가 경험한 규모에서는. 워크로드 특성에 맞게 적절히 분산하면 오히려 비용이 비슷하거나 낮아지는 경우도 생겼다.
실제로 존재하는 선택지들
2025년 말에서 2026년 초 기준으로 내가 직접 써본 옵션들을 정리하면 이렇다.
Cloudflare Workers — 전 세계 330개 이상 PoP에 배포된다. V8 기반 런타임을 쓰기 때문에 Node.js와 API가 비슷하지만 완전히 같지는 않다. 이게 나중에 나를 꽤 고생시켰는데, 이건 뒤에서 따로 얘기하겠다.
Fly.io — 성격이 조금 다르다. Workers처럼 V8 isolate가 아니라 실제 microVM을 전 세계에 분산 배포한다. Docker 컨테이너를 그대로 올릴 수 있어서 마이그레이션 비용이 낮다. 현재 42개 리전을 지원하고, 사용자와 가장 가까운 인스턴스로 자동 라우팅해준다. 내 경험상 기존 백엔드 앱을 옮기기엔 셋 중에 가장 진입 장벽이 낮았다.
Deno Deploy — 솔직히 기대보다 성숙해졌다. Deno 2.0이 나오면서 npm 패키지 호환성이 많이 좋아졌고(초반에 이게 걸려서 포기한 적이 있었는데 지금은 다르다), 배포 경험 자체는 셋 중에 가장 간단했다. 다만 복잡한 워크로드보다는 간단한 API나 edge middleware 용도에 더 맞는다는 느낌이 아직 남아 있다.
세 가지를 비교하면서 깨달은 건, “엣지 컴퓨팅”이라는 단어가 사실 꽤 다른 두 패턴을 하나로 묶어 부른다는 점이다. Workers 같은 서버리스 엣지(stateless, 격리된 실행 환경)와 Fly.io 같은 분산 VM(더 전통적인 서버이지만 지리적으로 분산). 어느 게 낫다기보다는 워크로드 특성에 따라 다른 선택이 된다.
인증 레이어를 엣지로 옮긴 실제 코드
우리 팀에서 먼저 엣지로 옮긴 건 인증 레이어였다. JWT 검증, 세션 확인 같은 작업은 DB 조회가 거의 없고 CPU 연산 위주라서 Workers에 딱 맞는 케이스다.
실제로 작성한 코드는 대략 이런 형태였다:
// auth-edge.ts — Cloudflare Workers
import { verify } from 'hono/jwt';
interface Env {
JWT_SECRET: string;
ORIGIN_URL: string;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
// 정적 에셋은 그냥 통과
if (url.pathname.startsWith('/static/')) {
return fetch(request);
}
const authHeader = request.headers.get('Authorization');
if (!authHeader?.startsWith('Bearer ')) {
return new Response('Unauthorized', { status: 401 });
}
const token = authHeader.slice(7);
try {
// JWT 검증은 엣지에서 — DB 조회가 없으니 빠름
const payload = await verify(token, env.JWT_SECRET);
// 검증된 요청만 오리진으로 포워딩
const originRequest = new Request(
env.ORIGIN_URL + url.pathname + url.search,
{
method: request.method,
headers: {
...Object.fromEntries(request.headers),
'X-User-Id': String(payload.sub),
'X-Verified-At-Edge': 'true',
},
body: ['GET', 'HEAD'].includes(request.method)
? undefined
: request.body,
}
);
return fetch(originRequest);
} catch {
return new Response('Invalid token', { status: 401 });
}
},
};
결국 이 구조의 핵심은 분리다 — 가볍고 반복적인 건 엣지에서, 무거운 비즈니스 로직은 오리진에서. JWT 검증을 엣지에서 처리하면 인증 실패 요청이 오리진까지 아예 도달하지 않아서 오리진 서버 부하도 줄고, 사용자 입장에서는 레이턴시가 눈에 띄게 줄어든다.
실제로 적용하고 나서 서울 기준 API 응답 시간이 480ms에서 140ms로 떨어졌다. 전체 로직을 옮긴 게 아니라 인증 레이어만 바꿨는데 이 정도면 꽤 극적이다.
내가 금요일 오후에 저지른 실수
아무튼, 여기서 삽질 이야기를 빼놓을 수가 없다.
Workers 마이그레이션 초기에 Node.js 코드를 그냥 복붙해서 Workers에 올리려 했다. 구체적으로는 crypto 모듈을 썼는데 — Workers 런타임에서는 Web Crypto API를 써야 한다. Node.js의 crypto.createHmac('sha256', secret)과 Web Crypto의 crypto.subtle.sign(...) 사이의 API 차이가 생각보다 컸다.
금요일 오후에 “그냥 빠르게 올려보자”는 생각으로 배포했다가, HMAC 검증 로직이 조용히 깨지면서 일부 요청이 401을 뱉기 시작했다. 모니터링에서 에러율이 올라가는 걸 보고 롤백했는데, 원인 파악하는 데 1시간 넘게 걸렸다. 로그도 불충분했고, Workers 환경과 로컬 환경 사이의 동작 차이를 처음에 의심하지 않았던 것도 문제였다.
Workers 환경에서는 Node.js 호환 API가 많이 추가됐지만 100% 동일하지는 않다. 특히 node:crypto를 폴리필로 지원하는 경우도 있는데, 동작 방식이 미묘하게 다를 수 있다. Cloudflare 공식 문서의 런타임 API 호환성 페이지를 먼저 꼼꼼히 읽는 게 정답이다 — 나처럼 나중에 고생하지 말고.
그 이후로는 Workers에 새 로직을 올릴 때 반드시 wrangler dev로 충분히 로컬 테스트를 마친 다음 배포한다. 당연한 말인데, 당연한 걸 지키지 않아서 생긴 문제였다.
DB가 단일 리전이면 엣지 효과가 반감된다
인증 레이어는 Workers로 해결했지만, DB 조회가 많은 API 엔드포인트는 다른 접근이 필요했다. 이 경우엔 Fly.io가 더 나은 선택이었다.
여기서 중요한 건 — 엣지에 앱을 분산해도 DB가 단일 리전이면 효과가 반감된다는 점이다. 서울 사용자 요청이 서울 근처 Fly.io 인스턴스에 도달해도, 그 인스턴스가 us-east-1에 있는 Postgres에 쿼리를 날려야 한다면 결국 레이턴시는 거의 줄어들지 않는다. 처음에 이걸 제대로 고려하지 않고 앱만 분산했다가 기대했던 효과가 안 나와서 한동안 멍했던 기억이 있다.
Fly.io에서 이 문제를 해결하는 방법 중 하나는 Fly Postgres의 멀티리전 읽기 복제본이다. 쓰기는 여전히 프라이머리(우리는 Virginia)로 가지만, 읽기는 사용자와 가장 가까운 복제본에서 처리한다. 우리 서비스 특성상 읽기:쓰기 비율이 약 8:2 정도라서 이게 꽤 효과적이었다.
설정 자체는 fly.toml에서 몇 줄 추가하는 수준이라 복잡하지 않았다. 다만 복제 지연(replication lag)이 있기 때문에 방금 쓴 데이터를 바로 읽어야 하는 워크로드에서는 주의가 필요하다. 우리는 쓰기 후 즉시 읽기가 필요한 케이스를 명시적으로 프라이머리로 라우팅하도록 처리했다. 조금 번거롭지만 데이터 일관성 문제로 고생하는 것보다는 낫다.
내가 100% 확신하지 못하는 부분은 — 이 구성이 초당 수천 건 이상의 쓰기가 발생하는 워크로드에서도 잘 버티느냐 하는 점이다. 우리 서비스 규모에서는 문제없이 동작했지만, 훨씬 큰 트래픽에서는 직접 검증해보지 못했다. 그 규모가 되면 아마 다른 고민이 더 많이 생길 것이다.
실제로 어떻게 접근할 것인가
두 달 정도 이것저것 써보고 나서 내가 내린 결론을 솔직하게 말하면 — 모든 걸 엣지로 옮기려는 시도는 하지 말라는 것이다.
엣지가 잘 맞는 케이스는 명확하다. 인증/인가, A/B 테스트 라우팅, 지오 기반 리다이렉션, 간단한 캐싱 레이어. 이런 건 Workers로 빠르게 구현하고 효과도 확실하다. 반면 복잡한 비즈니스 로직, 트랜잭션이 많은 DB 작업, 무거운 연산은 여전히 오리진 서버에 두는 게 맞다. 엣지 환경의 제약(실행 시간 제한, 메모리 한계, 런타임 차이)을 감안하면 무리하게 옮기는 게 오히려 복잡도만 높인다.
내라면 새 프로젝트를 시작할 때 이렇게 접근할 것이다. 우선 단일 리전으로 시작하되, 인증 레이어와 정적 에셋만 처음부터 엣지에 올린다. 그리고 실제 사용자 분포와 레이턴시 데이터를 6개월 정도 보고 나서, 병목이 어디인지 확인한 뒤 추가 분산 여부를 결정한다.
분산 배포는 복잡도를 올린다. 디버깅이 어려워지고, 배포 파이프라인이 복잡해지고, 팀이 이해해야 할 시스템이 늘어난다. 그 복잡도를 감당할 만한 실제 사용자 문제가 있을 때 도입하는 것이지, 미래를 위해 미리 구축하는 건 대부분 낭비로 끝난다.
지금 당장 엣지를 써봐야겠다면 — Cloudflare Workers부터 시작하라. 무료 티어가 생각보다 넉넉하고, wrangler CLI 경험이 괜찮으며, 커뮤니티도 활발하다. Fly.io는 그다음 단계로, 기존 컨테이너 기반 앱을 지리적으로 분산하고 싶을 때 진입 장벽이 가장 낮다. 순서가 중요하다.