NOONOO TRADING무료 채팅방 입장

API 요청 한도(레이트리밋)와 429 에러 — 봇이 조용히 멈추는 진짜 이유

레이트리밋(rate limit)은 거래소가 한 사용자에게 허용하는 단위 시간당 요청량입니다. 넘기면 응답 대신 429(Too Many Requests) 같은 오류가 돌아옵니다. 문제는 이게 조용히 터진다는 점입니다. 봇은 죽지 않고 계속 돌지만, 시세는 낡은 값으로 굳고 손절 주문은 전송에 실패합니다. 전략을 아무리 손봐도 원인이 안 잡히는 장애의 상당수가 여기 있습니다.

한도는 세 종류가 따로 돈다

거래소마다 이름과 숫자는 다르지만, 구조는 대체로 같습니다. 하나만 신경 쓰다가 다른 쪽에서 막히는 경우가 많습니다.

한도의 세 갈래

① 횟수 한도
단위 시간당 요청 개수 (예: 2초당 20회)
엔드포인트별로 따로 계산되는 경우가 많음

② 가중치(weight) 한도
요청마다 비용 점수가 다름
가벼운 시세 조회 1점 · 전체 호가창 50점 등
분당 총점 상한을 공유

③ 주문 전용 한도
주문·취소는 조회와 별개 카운터
예: 초당 10건 · 10초당 100건

→ 조회 한도에 여유가 있어도 주문 한도는 따로 막힌다

실제 상한값은 거래소·엔드포인트·계정 등급마다 다르고 공지 없이 조정되기도 합니다. 그러니 숫자를 외우지 말고 응답 헤더를 읽는 쪽으로 만들어야 합니다. 거래소 API의 기본 구조는 거래소 API 사용 가이드에 정리돼 있습니다.

폴링 주기만으로 한도가 소진되는 계산

가장 흔한 사고는 화려한 기능이 아니라 단순 반복 조회에서 납니다. 심볼 수와 주기를 곱하면 바로 보입니다.

10개 심볼을 1초마다 조회하는 봇

초당 요청 = 10심볼 × 1회 = 10회/초
분당 요청 = 10 × 60 = 600회/분

여기에 잔고 조회(1초마다) 60회
미체결 주문 조회(1초마다) 60회
포지션 조회(1초마다) 60회

합계 = 780회/분

가중치로 환산 — 시세 1점 · 잔고 5점 · 주문목록 3점 가정
시세 600 × 1 = 600점
잔고 60 × 5 = 300점
주문목록 60 × 3 = 180점
포지션 60 × 3 = 180점
합계 1,260점/분

→ 분당 상한이 1,200점이면 이미 초과
→ 심볼을 20개로 늘리면 두 배로 넘김

여기서 중요한 건 전략이 거래를 한 건도 하지 않아도 한도가 소진된다는 점입니다. 대기 상태에서 시세만 보고 있어도 요청은 계속 나갑니다. 그래서 심볼을 늘리는 순간, 백테스트에서 멀쩡하던 로직이 실전에서 데이터 공백을 겪습니다. 다중 종목 운용의 다른 고려사항은 API 봇 만들기 가이드에서 다뤘습니다.

429 다음에 오는 것이 더 위험하다

429는 경고에 가깝습니다. 진짜 문제는 그다음입니다.

429 이후의 전개

1. 요청 초과 → 429 응답
2. 봇이 실패로 보고 즉시 재시도
3. 재시도가 다시 한도를 밀어 올림
4. 거래소가 IP 단위 일시 차단(수 분~수십 분)
5. 차단 중에는 주문 취소·손절 전송도 실패

→ 포지션은 열려 있는데 손을 못 대는 상태

실전에서 손실로 이어지는 지점은 5번입니다. 조회가 안 되는 건 불편으로 끝나지만, 청산 주문이 안 나가는 건 손실입니다. 그래서 재시도 로직을 아무 생각 없이 while 반복으로 짜두면, 장애를 스스로 키우는 구조가 됩니다. 주문이 막히는 다른 조건들은 주문이 거부되는 이유에 있습니다.

무엇을 기준으로 세는가 — IP인가 계정인가

한도의 카운터가 무엇에 붙어 있는지 모르면 엉뚱한 해결책을 씁니다.

카운터의 기준

IP 기준
같은 서버에서 돌리는 봇 여러 개가 한도를 나눠 씀
→ 봇 3개를 한 PC에서 돌리면 각자 3분의 1
→ API 키를 새로 발급해도 소용 없음

API 키(계정) 기준
주문 관련 한도에서 흔함
→ 서버를 나눠도 계정이 같으면 그대로 걸림

둘 다 적용
공개 시세는 IP · 개인 주문은 키 기준으로 분리되는 경우가 일반적

이 구분이 중요한 이유는, "봇이 자꾸 429가 나서 키를 하나 더 만들었다"가 IP 기준 한도에서는 아무 효과가 없기 때문입니다. 반대로 계정 기준 주문 한도는 서버를 늘려도 그대로입니다. 키 발급과 권한 설정은 API 키 발급과 권한을 참고하면 됩니다.

