모든 대륙에 Prism의 프론트 도어 배치하기
요약
Prism v1.6은 Cloudflare Worker를 활용하여 전 세계 300개 이상의 도시에서 프론트엔드 처리를 수행함으로써 네트워크 지연 시간을 획기적으로 줄였습니다. 엣지(Edge)에서 인증 확인 및 캐시 조회를 처리하여 Mumbai 서버로의 불필요한 왕복을 방지합니다.
핵심 포인트
- Cloudflare Worker를 통한 글로벌 엣지 컴퓨팅 도입
- 인증 헤더 파싱 및 유효성 검사를 엣지에서 즉시 처리
- 캐시 조회를 통해 Mumbai 서버 방문 없이 응답 반환
- X-Prism-Edge-Region 헤더로 처리 지역 디버깅 지원
오늘 전까지는 Bangalore, London, 또는 San Francisco에서 오는 api.ssimplifi.com으로의 모든 요청이 어떤 작업이 수행되기 전에 반드시 Mumbai에 있는 EC2 인스턴스에 물리적으로 도달해야 했습니다. 인도 고객들에게 이는 눈에 보이지 않는 수준이었습니다. Prism이 첫 번째 유용한 바이트(byte) 작업을 수행하기 전까지 네트워크 시간이 20밀리초(ms) 미만이었기 때문입니다. 하지만 San Francisco의 개발자에게는 이야기가 달랐습니다. 요청을 분류하고, 캐시(cache)를 확인하고, AI 제공업체(AI provider)를 호출하기도 전에 왕복 약 250밀리초(ms)가 소요되었습니다. 내부적으로는 세계에서 가장 빠른 프록시(proxy)일지라도, 캘리포니아의 고객은 겪지 않아도 될 0.5초의 지연을 여전히 보게 될 수 있었습니다.
v1.6은 이 0.5초의 문제를 해결합니다. 이를 해결하는 방식은 어느 정도 고민이 필요한 부분이었습니다.
단순한 버전
이제 api.ssimplifi.com은 Cloudflare Worker에 의해 프론트엔드(fronted)됩니다. Cloudflare는 전 세계 300개 이상의 도시에서 해당 워커(worker)를 실행합니다. San Francisco에서의 요청은 San Francisco에 있는 Cloudflare 데이터 센터(datacenter)에 도달하고, London에서의 요청은 London에, Bangalore에서의 요청은 Bangalore에 도달합니다. 워커는 해당 로컬 데이터 센터에서 세 가지 작업을 수행합니다:
-
Authorization헤더를 파싱 (Parses)하고, API 키를 해싱하여 유효한지 확인합니다. 만약 헤더가 잘못되었거나 알 수 없는 경우, 그 자리에서 즉시 401을 반환합니다 — Mumbai까지 왕복(round-trip)하지 않습니다. 이를 통해 잘못된 요청, 기회주의적 스캔(opportunistic scan), 또는 코드에 잘못된 키를 붙여넣은 개발자의 모든 요청마다 약 250밀리초(ms)를 절약할 수 있습니다. -
정확한 캐시를 조회 (Looks up)합니다. Prism은 기본적으로 모든 동일한 요청을 한 시간 동안 캐싱합니다. 만약 귀하의 앱이 5분 전에 던졌던 것과 동일한 질문을 하고 있다면, 워커는 캐시된 답변을 가져와 직접 반환합니다 — Mumbai를 전혀 건드리지 않습니다.
-
그 외의 모든 것은 Mumbai로 전달 (Forwards)합니다. 스트리밍 응답(Streaming responses), 캐시 미스(cache misses), 시맨틱 캐시(semantic cache, "의미가 같은 질문" 매칭), 정책 및 예산 집행(policy and budget enforcement), v1.5 라우터 강화(router hardening), 과금(billing) — 이 모든 것은 여전히 Mumbai에서 변경 없이 실행됩니다. 워커는 프론트 도어(front door)이지, 재구현(re-implementation)이 아닙니다. 우리는 가격 책정이나 정책 집행을 엣지(edge)에서 재구현하는 것을 신뢰하지 않습니다. 그 부분들은 버그가 발생했을 때 곧바로 금전적 손실로 이어지는 영역이기 때문입니다.
이제 모든 응답에는 어떤 일이 일어났는지 확인할 수 있는 두 개의 새로운 헤더가 포함됩니다:
X-Prism-Edge-Cache: hit | passthrough | auth-reject
X-Prism-Edge-Region: SIN | SFO | FRA | BOM | ...
X-Prism-Edge-Region에 표시된 IATA 공항 코드는 귀하의 요청을 처리한 Cloudflare PoP(Point of Presence)를 나타냅니다. "내 클라이언트가 내가 예상한 엣지에 접속하고 있는가?"를 디버깅할 때 유용합니다.
지연 시간(Latency)에 대한 솔직한 보고
제가 여러분께 정말 말하고 싶은 내용은 이렇습니다: 캐시 히트(cache hit) 시 이제 전 세계 어디에서든 50밀리초(ms) 만에 응답이 돌아온다는 것입니다. 그것이 v1.6의 이상적인 모습입니다.
하지만 오늘 출시된 것은 그것이 아닙니다. 오늘 출시된 것은 그 꿈에 완벽히 도달하지는 못하더라도 의미 있는 개선을 이룬 버전입니다. 저는 여러분이 직접 측정해보고 제가 과장했다고 느끼게 만드는 것보다, 차라리 진실을 말씀드리고 추후에 보완해 나가는 쪽을 택하겠습니다.
인증 거부(auth-rejection) 측면에서의 승리는 확실하고 깔끔합니다. 이제 잘못된 형식의 API 키는 고객과 가장 가까운 Cloudflare PoP(Point of Presence)에서 401 응답을 받게 됩니다. 이는 해당 지역 내에서 몇 밀리초(ms)에서 지역 간 이동 시 약 50ms 정도의 시간이 소요됩니다. Mumbai(뭄바이)까지 왕복하던 이전의 600ms와 비교하면, 잘못된 키 요청에 대한 지연 시간(latency)이 약 95% 감소한 것입니다. 더 중요한 점은, 이러한 요청들이 Mumbai의 컴퓨팅 자원을 전혀 소모하지 않으므로, 실제 고객들의 정상적인 요청이 대기 시간(queue time)을 덜 겪게 된다는 것입니다.
캐시 히트(cache-hit) 측면에서의 승리는 부분적입니다. 에지(edge)에서 캐시된 항목을 찾으면 이를 즉시 반환합니다. 하지만 해당 항목들을 저장하는 캐시 자체인 Upstash Redis 인스턴스는 단일 지역인 Mumbai에 위치합니다. 따라서 에지라 할지라도 캐시를 읽기 위해서는 Mumbai까지 왕복해야 합니다. 싱가포르에서는 편도 약 70ms, 샌프란시스코에서는 편도 250ms가 소요됩니다. 따라서 현재의 에지 캐시 히트는 지리적 위치에 따라 엔드 투 엔드(end-to-end)로 약 300~500ms 정도 걸립니다. 이는 이전의 Mumbai 오리진(origin) 경로보다 약 200ms 빠른 것이지만, 제가 설계 문서(design doc)에서 약속했던 50ms에는 미치지 못합니다.
해결책은 작은 후속 릴리스에 포함되어 있습니다. Cloudflare Workers KV는 전 세계적으로 복제되는 키-값 저장소(key-value store)입니다. 여기에 데이터를 쓰면 몇 초 내에 모든 Cloudflare 데이터 센터로 값이 전파되며, 읽기 작업은 로컬에서 이루어집니다. 정확한 캐시 항목들을 Workers KV에 배치하는 작업(Upstash를 신뢰할 수 있는 원천(source of truth)으로 사용하고 cron을 통해 복제하는 방식)은 대략 이틀 정도의 작업 분량입니다. 이것이 v1.6.5 버전이 될 것입니다. 이 버전이 배포된 후에는 캐시 히트가 어디서든 실제로 약 30~50ms 내에 이루어질 것이며, 이것이 바로 이번 릴리스의 이상적인 모습입니다.
제가 이 모든 것을 말씀드리는 이유는, 지금 당장 서사(narrative)를 조금 잃더라도 고객이 v1.6을 벤치마킹한 후 속았다고 느끼게 하고 싶지 않기 때문입니다. 아키텍처(architecture)는 올바르게 설계되었으며, 단지 최적화가 아직 완료되지 않았을 뿐입니다.
중요했던 몇 가지 선택 사항
빌드 과정에서 다시 하더라도 똑같이 선택할 몇 가지 결정 사항들과, 취소해야만 했던 한 가지 결정 사항에 대해 말씀드리겠습니다.
Workers Routes 대신 Workers Custom Domain 사용. 호스트 이름 앞에 Worker를 배치하는 표준 패턴은 "라우트 (route)"를 연결하고 Cloudflare DNS에서 주황색 구름을 켜는 것입니다. 저희도 처음에는 그 방식을 시도했습니다. 하지만 결과는 처참했습니다. api.ssimplifi.com은 최상위 도메인(apex)인 ssimplifi.com 아래에서 두 단계 깊이의 서브도메인인데, Cloudflare의 무료 Universal SSL은 두 단계 깊이의 서브도메인을 지원하지 않습니다. 주황색 구름을 켜는 순간 모든 요청에 대해 TLS 핸드셰이크 (TLS handshake)가 실패했고, 잠시 동안 고객들이 API에 전혀 접속할 수 없는 상황이 발생했습니다. 저희는 몇 분 내에 롤백(rollback)을 진행한 후, Workers Custom Domain(중첩 깊이에 상관없이 호스트 이름별 인증서를 자동으로 프로비저닝함)으로 전환했으며, 그 이후로는 아무 문제 없이 작동하고 있습니다. 유료 티어는 필요하지 않았습니다. 만약 Cloudflare 무료 플랜을 사용하면서 깊은 단계의 서브도메인 앞에 Worker를 배치하려 한다면, Custom Domain이 정답입니다. Routes 방식은 그런 경우에 있어 실수하기 쉬운(footgun) 방식입니다.
Python과 TypeScript 간의 바이트 단위로 동일한 지문 (Byte-identical fingerprints). 이것은 v1.6에서 반드시 완벽해야만 했던 단 하나의 요소였습니다. 캐시 지문 (cache fingerprint)은 요청을 JSON으로 직렬화한 표현에 대한 SHA-256 해시입니다. Python의 json.dumps는 JavaScript의 JSON.stringify와 세 가지 작지만 치명적인 차이점이 있습니다. 첫째, Python은 쉼표와 콜론 뒤에 공백을 사용합니다. 둘째, Python의 ensure_ascii=True는 비-ASCII 문자를 \uXXXX로 이스케이프(escape)합니다. 셋째
정책 강제(policy enforcement)를 에지(edge)에 두지 마세요. 유혹적인 생각입니다. 고객이 차단 목록(deny-list) 규칙을 추가하면 에지에서 이를 강제함으로써 Mumbai까지의 왕복(round-trip)을 피할 수 있으니까요. 문제는 다음과 같습니다. v1.4 정책은 Supabase에 존재하며, 이를 처리하려면 매 요청마다 느린 Supabase fetch를 수행하거나(이는 목적에 어긋납니다), "opus 차단"을 전역 에지 캐시 무효화(edge-cache invalidation)로 변환하는 복잡한 전파(propagation) 체계가 필요합니다. 더 쉽고 안전한 방법은 정책을 Mumbai에 그대로 두는 것입니다. 최악의 경우, 정책에 의해 차단되었어야 할 요청이 403 오류를 받기 전까지 Mumbai까지 도달하는 상황입니다. 이는 차단 경로(deny path)에서 약 150ms의 추가적인 컴퓨팅 자원 낭비를 초래하지만, 차단된 트래픽을 한 시간 동안 조용히 허용해 버리는 캐시 일관성(cache-coherence) 버그를 방지하는 비용치고는 충분히 가치가 있습니다.
ctx.waitUntil을 통한 통계(stats) 쓰기. 에지가 캐시 히트(cache hit)를 제공할 때, 워커(worker)는 Mumbai가 쓰는 것과 동일한 Redis 통계 해시(stats hash)를 업데이트합니다. 우리는 이를 Cloudflare의 ctx.waitUntil() 메커니즘을 통해 수행하며, 이는 응답이 고객에게 반환된 후에 작업이 수행되도록 예약합니다. 고객은 통계 쓰기를 위해 기다릴 필요가 없으며, 대시보드 카운터는 여전히 올라갑니다. 작은 부분이지만, 에지 히트 시의 지연 시간(latency)이 통계를 추적하지 않을 때와 동일하게 유지되도록 만드는 중요한 디테일입니다.
변경 사항
만약 당신이 뱅갈로르(Bangalore)에서 이 글을 읽고 있는 인도 고객이라면: 거의 차이를 느끼지 못할 것입니다. 에지를 거치는 Mumbai-to-Mumbai 경로는 직접 연결과 홉 수(hop count)가 동일하기 때문입니다. 저희는 당신의 병목 현상(bottleneck)이 아닙니다. 당신의 병목 현상은 AI 제공업체들이며, v1.5 작업은 그 문제를 훨씬 더 견딜 만하게 만들었습니다.
만약 당신이 해외 고객이라면: 오늘부터 잘못된 키(bad-key)로 인한 거부 요청이 더 빠르게 반환되는 것을 볼 수 있으며, 캐시 히트가 대략 200ms 더 빠르게 이루어지는 것을 확인할 수 있을 것입니다. 둘 다 실질적인 승리입니다. 하지만 둘 다 핵심적인 수치는 아닙니다. 핵심적인 수치는 v1.6.5에서 공개될 예정이며, 제대로 된 벤치마크(benchmark)를 수행하기 전까지는 그때까지 기다려 보시기를 권장합니다.
만약 당신이 Prism 도입을 고려 중인 SRE(Site Reliability Engineer)라면: 이제 아키텍처(architectural) 측면의 해답이 당신이 원하던 모습이 되었습니다. 사용자에게는 가까운 프론트 도어(front door); 하나의 리전(region)에 위치한 컨트롤 플레인(control plane); 그리고 아직은 아니지만 전 세계적으로 복제(replicated)되기를 원하는 캐시(cache). 형태는 올바릅니다. 옵티마이저(optimizer)에 대한 추가적인 검토가 필요할 뿐입니다.
이 작업이 존재하는 근본적인 이유는 "우리는 AI를 더 저렴하게 만든다", "우리는 AI를 관찰 가능하게(observable) 만든다", 그리고 "우리는 AI를 생존 가능하게(survivable) 만든다"라는 이전의 세 가지 핵심 기둥(pillars)이 모두 고객이 가치 있게 여기는 기능들이기 때문입니다. 만약 샌프란시스코의 고객이 0.5초를 기다리지 않고 프론트 도어에 도달할 수 없다면, 이 기능들은 결코 충분하지 않았을 것입니다. v1.6은 그 논의의 물꼬를 트며, v1.6.5는 그 논의에서 승리할 것입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기