Cloudflare를 붙였는데 한국에서 오히려 느려진 이유
한국 사용자를 대상으로 운영 중인 데모 사이트에서 응답 지연을 점검했습니다. 도메인은 Cloudflare 프록시를 거치게 해뒀고, 원본 IP와 오리진을 보호하기 위해 켜둔 상태였습니다. 그러다 사이트를 손보는 중에 "버튼이 한 박자 늦게 눌리는 것 같다"는 얘기가 나왔고, 확인해 보니 앱 코드가 아니라 이 프록시 경로가 지연의 가장 큰 덩어리였습니다.
먼저 curl로 페이지별 응답 시간을 재봤습니다.
/dashboard TTFB 1.52s
/orders TTFB 1.03s
/tickets TTFB 1.03s
/leads TTFB 0.82s대시보드가 1.5초인 건 이해가 갔습니다. DB 질의가 40번 나가는 무거운 화면이라 그러려니 했습니다. 이상한 건 가벼운 페이지들이었습니다. 질의가 몇 개 안 되는 화면도 1초 안팎이 나왔습니다. 페이지마다 편차가 있다기보다, 모든 페이지에 0.8초쯤 되는 바닥값이 깔려 있는 모양새였습니다.
처음에는 오리진 서버의 성능을 의심했습니다. 전에 콜드 스타트로 502를 겪은 적이 있어서 그쪽부터 봤는데, 서버가 힘들면 무거운 페이지일수록 더 느려져야지 이렇게 균일한 상수가 붙는 건 설명이 안 됐습니다. 이런 모양의 지연은 대개 코드가 아니라 경로에 있습니다.
cf-ray 세 글자로 끝난 확인
응답 헤더를 열어봤습니다.
$ curl -s -o /dev/null -D - https://cp.duolabs.co.kr/tickets
server: cloudflare
cf-cache-status: DYNAMIC
cf-ray: ........-LAXcf-ray 헤더 끝의 세 글자가 그 요청을 처리한 Cloudflare 엣지의 공항 코드입니다. LAX, 로스앤젤레스였습니다. 한국 사용자의 요청이 LA 엣지를 경유한 뒤 오리진으로 전달되고, 응답도 같은 경로를 거쳤습니다. 태평양을 두 번 건너면 왕복으로 수백 ms가 그냥 사라집니다. 모든 페이지에 붙어 있던 0.8초의 정체가 대충 맞아떨어졌습니다.
Cloudflare는 서울에도 엣지가 있는데 왜 LAX로 갔는지는 확인하지 못했습니다. 무료 플랜 트래픽은 가까운 PoP 대신 먼 PoP로 배정되는 경우가 있다고 알려져 있지만, 배정 기준이 공개돼 있지 않아서 어디까지나 추정입니다. 다만 cf-ray가 LAX를 가리키는 동안에는 앱을 아무리 다듬어도 이 몫은 줄지 않는다는 것만은 분명했습니다.
선택지 정리
세 가지쯤으로 정리했습니다. 프록시를 끄고 DNS만 쓰면 경로 문제는 사라지지만 오리진 서버의 원본 IP가 그대로 노출됩니다. Argo 같은 유료 라우팅을 쓰는 방법도 있습니다. 아니면 경로는 그대로 두고 앱 쪽에서 체감을 줄이는 방법이 있습니다.
저는 일단 세 번째부터 했습니다. 스켈레톤과 클릭 피드백을 정리하는 쪽인데, 이 얘기는 글이 따로 있습니다. 프록시를 어떻게 할지는 아직 결정하지 못했습니다. 운영 중인 오리진의 IP 노출은 피해야 했습니다.
같은 증상을 만나면 앱 코드를 열기 전에 curl 한 줄부터 쳐보는 게 순서인 것 같습니다. 모든 페이지에 비슷한 크기로 붙는 지연은 코드보다 경로를 먼저 의심하라는 신호이고, cf-ray 세 글자면 확인이 끝납니다.
같은 작업에서 겪은 다른 문제:
함께 읽기
- 새 서브도메인이 특정 브라우저에서만 안 열릴 때: DNS 캐시 점검법새 서브도메인을 추가한 직후 “Safari에서는 열리는데 Chrome에서는 안 열린다” 같은 일이 생길 수 있습니다. 이럴 때 바로 서버 장애나 DNS 설정 오류라고 판단하기 쉽지만, 실제 원인은 로컬 또는 브라우저 DNS 캐시인 경우가 많습니다.
- Next.js + Postgres 프로젝트 호스팅 비용 비교: 관리형 플랫폼과 VPSNext.js와 PostgreSQL로 만든 웹 애플리케이션을 운영할 때 관리형 플랫폼과 VPS 중 어느 쪽이 더 저렴한지는 월 기본요금만으로 판단하기 어렵습니다. 관리형 플랫폼은 배포와 확장을 대신 처리하고, VPS는 낮은 인프라 비용 대신 서버 운영을 직접 맡아야 합니다.
- 질문 8개를 캐시할 때 Redis를 도입하지 않은 이유문서 질의응답 기능은 검색과 답변 생성을 함께 수행하기 때문에 일반 API보다 처리 비용이 큽니다. 그런데 모바일 화면에서 제공하는 질문이 미리 준비된 여덟 개로 고정되어 있다면 캐시 대상은 매우 작아집니다.
- Docker 빌드 컨텍스트가 1.06GB까지 커진 이유Docker 빌드가 느리면 패키지 설치나 애플리케이션 컴파일부터 보게 됩니다. 저도 타입 검사 실패를 고치는 데 집중하다가 그보다 앞선 load build context 단계에서 40초가 넘게 쓰이고 있다는 사실을 뒤늦게 봤습니다. 실패 원인과는 별개였지만, 매 배포마다 반복되는 낭비였습니다.
- 로컬에서는 통과한 Next.js 타입 검사가 Docker 배포에서만 실패하는 이유Next.js 앱에서 로컬 타입 검사는 통과하는데 Docker 배포에서만 같은 오류가 다시 나오면 코드보다 빌드 이미지에 무엇이 들어갔는지 봐야 합니다. 이번 작업에서는 린트, 타입 검사, 프로덕션 빌드를 모두 통과시킨 뒤 배포했는데 원격 빌드만 실패했습니다. 같은 커밋과 같은 잠금 파일을 썼으니 처음에는 환경 차이가 …