국가 간 API 통신 이슈 해결하기

서비스를 오픈하고 얼마 지나지 않아 고객에게 화면이 너무 늦게 뜬다는 CS가 들어왔습니다. 서버 실행 시간과 DB slow query를 살펴봤지만, 당시 확인한 범위에서는 지연을 설명할 만한 단서를 찾지 못했어요.

이미지는 서울 리전의 스토리지에서, API는 상해 리전의 서버에서 제공하고 있었습니다. 이미지를 CDN으로 옮기고 HTTP/2를 적용했지만 CS는 계속됐어요. 이미지 전송을 개선한 것과 사용자가 기다리는 API 응답을 개선한 것은 별개의 일이었습니다.

2023년 운영 경험을 바탕으로 정리한 글입니다. 아래 측정 절차는 같은 문제를 다시 만났을 때 사용할 보완안이며, 당시 수행한 실험이나 확보하지 않은 성능 수치를 새로 덧붙인 것은 아닙니다.

서버가 빠른데 화면은 왜 늦을까

애플리케이션 로그의 실행 시간은 요청이 서버에 들어와 처리되는 구간을 주로 보여줍니다. 사용자 쪽 DNS 조회, 연결 수립, TLS 협상, 전송 대기와 응답 다운로드는 별도로 볼 필요가 있어요. 로그에 찍힌 짧은 처리 시간만으로 서버 앞단의 큐나 네트워크까지 정상이라고 결론 내릴 수는 없습니다.

HTTP/1.1에서는 브라우저의 출처별 동시 연결 제한 때문에 이미지 요청이 대기할 수 있습니다. HTTP/2의 다중화는 이 문제를 줄이지만, 먼 경로의 왕복 지연이나 패킷 손실을 없애주지는 않아요. CDN 도입 뒤에도 같은 API가 느리다면 API 요청을 따로 추적해야 합니다. HTTP/1.x 연결 관리, HTTP/2 명세

국가보다 실제 요청 경로를 봐야 했다

API 서버는 상해에 있었고, 지연을 호소한 사용자는 광저우에 있었습니다. 당시 거리 검색 결과를 보고 같은 중국 안에서도 지역 간 이동 거리가 크다는 점을 의식하게 됐어요.

2023-10-07-image1 당시 검색한 상해–광저우 육로 이동 거리: 약 1,467km

2023-10-07-image2 당시 검색한 상해–서울 지리적 중심 간 거리: 866km

두 값은 측정 기준이 달라 비교 자료로 쓰기 어렵습니다. 육로 거리도, 도시 사이 직선거리도 패킷의 실제 경로가 아니에요. 통신사 간 연결, 경로 우회, 혼잡, 손실, 연결 재사용 여부에 따라 같은 두 도시 사이에서도 응답 시간은 달라집니다.

그래서 이 사례에서 검증할 가설은 “중국이 넓어서 느리다”보다 “광저우 사용자에서 상해 서버로 가는 공용 인터넷 구간이 병목일 수 있다”가 더 정확합니다.

지역 프록시와 CCN으로 경로 바꾸기

광저우, 베이징, 충칭, 상해에 프록시 서버를 두고, 지역별 요청을 해당 프록시로 보낸 뒤 Tencent Cloud CCN을 통해 상해 API 서버로 전달하는 구성을 사용했습니다.

2023-10-07-image3 프록시 배치를 위한 주요 지역 구분

2023-10-07-image4 지역 프록시와 CCN으로 API 서버에 연결하는 구조

구간변경 전변경 후
사용자 진입점상해 API 서버지역 프록시
지역 간 전송공용 인터넷 경로CCN에 연결된 사설 네트워크
애플리케이션 처리상해 API 서버상해 API 서버

CCN은 리전의 VPC 등을 연결하는 네트워크 서비스입니다. 브라우저가 CCN에 직접 접속하는 것은 아니에요. 사용자에서 지역 프록시까지는 여전히 공용 인터넷을 지나며, 지역 프록시를 선택하는 DNS·트래픽 분산 설정도 따로 필요합니다.

