AI API 호출 회선 추천의 핵심은 '어느 회선이 가장 빠른가'가 아니라 '어느 회선이 요청을 같은 아웃바운드로 안정적으로 보내는가'입니다. 웹 페이지는 한 번 끊기면 새로고침하면 그만이지만, 스크립트에서는 타임아웃 한 번에 전체 배치가 다시 돌아가고 중복 과금까지 생길 수 있습니다. 아래에서 고정 아웃바운드, 동시 연결, 타임아웃 재시도 세 가지를 나눠 설명하고, 마지막에 그대로 따라 할 수 있는 점검 절차를 제시합니다.

AI API 호출과 웹 페이지의 차이

같은 구독이라도 브라우저에서 열리는 서비스가 스크립트에서는 안 돌아갈 수 있습니다. 차이는 대역폭이 아니라 요청 형태와 실패 비용에 있습니다.

  • 연결 지속 시간이 다릅니다. 웹 페이지는 한 번 로딩할 때 수십 개의 짧은 요청으로 구성되고 각 요청은 금방 끝납니다. 반면 API의 스트리밍 응답은 연결 하나가 수십 초에서 수 분까지 이어지므로, 중간에 한 번만 흔들려도 출력에 그대로 드러납니다.
  • 실패 비용이 다릅니다. 웹은 실패하면 한 번 다시 시도하면 끝이지만, 배치 작업은 다시 돌려야 하고 그 과정에서 할당량과 비용이 소모되며 중복 제출로 중복 결과가 생길 수도 있습니다.
  • 아웃바운드 민감도가 다릅니다. 적지 않은 API 서비스가 '계정 + 아웃바운드 IP' 조합으로 리스크 관리를 합니다. 아웃바운드가 같은 날에도 계속 바뀌면 추가 인증이나 일시적 속도 제한이 걸리기 쉽고, 심하면 아예 거부됩니다.
  • 동시성 모델이 다릅니다. 브라우저는 탭 몇 개가 한계지만, 스크립트는 수십 개 연결을 동시에 열 수 있습니다. 부하는 클라이언트 연결 풀과 아웃바운드의 NAT 세션 테이블에 걸립니다.

이 네 가지를 함께 놓고 보면 회선 선택 순서가 분명해집니다. 먼저 아웃바운드 안정성을 확보하고, 그다음 회선 유형을 따지고, 마지막에 타임아웃 파라미터를 조정하면 됩니다.

고정 아웃바운드: 왜 가장 빠른 것보다 중요한가

고정 아웃바운드란 같은 계정의 요청이 장기간 동일한, 혹은 동일한 소규모 그룹의 아웃바운드 IP로 나가는 것을 뜻합니다. 이는 '얼마나 빠른가'가 아니라 '서버가 나를 알아보는가'를 해결하는 문제입니다.

아웃바운드가 흔들리면 생기는 일

  • 세션과 토큰이 IP에 묶여 있을 때 재접속으로 IP가 바뀌면 기존 세션이 무효화되어 다시 인증해야 합니다.
  • IP 허용 목록을 설정한 인터페이스는 아웃바운드가 바뀌는 순간 403을 반환합니다.
  • 리스크 관리 시스템이 같은 계정이 짧은 시간에 여러 지역에서 로그인한 것으로 보면 검증 수준을 높입니다.
  • 문제를 들여다볼 때 로그의 아웃바운드 IP가 매번 다르면 회선 문제인지 코드 문제인지 판단하기 어렵습니다.

공유 아웃바운드가 곧 나쁜 회선은 아니다

많은 사람이 '저녁 피크에 느려진다'를 회선 품질 탓으로 돌리지만, 실제 원인은 아웃바운드 공유인 경우가 많습니다. 같은 아웃바운드 IP에 사용자가 몰리면 대상 서비스는 그 IP의 고빈도 요청으로 보고 속도 제한을 겁니다. 판별법은 오류 유형을 보는 것입니다. 429나 연결 재설정이 뜨는데 대역폭 측정은 정상이라면 거의 아웃바운드 쪽 문제입니다.

아웃바운드가 고정인지 확인하는 방법

요청 출발 IP를 돌려주는 에코 주소를 준비해 두고, 프록시를 켠 상태에서 연속으로 요청해 매번 결과를 기록합니다. 연결을 끊었다 다시 붙인 뒤 한 번 더, 몇 시간 뒤에 또 한 번 측정합니다. 세 번의 결과가 같아야 고정이라고 볼 수 있습니다.