응답 헤더로 남은 한도를 읽는다

대부분의 거래소는 응답 헤더에 현재 사용량과 남은 한도를 담아 보냅니다. 이름은 거래소마다 다르지만 역할은 같습니다.

헤더 활용 패턴

· 사용량 헤더를 매 응답마다 파싱해 저장
· 상한의 70%를 넘으면 비필수 조회를 자동 중단
· 90%를 넘으면 주문 외 요청 전면 정지
· 429 응답의 재시도 대기 안내 값이 있으면 그 값을 그대로 따른다

→ 상한을 코드에 상수로 박아두지 말고
→ 헤더가 알려주는 실측값으로 조절

상한을 상수로 하드코딩하면, 거래소가 정책을 바꾼 날 봇이 통째로 멈춥니다. 헤더 기반으로 만들면 정책이 바뀌어도 스스로 속도를 낮춥니다. 이건 자동매매를 오래 굴릴 때 유지보수 비용을 가장 크게 줄여주는 부분입니다.

줄이는 방법 ① 폴링을 구독으로 바꾼다

가장 효과가 큰 변경입니다. REST 폴링은 "바뀌었나요"를 반복해서 묻는 방식이고, WebSocket 구독은 바뀔 때만 받아오는 방식입니다.

같은 10심볼 실시간 시세 — 방식별 요청 수

REST 폴링(1초 주기)
10 × 60 = 600회/분
1시간이면 36,000회

WebSocket 구독
연결 1회 + 구독 메시지 10회
이후 요청 수 0
시세는 서버가 밀어줌

→ 요청 한도 소모가 사실상 사라짐
→ 지연도 폴링 주기(최대 1초)에서 체감상 즉시로

단, 구독에는 별도의 관리 비용이 있습니다. 연결이 끊기면 조용히 낡은 값이 남아 봇은 정상처럼 보이면서 옛날 가격으로 판단합니다. 그래서 구독을 쓸 때는 마지막 수신 시각을 기록하고, 일정 시간(예: 5초) 이상 갱신이 없으면 신규 진입을 멈추는 신선도 검사를 같이 넣습니다. 데이터가 낡았을 때 생기는 문제는 룩어헤드 편향과는 반대 방향이지만, 결과적으로 실제와 다른 정보로 판단한다는 점은 같습니다.

줄이는 방법 ② 개별 조회를 일괄 조회로

심볼마다 따로 부르는 대신 한 번에 전부 받아오는 엔드포인트가 있는 경우가 많습니다.

50개 심볼 시세를 받을 때

개별 조회
50회 × 가중치 1 = 50점
심볼이 늘면 선형으로 증가

전체 티커 일괄 조회
1회 × 가중치 40 = 40점
심볼이 500개여도 그대로 40점

→ 심볼이 40개를 넘는 순간부터 일괄이 유리
→ 500심볼이면 500점 대 40점, 12배 차이

주의할 점은 일괄 조회의 가중치가 크다는 것입니다. 심볼이 5개뿐인데 전체 티커를 매초 부르면 오히려 손해입니다. 손익분기점은 위 계산처럼 개별 가중치 × 심볼 수일괄 가중치를 비교해 정합니다.

줄이는 방법 ③ 데이터마다 주기를 따로 준다

모든 데이터를 같은 주기로 볼 이유가 없습니다. 잔고는 초 단위로 바뀌지 않습니다.

주기 분리 — 앞의 780회/분 봇 개선

변경 전
시세 1초 · 잔고 1초 · 주문목록 1초 · 포지션 1초
= 780회/분

변경 후
시세 → WebSocket 구독 (0회)
잔고 → 30초마다 = 2회/분
주문목록 → 10초마다 = 6회/분
포지션 → 5초마다 = 12회/분
+ 주문 체결 이벤트 발생 직후에만 즉시 1회 갱신

합계 20회/분 안팎

→ 780회 → 20회, 97% 감소

핵심은 마지막 줄입니다. 주기를 늘리면 반응이 느려질까 걱정되지만, 중요한 변화는 대부분 내 주문이 체결됐을 때 생깁니다. 그러니 평상시 주기는 길게 두고, 이벤트가 생긴 직후에만 즉시 한 번 더 조회하면 정확도는 유지되면서 요청 수만 줄어듭니다.

줄이는 방법 ④ 지수 백오프와 지터

429가 났을 때 얼마나 기다릴지의 문제입니다. 고정 간격 재시도는 한도가 회복되기 전에 다시 때리기 쉽습니다.

지수 백오프 대기 시간

1회차 실패 → 1초 대기
2회차 실패 → 2초
3회차 실패 → 4초
4회차 실패 → 8초
5회차 실패 → 16초
상한 60초에서 고정

지터(무작위 흔들기) 추가
대기 = 기본값 × (0.5 ~ 1.5 사이 난수)
4초 → 2.0초 ~ 6.0초 중 하나

