Big Project · AI's EYE 39
Cloudflare 공개 시연 배포 구조 분석
공개 데모 서버라면 Vision 추론까지 한 VM에서 상시 실행해야 완성된 배포처럼 보인다. 왜 CPU stack과 GPU 작업을 떼어놓았는지 궁금하다.
근거 · 프로젝트 문서 · 코드 · 테스트
1장: 데모 서버의 감춰진 경계: 왜 Vision 추론은 잠시 멈춰 서는가?
솔라는 모니터에 띄워진 짧은 기술 문서를 읽고 있었다. 최근에 공개된 한 데모 시스템의 배포 구조에 대한 설명이었다. 흥미로운 기술들이 많이 사용되어 집중해서 읽어 내려가던 솔라의 눈길이 한 문장에서 탁, 하고 걸렸다.
GCP와 미니PC는 Dashboard·API·AICC·PostgreSQL·저장된 replay를 맡고 YOLO 분석은 별도 GPU 환경에서 필요할 때 실행한다.
솔라는 미간을 살짝 좁혔다. 문장 자체는 이해하기 어렵지 않았다. 상시 가동되어야 하는 서비스들은 클라우드와 사무실의 작은 PC에서 돌아가고, 무거운 AI 분석 작업은 다른 곳에서, 그것도 필요할 때만 처리된다는 뜻이었다. 하지만 바로 그 점이 솔라의 머릿속에 있던 ‘잘 만든 공개 데모’의 그림과 맞지 않았다.
“언니, 이것 좀 봐. 뭔가 이상하지 않아?”
솔라가 부르자, 옆에서 자신의 작업을 하던 루나가 고개를 돌렸다.
“어떤 게?”
“이 데모 시스템 말이야. 사용자가 쓰는 대시보드나 API 같은 건 항상 켜져 있는데, 핵심 기능인 비전 분석은 요청이 올 때만 따로 실행한대. 공개 데모라면 모든 기능이 착 달라붙어서 하나의 완성된 제품처럼 항상 준비되어 있어야 하는 거 아니야? 이건 마치 레스토랑은 열어뒀는데, 주방장은 손님이 와야 출근하는 느낌이랄까. 뭔가 덜 만들어진 것 같아.”
솔라의 말에는 ‘제대로 된 배포라면 모든 것이 한 VM 안에서 빈틈없이 상시 가동되어야 한다’는 믿음이 묻어 있었다. 분리된 구성, 특히 ‘필요할 때만’이라는 조건은 어딘가 임시방편처럼 느껴졌다.
루나는 솔라가 가리키는 문장을 잠시 들여다보더니, 대답 대신 옆에 놓인 화이트보드를 끌어왔다. 그리고는 아무 말 없이 커다란 상자 세 개를 그렸다. 첫 번째 상자에는 ‘CPU 서버 (클라우드/미니PC)’, 두 번째 상자에는 ‘GPU 환경 (별도)’, 마지막 상자에는 ‘사용자’라고 적었다.
“그 문장에 나온 서비스들을 제자리에 한번 배치해 볼까? 각자 어느 집에 사는지, 우리가 직접 이사시키는 거야.”
솔라는 루나의 의도를 알아채고 펜을 들었다. 먼저 망설임 없이 ‘Dashboard’, ‘API’, ‘AICC’, ‘PostgreSQL’, ‘저장된 replay’를 첫 번째 ‘CPU 서버’ 상자 안으로 옮겨 적었다. 여기까지는 쉬웠다. 항상 켜져 있어야 하는 서비스들이 모여 사는 집이었다.
문제는 ‘YOLO 분석’이었다. 솔라는 잠시 펜을 허공에 띄운 채 망설였다. 그리고는 두 번째 ‘GPU 환경’ 상자 안에 ‘YOLO 분석’이라고 적어 넣었다. 이것만으로는 부족하다고 느꼈는지, 그 옆에 작은 글씨로 괄호를 치고 ‘필요할 때만’이라고 덧붙였다.
화이트보드에는 방금 읽은 문장이 하나의 지도로 펼쳐져 있었다. 왼쪽의 CPU 서버 상자는 여러 서비스들이 북적이며 사는 아파트 같았고, 오른쪽의 GPU 환경 상자는 외따로 떨어진 별채처럼 보였다.
솔라는 자신이 그린 지도를 물끄러미 바라보았다. 머릿속에서 맴돌던 ‘미완성’이라는 단어 대신 ‘분리’라는 단어가 선명하게 떠올랐다. 그냥 합치지 않은 것이 아니었다. 이것은 명백히 의도된 분리였다. 항상 불이 켜져 있는 본채와, 주인이 부를 때만 문을 여는 별채.
“이렇게 그려놓고 보니까… 정말로 두 개의 다른 세상이네. 그냥 어쩌다 보니 떨어진 게 아니라, 처음부터 각자 다른 역할을 하도록 설계된 거구나.”
솔라의 목소리에는 처음의 의아함 대신 새로운 궁금증이 담겨 있었다. 시스템이 분리되어 있다는 사실 자체를 받아들이자, 비로소 진짜 질문이 보이기 시작했다.
“알겠어. 일부러 분리했다는 건 이제 알겠어. 그런데 왜? 왜 굳이 이렇게 나눠야만 했을까? 이 두 집 사이를 요청은 어떻게 오고 가는 거지? 하나로 합쳐서 관리하는 게 훨씬 간단하지 않았을까?”
2장: 요청 흐름 해부: CPU, GPU, 그리고 대용량 데이터는 어디로 가는가?
솔라의 질문이 허공에 잠시 머물렀다. 루나는 곧바로 대답하는 대신, 솔라가 그린 화이트보드의 ‘서비스 분리 지도’ 옆 빈 공간을 가리켰다. 거기에는 여전히 ‘사용자’라고 적힌 상자가 덩그러니 놓여 있었다.
“그 질문에 대한 답은, 저 ‘사용자’ 상자에서 출발하는 화살표를 그려보면 알 수 있어. 그런데 화살표가 하나가 아니야. 서로 다른 성격의 요청 세 가지가 있다고 생각해 보자.”
루나는 마커를 들어 ‘사용자’ 상자 옆에 세 가지 종류의 ‘요청’을 나열했다.
- 일반 방문객: 데모 사이트의 대시보드를 둘러보는 평범한 웹 트래픽.
- 분석 의뢰인: 특정 영상에 대한 Vision 분석을 요청하는 무거운 작업.
- 대형 화물 운송: 분석을 위해 100MB가 넘는 큰 영상 파일을 업로드하는 경우.
“자, 이제 이 세 요청이 각각 어떤 길을 통해 우리가 그린 저 두 개의 집, ‘CPU 서버’와 ‘GPU 환경’에 도착하는지 길을 내어주는 거야. 솔라 네가 교통 관제사가 되는 거지.”
솔라는 루나의 의도를 파악하고 다시 펜을 들었다. 첫 번째 요청은 간단했다. ‘일반 방문객’의 요청은 당연히 시스템의 정문으로 가야 했다.
“공개된 주소로 들어오는 일반적인 트래픽이니까, Cloudflare Tunnel을 통해서 상시 대기 중인 CPU 서버로 가면 되겠네.”
솔라는 ‘사용자’ 상자에서 ‘CPU 서버’ 상자로 이어지는 화죽 그었다. 화살표 위에는 ‘공개 요청 (Dashboard, API)’이라고 썼다. 시스템의 가장 기본적인 통신 경로였다. 모든 게 이 길을 통할 것만 같았다.
“좋아. 그럼 두 번째, Vision 분석 요청은?”
“음… 분석 작업 자체는 GPU 환경에서 일어나지만, 요청 접수는 CPU 서버의 API가 받지 않을까? 사용자가 ‘분석 시작’ 버튼을 누르면, 그 신호가 먼저 CPU 서버에 가고, 서버가 다시 GPU 환경에 ‘일해!’ 하고 명령하는 거지.”
솔라는 ‘CPU 서버’ 상자에서 ‘GPU 환경’ 상자로 이어지는 점선을 그렸다. 필요할 때만 연결되는 임시 통로라는 느낌을 살리고 싶었다.
이제 마지막, ‘대형 화물 운송’이 남았다. 솔라는 당연하다는 듯 ‘사용자’ 상자에서 첫 번째 화살표와 같은 경로로, CPU 서버를 향해 또 하나의 굵은 화살표를 그리려 했다. 모든 요청은 정문으로 들어오는 것이 당연해 보였다.
그때, 루나가 조용히 솔라의 펜 끝을 가리켰다.
“잠깐. 그런데 그 정문은 너무 큰 짐을 들고는 들어갈 수가 없어. Cloudflare에는 무료 플랜 기준으로 요청 하나당 크기 제한이 있거든.”
솔라의 펜이 허공에서 멈췄다. ‘모든 요청은 동일한 경로를 탈 것’이라는 솔라의 무의식적인 가정이 깨지는 순간이었다. 정문으로 못 들어간다면, 다른 길이 있다는 뜻이었다.
“그럼… 뒷문이라도 있는 거야?”
“맞아. 대용량 파일을 위한 별도의 통로, Tailscale HTTPS라는 비공개 경로를 따로 열어둔 거지. 일반 방문객은 모르는, 허가된 사용자만 이용할 수 있는 화물 전용 통로랄까.”
솔라는 그리려던 화살표를 지우고, 대신 ‘사용자’ 상자에서 CPU 서버로 향하는 새로운 길을 그렸다. 첫 번째 화살표와는 명백히 구분되는, 다른 색의 펜으로 그린 이 길 위에는 ‘대용량 업로드 (Tailscale)’라고 적었다.
화이트보드 위에는 이제 세 종류의 요청에 대한 세 갈래 길이 선명하게 그려져 있었다.
- 공개 요청: 사용자가 Cloudflare Tunnel을 통해 CPU 서버에 접속하는 길.
- GPU 추론: CPU 서버가 내부적으로 GPU 환경에 작업을 지시하는 길.
- 대용량 업로드: 사용자가 Tailscale이라는 별도 경로를 통해 CPU 서버에 파일을 전송하는 길.
솔라는 완성된 요청 흐름도를 보며 중얼거렸다.
“아… 이건 그냥 시스템을 나눈 게 아니었네. 들어오는 요청의 성격에 맞춰서 아예 다른 도로를 만들어준 거구나. 일반 승용차 길, 내부 작업자 전용 길, 그리고 대형 트럭 전용 도로처럼.”
더 이상 모든 것이 한곳에 통합되어야 한다는 생각은 들지 않았다. 각기 다른 목적을 가진 요청들이 각자에게 최적화된 경로로 움직이는 모습이 오히려 훨씬 더 정교하고 효율적인 설계로 보이기 시작했다. 요청의 종류에 따라 길을 분류하고 안내하는 것, 그것이 이 시스템의 숨겨진 역할 분담이었다.
지도를 완성하고 나자 새로운 질문이 고개를 들었다.
“이제 각 요청이 어디로, 어떻게 흘러가는지는 알겠어. 그런데 왜? 왜 이렇게까지 길을 나눠야 했을까? 그냥 정문(Cloudflare)을 유료로 써서 크게 만들고, GPU 서버도 항상 켜두면 훨씬 간단했을 텐데. 굳이 이렇게 복잡하게 여러 길을 만든 이유가 뭘까?”
3장: 비용, 지연 시간, 하드웨어: 분리 배포의 세 가지 결정 요소
솔라의 마지막 질문이 화이트보드의 복잡한 화살표들 사이에 내려앉았다. “왜 이렇게 복잡하게 여러 길을 만들었을까?” 그녀의 질문은 단순함에 대한 갈망, 즉 더 많은 비용을 지불해서라도 하나의 통합된 시스템을 만들 수 있지 않았냐는 아쉬움에서 비롯된 것이었다.
루나는 솔라가 그린 세 갈래의 요청 흐름도를 잠시 바라보았다. 그리고는 그 옆에 작은 표를 하나 그리기 시작했다. 표의 머리에는 ‘통합’, ‘분리’라는 두 개의 열을 만들고, 행에는 ‘비용’, ‘지연 시간’, ‘하드웨어’라고 적었다. 아직 빈칸투성이인 작은 격자였다.
“좋은 질문이야. ‘그냥 돈을 더 쓰면 되지’ 라는 생각은 시스템을 설계할 때 가장 먼저, 그리고 가장 자주 만나는 유혹이거든.”
루나는 표의 빈칸을 가리키며 말했다.
“솔라 네가 방금 제안한 ‘단순한’ 시스템, 즉 비싼 GPU 서버를 항상 켜두고 Cloudflare 유료 플랜으로 대용량 업로드까지 처리하는 모델을 ‘통합’ 시나리오라고 해보자. 그리고 지금 우리가 보고 있는 이 데모 시스템을 ‘분리’ 시나리오라고 하고. 이 표의 빈칸을 한번 채워볼까? 각 시나리오의 장단점을 직접 비교해 보는 거야.”
솔라는 루나의 의도를 금세 알아차렸다. 기술의 우아함만을 따지는 것이 아니라, 현실의 제약 조건 안에서 최선을 찾는 과정으로 그녀를 이끌고 있었다. 솔라는 펜을 들고 ‘통합’ 시나리오의 빈칸부터 채워나가기 시작했다.
“일단… GPU 서버를 24시간 내내 클라우드에서 돌리면… 비용은 확실히 ‘높음’이겠네. 대용량 트래픽을 위한 네트워크 비용도 추가될 거고.”
솔라는 ‘통합’ 열의 ‘비용’ 칸에 ‘매우 높음’이라고 적었다.
“대신 지연 시간은 낮아지겠지. 언제 요청이 와도 GPU가 즉시 응답할 테니까. ‘매우 낮음’.”
“하드웨어는… 모든 걸 처리할 수 있는 고사양의 단일 시스템이 필요할 테니, ‘고성능 통합 장비’라고 써야겠다.”
이제 반대쪽 ‘분리’ 시나리오를 채울 차례였다. 솔라는 잠시 고민했다. 이미 답은 눈앞의 시스템 구성도에 나와 있었다.
“비용은 확실히 낮지. GPU는 필요할 때만 쓰고, 무료 플랜의 제약을 피하기 위해 다른 길을 썼으니까. ‘낮음’.” “하지만 지연 시간은 손해를 보네. 특히 첫 GPU 요청은 서버가 켜지는 시간 때문에 느려질 수밖에 없으니까. ‘일부는 높음’이라고 해야겠어. 대시보드 같은 건 빠르지만.” “하드웨어는… 역할별로 최적화된 여러 장비가 필요하네. ‘역할별 분산 장비’.”
솔라는 자신이 채운 표를 가만히 들여다보았다.
| 통합 시나리오 (가상) | 분리 시나리오 (실제) | |
|---|---|---|
| 비용 | 매우 높음 | 낮음 |
| 지연 시간 | 매우 낮음 | 일부 높음 |
| 하드웨어 | 고성능 통합 장비 | 역할별 분산 장비 |
명확했다. ‘통합’ 시나리오는 이상적이지만 비쌌고, ‘분리’ 시나리오는 약간의 불편함을 감수하는 대신 현실적인 비용으로 시스템을 운영하게 해 주었다. 배포 결정은 단순히 기술적 완성도를 좇는 행위가 아니었다. 그것은 마치 예산을 고려해 쇼핑 목록의 우선순위를 정하는 일처럼, 주어진 제약 조건 속에서 최적의 균형점을 찾는 과정이었다.
솔라는 이 작은 표가 강력한 분석 도구가 될 수 있음을 깨달았다. 이것은 단순한 비교표가 아니라, 새로운 서비스를 설계하거나 기존 시스템을 평가할 때 어떤 것을 얻고 어떤 것을 포기할지 결정하게 해주는 ‘배포 결정 요인 분석틀’이었다.
“아…”
낮은 탄성이 솔라의 입에서 흘러나왔다.
“이건 그냥 ‘기술’ 문제가 아니라 ‘경영’ 문제였구나. 어떤 기술이 더 좋냐가 아니라, 우리가 가진 자원으로 어떤 가치를 우선할 거냐의 문제.”
솔라는 이제 처음의 의문으로 돌아가 스스로에게 질문을 던졌다. 이 배포 구조는 지연 시간, 비용, 하드웨어 한계를 고려하여 각 서비스 역할을 어떻게 분리하고 통합했는가?
머릿속에서 답이 명료하게 정리되었다.
“응답 속도가 중요한(지연 시간) 공개 요청은 항상 켜져 있는 저비용 CPU 서버(비용/하드웨어)로 보낸다. 하지만 비싼(비용) GPU 자원은 사용 빈도가 낮으므로, 약간의 초기 지연 시간을 감수하고(지연 시간) 필요할 때만 호출한다. 그리고 플랫폼의 파일 크기 제한(하드웨어 제약)을 넘는 요청은 아예 다른 경로로 처리한다.”
솔라는 화이트보드에 그려진 ‘서비스 분리 지도’와 ‘요청 라우팅 분류기’를 손가락으로 짚으며 중얼거렸다. 처음에는 미완성처럼 보였던 이 시스템의 모습이, 이제는 현실의 제약 속에서 찾아낸 가장 현명한 해답으로 보였다. 각 서비스들이 제자리에 있는 이유, 그리고 그들 사이를 흐르는 요청들이 각기 다른 길을 택한 이유를 이제는 명확히 설명할 수 있었다. 그것은 타협의 산물이자, 영리한 설계의 증거였다.