export ECHO_URL="에코 주소 입력"

# 연속 요청으로 아웃바운드 IP가 흔들리는지 확인
for i in 1 2 3 4 5; do
  curl -s --max-time 10 "$ECHO_URL"
  printf '\n'
  sleep 2
done
결론: 하나만 기억해야 한다면 아웃바운드 IP의 안정성을 기억하세요. 인증, 리스크 관리, 문제 해결이 모두 여기서 수렴하며, 속도는 안정성을 확보한 뒤에 최적화하면 됩니다.

동시 연결과 회선 유형의 조합

동시 연결 상한은 네 구간이 함께 결정합니다. 로컬 연결 풀, 프록시 클라이언트의 멀티플렉싱 설정, 회선 대역폭과 아웃바운드 NAT 세션 테이블, 대상 서비스의 계정 단위 속도 제한입니다. 어느 한 구간이 꽉 차도 증상은 타임아웃이나 연결 거부로 나타나므로, 한 구간만 보면 오판하기 쉽습니다.

세 가지 회선 유형의 차이

회선 유형 경로 특성 아웃바운드 특성 적합한 호출 형태 유의 사항
IEPL 전용선 통신사 전용선으로 해외에 나가며 공용 인터넷 국제 구간을 거치지 않음 아웃바운드가 비교적 고정적이고 흔들림이 적음 장시간 연결, 스트리밍 응답, 배치 작업 비용이 높고 선택 가능한 지역이 직접 연결보다 적음
중계 가까운 중계 노드에 먼저 연결한 뒤 중계 노드에서 해외로 나감 아웃바운드는 착지 노드가 결정 일상적 호출, 비용에 민감한 상황 한 홉이 늘어나며 중계 노드가 혼잡하면 전체가 느려짐
직접 연결 로컬에서 해외 노드에 바로 연결 아웃바운드가 노드에 따라 달라짐 가벼운 요청, 임시 디버깅 국제 구간이 혼잡할 때 변동이 큼

어떤 유형을 고를지는 가격이 아니라 호출 형태가 결정합니다. 스트리밍 응답과 배치 작업은 전용선을 우선하고, 임시 디버깅은 직접 연결로 충분하며, 중계는 둘을 분리하는 데 적합합니다. 디버깅은 한 회선, 운영은 다른 회선으로 나누면 됩니다.

프로토콜 계층에서 볼 두 가지

Shadowsocks, VMess, Trojan, VLESS는 TCP를 사용하고, Hysteria2, TUIC는 QUIC 기반으로 UDP를 사용합니다. API 호출에서 프로토콜 선택이 영향을 주는 지점은 두 가지입니다.

  • 시간 검증. VMess는 클라이언트와 서버의 시간 일치에 의존하므로 시스템 시간 오차가 크면 핸드셰이크가 그대로 실패합니다. 이런 실패는 회선이 막힌 것처럼 보이지만, 실제로는 시간을 한 번 동기화하면 해결됩니다.
  • UDP 사용 가능 여부. Hysteria2와 TUIC는 패킷 손실이 있는 구간에서 더 안정적이지만, 네트워크가 UDP를 제한하지 않아야 합니다. 일부 사무실 네트워크와 공용 네트워크는 UDP를 제한하므로 이때는 TCP 계열 프로토콜로 되돌아가야 합니다.
스트리밍 응답이 '모아 두었다가 한꺼번에' 돌아온다면 대개 회선 문제가 아니라 클라이언트나 중간 계층이 버퍼링을 한 것입니다. 점검할 때는 첫 바이트 도착 시간을 먼저 보고, 이후 출력이 구간별로 도착하는지 확인하세요.

타임아웃과 재시도: 클라이언트 측 최소 설정

타임아웃은 총합 하나만 정한다고 끝나지 않습니다. 요청 하나를 네 구간으로 나눠 봐야 합니다. 연결 수립, TLS 핸드셰이크, 첫 바이트 반환, 그리고 스트리밍 출력 중의 유휴 간격입니다. 네 구간을 뭉쳐서 설정하면 문제가 생겼을 때 어느 구간인지 짚을 수 없습니다.

  • 연결 타임아웃은 짧게 잡으세요. 연결이 안 되면 회선을 바꾸면 되고, 매달려 기다릴 필요는 없습니다.
  • 첫 바이트 타임아웃은 TLS와 요청 전송부터 첫 바이트 반환까지를 포괄하며, 연결 타임아웃보다 넉넉하게 잡아도 됩니다.
  • 유휴 읽기 타임아웃은 스트리밍 응답을 위한 것으로, 두 출력 구간 사이의 간격만 계산하고 전체 소요 시간은 계산하지 않습니다.
  • 총 소요 시간은 스트리밍 요청에 설정하지 마세요. 긴 답변이 자기 클라이언트에 의해 잘려 나갑니다.