→ 여러 봇이 동시에 재시도해 다시 몰리는 것을 방지

지터가 없으면, 같은 서버의 봇 여러 개가 정확히 같은 초에 동시 재시도해서 다시 한도를 넘깁니다. 무작위로 흩어주는 것만으로 회복 속도가 눈에 띄게 달라집니다. 그리고 재시도 횟수에는 반드시 상한을 둡니다. 무한 재시도는 차단을 연장할 뿐입니다.

주문 경로는 조회 경로와 분리한다

설계 차원의 원칙입니다. 조회 요청 때문에 주문이 밀리면 안 됩니다.

우선순위 큐 설계

1순위 — 절대 지연 불가
손절·청산 주문, 주문 취소
한도 예산의 일정 몫을 항상 비워둔다

2순위 — 지연 허용
신규 진입 주문, 포지션 조회

3순위 — 언제든 버려도 됨
차트 히스토리 백필, 통계용 수집, 로그용 조회
한도 사용률이 70%를 넘으면 자동 중단

→ 한도가 부족할 때 무엇을 먼저 포기할지를
→ 장애가 나기 전에 코드로 정해둔다

"한도의 몫을 비워둔다"는 게 요점입니다. 사용률 100%를 목표로 최적화하면, 변동성이 튀어 재시도가 늘어난 순간 가장 중요한 주문부터 실패합니다. 큰 주문을 나눠 넣는 TWAP 분할 집행이나 아이스버그 주문을 쓸 때는 주문 건수 자체가 늘어나므로, 주문 전용 한도를 먼저 확인해야 합니다.

데이터 수집·백테스트에서 나오는 사고

실시간 운용이 아니라 과거 데이터를 긁을 때 한도를 넘기는 경우가 오히려 더 흔합니다.

1분봉 3년치 백필

1년 = 525,600분 → 3년 = 1,576,800개
1회 요청당 1,000개 수신 가정
필요 요청 = 1,577회

제한 없이 반복문으로 돌릴 때
초당 20회 전송 → 79초 만에 1,577회
→ 대부분 429 · IP 차단

요청 간 0.2초 간격을 둘 때
1,577 × 0.2초 = 약 5분 15초
→ 차단 없이 완주

→ 5분을 아끼려다 30분 차단을 부른다

백필은 급할 이유가 없는 작업입니다. 요청 사이에 간격을 두고, 중간에 끊겨도 이어받을 수 있게 마지막으로 성공한 시각을 저장하면 재실행이 쉬워집니다. 수집한 데이터로 검증할 때의 주의점은 백테스트 가이드에 정리돼 있습니다.

점검 체크리스트

봇을 실계좌에 올리기 전

분당 요청 수와 가중치 합을 계산해서 적어봤는가
조회 · 주문 한도를 각각 확인했는가
응답 헤더의 사용량을 읽어 로그에 남기는가
429 처리에 지수 백오프 + 지터 + 재시도 상한이 있는가
실시간 시세를 WebSocket으로 받는가
구독 데이터의 신선도 검사가 있는가
손절·취소 주문용 한도 여유를 남겨두는가
한 IP에서 봇 몇 개가 도는지 파악하고 있는가
429가 연속 발생하면 관리자에게 알림이 가는가
백필·수집 스크립트에 요청 간격이 있는가

⑨가 실무에서 제일 자주 빠집니다. 429는 예외 처리로 조용히 삼켜지기 쉬워서, 봇은 멀쩡해 보이는데 아무 일도 안 하고 있는 상태가 며칠씩 이어집니다. 실패를 로그에만 남기지 말고 임계치를 넘으면 밖으로 알리도록 만들어야 합니다.

정리

한도는 횟수 · 가중치 · 주문 전용으로 따로 돈다
거래를 안 해도 폴링만으로 한도가 소진된다
429보다 위험한 건 그 뒤의 IP 차단 — 청산 주문이 막힌다
카운터가 IP 기준이면 키를 늘려도 소용없다
상한은 하드코딩하지 말고 응답 헤더로 읽는다
폴링 → 구독 전환이 가장 큰 절감 (600회/분 → 0회)
데이터마다 주기를 다르게, 이벤트 직후에만 즉시 갱신
재시도는 지수 백오프 + 지터 + 상한
손절·취소용 한도 여유는 항상 비워둔다
429가 반복되면 알림이 울리게 한다

한 줄로 줄이면 이렇습니다. 레이트리밋은 성능 문제가 아니라 안전 문제입니다. 요청이 몇 개인지는 평소엔 아무 상관이 없다가, 시장이 급하게 움직여 재시도가 몰리는 바로 그 순간에 손절 주문을 막습니다. 그러니 한도는 다 쓰는 대상이 아니라, 가장 급할 때 쓰려고 남겨두는 예산으로 보는 편이 맞습니다.

NOONOO TRADING 무료 채팅방에서 실시간 트레이딩을 같이 보세요.

무료 채팅방 입장 →

📈 OKX 신규 가입 시 거래 수수료 할인

OKX 수수료 할인 가입 →