따라서 CCN 연결만 만들고 프록시가 상해의 공인 IP를 계속 호출한다면 의도한 사설 경로를 타지 않을 수 있습니다. VPC CIDR 중복, 라우트, 보안 그룹, 프록시의 upstream 사설 주소를 함께 확인해야 해요.

다시 검증한다면 이렇게 측정하겠다

먼저 지연이 발생한 지역·통신사에서 같은 API를 직접 경로와 프록시 경로로 반복 호출합니다. 같은 요청 본문·인증·응답 크기로 비교하고, 시간대와 캐시 상태를 기록해요. 서울의 개발자 PC에서만 측정하면 광저우 사용자의 문제를 놓칠 수 있습니다.

연결 구간을 살펴볼 때는 다음처럼 curl의 시간 정보를 활용할 수 있습니다. 읽기 전용 API 주소로 바꾸고 인증이 필요하면 해당 서비스 방식에 맞춰 추가합니다.

1
2
3
4
curl --silent --show-error --output /dev/null \
  --connect-timeout 5 --max-time 30 \
  --write-out 'code=%{http_code} dns=%{time_namelookup} connect=%{time_connect} tls=%{time_appconnect} first_byte=%{time_starttransfer} total=%{time_total}\n' \
  'https://api.example.com/read-only-endpoint'

각 시간은 요청 시작부터의 누적 시간(초)입니다. 단순한 HTTPS 연결에서 connect - dns는 TCP 연결 구간, tls - connect는 TLS 구간을 살펴보는 데 도움이 됩니다. first_byte에는 연결과 요청 전송, 서버 대기·처리 등이 섞여 있으므로 그것을 서버 처리 시간으로 읽으면 안 됩니다. 이 명령은 매번 새 프로세스에서 실행되므로 브라우저의 연결 재사용도 별도로 확인해야 해요. curl 시간 변수

관찰다음 확인
서버 처리 시간도 함께 증가애플리케이션·DB·서버 큐
서버는 비슷한데 연결·첫 바이트가 늦음지역별 경로, 손실, 프록시 대기
첫 바이트는 빠르고 완료만 늦음응답 크기와 다운로드 속도
프록시 경로가 특정 통신사에서만 개선지역 평균보다 통신사·시간대별 분포

한두 번 빠른 결과보다 p50·p95와 오류율·타임아웃 비율을 함께 비교하는 편이 좋습니다. 프록시 로그와 API 로그를 같은 요청 ID로 연결하면, 경로 개선인지 서버 부하 변화인지 구분하기 쉬워요. 중간 라우터의 ICMP 응답 제한만으로 종단 간 패킷 손실을 단정하지 않는 것도 필요합니다.

프록시가 늘어나며 생기는 비용

프록시를 추가하면 지역 서버 운영비와 네트워크 요금뿐 아니라 장애 지점도 늘어납니다. 지역 프록시가 실패할 때 다른 정상 진입점으로 전환할 수 있어야 하고, 타임아웃 설정도 서비스 응답 시간에 맞아야 해요. 특히 결제·문서 발행 같은 쓰기 요청은 프록시의 무조건 재시도로 중복 처리되지 않도록 멱등성을 확인해야 합니다.

이 사례에서 지역 프록시와 CCN은 경로를 바꾸기 위한 유효한 접근이었습니다. 다만 당시 지역별 전후 측정값을 이 글에 제시할 수 없어 개선률이나 물리적 거리의 기여도를 숫자로 주장하지는 않겠습니다. 다음에는 네트워크 구성을 바꾸기 전에 같은 조건의 비교 데이터를 먼저 남기고 싶어요.

CDN도 제품과 설정에 따라 동적 요청을 가속할 수 있으므로 “이미지는 CDN, API는 CCN”을 일반 규칙으로 삼을 필요는 없습니다. 먼저 사용자 요청의 어느 구간이 느린지 확인하고, 그 구간을 바꿀 수 있는 가장 작은 조치를 선택하는 것이 이 경험에서 남은 교훈입니다.

참고 자료