재시도는 세 가지 조건을 만족해야 합니다. 요청이 재시도 가능해야 하고, 오류가 재시도 가능한 종류이어야 하며, 횟수에 상한이 있어야 합니다. 연결 실패, 읽기 타임아웃, 429, 5xx는 재시도할 수 있습니다. 400, 401, 403 같은 파라미터·인증 오류는 몇 번을 재시도해도 결과가 같고 문제만 키웁니다.

import random
import time

import httpx

API_URL = "https://api.example.com/v1/chat"

# 네 구간 타임아웃을 따로 설정: 연결 5s, 유휴 읽기 60s
timeout = httpx.Timeout(connect=5.0, read=60.0, write=10.0, pool=5.0)
# 연결 풀 상한이 로컬 동시성 상한을 결정
limits = httpx.Limits(max_connections=16, max_keepalive_connections=8)

with httpx.Client(timeout=timeout, limits=limits) as client:
    for attempt in range(4):
        try:
            with client.stream("POST", API_URL, json={"stream": True}) as resp:
                if resp.status_code in (429, 500, 502, 503, 504):
                    raise httpx.HTTPStatusError(
                        "retryable", request=resp.request, response=resp
                    )
                resp.raise_for_status()
                for line in resp.iter_lines():
                    handle(line)  # 한 줄씩 처리하고 리스트에 모아 두지 말 것
            break
        except (httpx.ConnectError, httpx.ReadTimeout, httpx.HTTPStatusError):
            if attempt == 3:
                raise
            # 지수 백오프에 지터를 더해 여러 작업이 동시에 재시도하지 않도록
            time.sleep(min(2 ** attempt, 8) + random.uniform(0, 0.5))
스트리밍 응답에서 이미 내용을 출력한 뒤 재시도하면 중복 출력과 중복 과금이 발생합니다. 올바른 방법은 출력된 위치를 기록하거나, 재시도 판단을 요청 전송 이전에 두는 것입니다.

DNS와 분할 라우팅 규칙의 조합

DNS 누출이란 도메인을 로컬 DNS로 해석해 '나에게 가장 가까운' 노드를 받는 것이지, '아웃바운드에 가장 가까운' 노드를 받는 것이 아닙니다. 대부분의 API는 서버가 단일 진입점을 쓰기 때문에 영향이 제한적입니다. 하지만 지역별로 다른 결과를 돌려주는 서비스라면 해석 결과가 요청이 어느 지역으로 가는지를 그대로 결정하고, 인증까지 실패할 수 있습니다.

전역보다 분할 라우팅이 편하다

API 도메인을 특정 회선 그룹으로 고정하고 나머지 트래픽을 기본 그룹으로 보내면 세 가지 이점이 있습니다. 아웃바운드가 안정되고, 디버깅할 때 따로 전환할 수 있으며, 한 회선에 문제가 생겨도 다른 작업에 영향이 없습니다. 규칙은 IP보다 도메인 접미사 기준으로 쓰는 편이 안정적입니다. API 진입 IP는 바뀌지만 도메인은 바뀌지 않기 때문입니다.

점검 순서

  1. 프록시를 켠 상태에서 대상 도메인을 해석하고 결과를 기록합니다.
  2. 프록시를 끄고 다시 해석해 두 결과가 같은 지역에 속하는지 비교합니다.
  3. 해석 결과만 보지 말고 에코 주소로 최종 아웃바운드 IP를 확인합니다.
  4. 해석 결과와 아웃바운드 지역이 어긋나면 클라이언트의 DNS 설정과 분할 라우팅 규칙 우선순위를 확인합니다.

회선 선택 시 자체 점검표

위 내용을 하나의 점검표로 압축했습니다. 회선을 고를 때 항목별로 대조하면 파라미터 표를 보는 것보다 유용합니다. 아래 네 가지 숫자는 구독 측에서 바로 확인할 수 있는 조건이며, 속도 측정 결과가 아닙니다.

  • 120+ 국가·지역별 아웃바운드 선택
  • 150+ 회선, 도메인별 분할 라우팅 지원
  • 무제한 기기 수, 병목은 아웃바운드 측
  • 7일 사유 없는 환불, 관찰 한 주기에 충분
  • ✅ 아웃바운드 IP가 고정인지, 연결을 끊었다 다시 붙여도 그대로인지.
  • ✅ 아웃바운드 지역을 지정할 수 있는지, 전환할 회선이 여러 개인지.
  • ✅ 장시간 연결과 스트리밍 응답을 지원하는지, 클라이언트가 버퍼링을 하지 않는지.
  • ✅ 단일 계정의 동시 연결에 제한이 있는지, 제한이 로컬에 있는지 아웃바운드 측에 있는지.
  • ✅ 도메인별 분할 라우팅을 지원하는지, 규칙을 따로 조정할 수 있는지.
  • ✅ 과금 방식이 호출량과 맞는지: 월 결제는 개통일 기준으로 리셋되고, 트래픽 패키지는 소진될 때까지 유효하며 만료되지 않습니다.
  • ❌ 표기 대역폭만 보고 아웃바운드 안정성은 보지 않는 것.
  • ❌ 디버깅, 운영, 배치 작업을 같은 아웃바운드에 몰아넣는 것.
  • ❌ 요청 한 번의 결과로 회선의 좋고 나쁨을 판단하는 것.

점검 절차: 아웃바운드 IP에서 동시 연결까지

문제가 생겼을 때 파라미터부터 바꾸지 말고 아래 순서를 따르세요. 각 단계마다 판단 근거가 분명합니다.

  1. 프록시가 적용됐는지 확인. 에코 주소로 요청해 아웃바운드 IP가 목표 지역인지 봅니다. IP가 그대로라면 뒤는 볼 필요도 없습니다.
  2. DNS 확인. 프록시 안과 밖의 해석 결과를 비교해 누출이 있는지 판단합니다.
  3. 시스템 시간 확인. VMess 등 프로토콜은 시간 검증에 의존하므로 오차가 크면 핸드셰이크가 실패합니다.
  4. TLS 확인. 인증서 오류, SNI 불일치, 중간 장비 차단은 모두 연결 재설정으로 나타납니다.
  5. 스트리밍 출력 확인. 첫 바이트 시간과 줄 단위 도착 여부를 보고 버퍼링이 있는지 판단합니다.
  6. 동시 연결 확인. 낮은 동시성에서 시작해 서서히 올리면서 오류 유형 분포를 기록하세요. 평균값만 보면 안 됩니다.
  7. 회선 전환 확인. 다른 회선에서 재현해 보세요. 재현되면 클라이언트나 코드 문제, 재현되지 않으면 회선 문제입니다.
순서가 중요합니다. 아웃바운드 IP는 요청이 올바르게 식별되는지를, DNS는 요청이 어느 지역으로 가는지를, 타임아웃과 재시도는 실패 이후에 문제가 커지는지를 결정합니다. 이 순서로 점검하면 회선만 계속 바꾸는 것보다 시간이 절약됩니다.

일주일 관찰 기록과 결론

요청 한 번의 결과로는 아무것도 알 수 없습니다. 해외 회선은 시간대에 따라 성능이 달라지며, 평일 낮, 저녁 피크, 새벽은 서로 다른 상태입니다. 일주일 동안 같은 요청 묶음을 기록하고, 매번 다섯 개 필드를 남기세요.

  • 시간과 회선 이름
  • 아웃바운드 IP
  • 첫 바이트 시간
  • HTTP 상태 코드 또는 오류 유형
  • 재시도 발생 여부

일주일 뒤에 보는 것은 평균이 아니라 분포입니다. 오류가 어느 시간대, 어느 회선, 어떤 유형에 몰려 있는지 봅니다. 429가 저녁 피크에 몰린다면 아웃바운드 공유, 연결 타임아웃이 특정 회선에 몰린다면 그 회선의 진입 구간 혼잡, TLS 오류가 산발적으로 나타난다면 클라이언트와 중간 장비를 먼저 확인하세요.

결론: AI API 호출의 회선 선택 순서는 고정 아웃바운드가 먼저, 회선 유형이 다음, 타임아웃과 재시도가 마지막입니다. 아웃바운드가 안정된 뒤에야 동시 연결과 타임아웃을 튜닝할 의미가 생깁니다. 아웃바운드가 불안정하면 어떤 파라미터를 조정해도 실패를 미룰 뿐입니다.