재접속에도 살아남는 암호화폐 피드, 그리고 차단을 막아 주는 레이트 리미터 | eSeGeCe 블로그

재접속에도 살아남는 암호화폐 피드, 그리고 차단을 막아 주는 레이트 리미터

· 컴포넌트

암호화폐 거래소와 통신하는 애플리케이션은 결국 똑같은 두 가지 배관 코드를 작성하게 됩니다. 첫 번째는 소켓이 끊긴 뒤 시세 데이터 구독을 복원하는 코드입니다. 두 번째는 거래소가 여러분을 스로틀링하지 않도록 REST 호출 속도를 늦추는 코드입니다. 둘 다 흥미로운 코드가 아니고, 둘 다 미묘하게 잘못되기 쉬우며, 지금까지는 둘 다 애플리케이션의 몫이었습니다.

sgcWebSockets 2026.7은 이 둘을 내장했습니다. 거래소 API 클라이언트가 재접속 후 자신의 구독을 스스로 다시 보낼 수 있고, 모든 기성 REST 클라이언트에는 일정 시간 창 안에서 보낼 수 있는 요청 수를 제한하는 클라이언트 측 레이트 리미터가 생겼습니다. 둘 다 선택 기능이며 기본적으로 꺼져 있으므로, 켜기 전까지는 업그레이드해도 아무것도 달라지지 않습니다.

아무것도 구독하지 않은 채로 돌아오는 재접속

WatchDog는 언제나 연결을 되살리는 데 능했습니다. 케이블이 뽑히고, 프록시가 타임아웃되고, 거래소가 게이트웨이를 재시작해도, 몇 초 뒤면 클라이언트는 다시 접속되어 있습니다. 그 부분은 잘 동작합니다.

돌아오지 않는 것은 여러분의 구독입니다. 거래소는 그 구독을 기억하지 않습니다. 새 소켓은 빈 세션이므로, 이전 소켓에서 요청했던 체결과 호가창 스트림은 더 이상 존재하지 않습니다. 증상이 고약한 이유는 아무것도 고장 나 보이지 않기 때문입니다. 클라이언트는 Connected를 보고하고, 오류도 발생하지 않으며, 메시지 이벤트만 조용히 멈춥니다. 차트는 소리 없이 낡아 갑니다.

흔한 해결책은 자체 구독 목록을 유지하다가 OnConnect에서 다시 재생하는 것입니다. 이는 요청했던 모든 심볼, 모든 채널, 모든 스트림을 기억해야 하고, 코드베이스 어딘가에서 구독하거나 구독 해제할 때마다 그 목록을 갱신하는 것도 잊지 말아야 한다는 뜻입니다. 여러분의 전략과는 아무 상관 없는 장부 관리입니다.

자동 재구독 켜기

API 클라이언트는 여러분이 무엇을 구독했는지 이미 알고 있습니다. 여러분이 그것을 통해 구독했기 때문입니다. 2026.7에서는 API 클라이언트가 그 목록을 보관해 두었다가 재접속 후 스스로 다시 보낼 수 있습니다. 거래소 옵션 객체에 있는 불리언 하나, Resubscribe입니다.

아래는 오늘날 대부분의 트레이딩 앱이 손으로 작성하고 있는 버전과, 그것을 대체하는 코드입니다.

uses
  sgcWebSocket, sgcWebSocket_API_Binance;

// Before: keep your own list and replay it on every reconnect.
procedure TForm1.OnBinanceConnect(Connection: TsgcWSConnection);
begin
  sgcWSAPI_Binance1.SubscribeTrades('btcusdt');
  sgcWSAPI_Binance1.SubscribeTicker('btcusdt');
  sgcWSAPI_Binance1.SubscribeTrades('ethusdt');
  // ... and every other symbol you had open, in the right order.
end;

// After: the API client replays them for you.
sgcWSAPI_Binance1.Binance.Resubscribe := True;

Resubscribe를 켜면 클라이언트는 여러분이 구독할 때마다 그것을 기록해 두었다가, WatchDog가 소켓을 되살린 뒤 새 세션에서 다시 전송합니다. 구독을 해제하면 항목이 제거되므로, 의도적으로 닫은 스트림이 다음 재접속 때 되살아나는 일은 없습니다.

이 기능은 WatchDog를 대체하는 것이 아니라 함께 쓰입니다. WatchDog가 전송 계층을 복원하고, 그 위에서 Resubscribe가 세션을 복원합니다.

uses
  sgcWebSocket, sgcWebSocket_API_Kraken;

begin
  sgcWebSocketClient1.WatchDog.Enabled := True;
  sgcWebSocketClient1.WatchDog.Timeout := 5;   // retry the socket every 5 seconds

  sgcWSAPI_Kraken1.Client := sgcWebSocketClient1;
  sgcWSAPI_Kraken1.Kraken.Resubscribe := True; // and restore the streams on top

  sgcWebSocketClient1.Active := True;
  sgcWSAPI_Kraken1.SubscribeTrade(['XBT/USD', 'ETH/USD']);
end;

기본값은 False입니다. 이미 손으로 재구독하고 있는 기존 애플리케이션은 중복 구독 없이 예전과 똑같이 계속 동작하며, 여러분이 직접 작성한 코드를 지우고 이 플래그를 켤 때까지 그대로입니다.

어떤 거래소가 지원되나

Resubscribe는 각 거래소 클라이언트의 옵션 객체에 있습니다. Binance, Bitfinex, Bitget, BitMEX, Bitstamp, Bybit, CEX, CEX Plus, Coinbase, Crypto.com, Deribit, Gate.io, Huobi, Kraken, KuCoin, MEXC, OKX에서 사용할 수 있고, 세션 기반인 XTB 스트리밍 피드에서도 사용할 수 있습니다. 프로퍼티 이름은 항상 동일하므로, 폼에 올린 컴포넌트에 따라 Coinbase.Resubscribe, Bybit.Resubscribe, OKX.Resubscribe처럼 읽히게 됩니다.

IG를 비롯한 여러 브로커가 사용하는 Lightstreamer 클라이언트는 내부적으로 같은 일을 합니다. 세션 리바인드 후 서버에는 이전 구독 기록이 없으므로, 클라이언트가 요청 없이도 로컬 컬렉션에서 구독을 다시 재생합니다.

클라이언트 쪽에서 보는 레이트 한도

트레이딩 앱의 나머지 절반은 REST 쪽입니다. 주문 넣기, 주문 취소, 잔고 조회, 심볼 목록 조회 같은 것들입니다. 모든 거래소는 이런 엔드포인트에 대한 요청 예산을 공개합니다. 보통 IP당 또는 키당 분당 요청 수나 가중치입니다. 이를 초과하면 429를 받고, 계속 초과하면 일시 차단을 당하는데, 그날은 훨씬 더 나쁜 하루가 됩니다.

2026.7은 TsgcHTTPAPI_client에 클라이언트 측 레이트 리미터를 추가했으므로, 모든 기성 REST 클라이언트가 이를 상속받습니다. 슬라이딩 윈도 방식입니다. 임의의 IntervalMs 창 안에서 최대 MaxRequests개의 요청만 허용합니다. 거래소가 공개한 규칙에 맞춰 설정하면 됩니다. 결국 중요한 것은 거래소의 한도뿐이기 때문입니다.

uses
  sgcHTTP_API, sgcHTTP_API_Binance;

var
  oBinance: TsgcHTTP_API_Binance_Rest;
begin
  oBinance := TsgcHTTP_API_Binance_Rest.Create(nil);
  try
    oBinance.RateLimit.Enabled     := True;
    oBinance.RateLimit.IntervalMs  := 60000;   // one minute window
    oBinance.RateLimit.MaxRequests := 1000;    // stay under the published budget
    oBinance.RateLimit.Behavior    := rlbWait; // default

    // Every request through this component now takes a slot first.
    oBinance.GetAccountInformation;
  finally
    oBinance.Free;
  end;
end;

스로틀링을 당하는 전형적인 경로는 시작 시의 버스트입니다. 애플리케이션이 실행되면서 심볼 목록, 잔고, 미체결 주문, 그리고 40개 종목의 최근 캔들을 모두 첫 1초 안에 한꺼번에 불러옵니다. 그 자체로는 전혀 무리한 일이 아니지만, 거래소는 여전히 1초에 40개의 요청으로 셉니다. 레이트 리미터는 초기화 코드 곳곳에 Sleep 호출을 뿌리지 않고도 바로 그런 형태의 트래픽을 매끄럽게 다듬어 줍니다.

레이트 리미터는 지연 생성됩니다. RateLimit를 전혀 건드리지 않으면 객체가 할당되지 않고 요청 경로는 바이트 단위로 예전 그대로이므로, 사용하지 않는다면 치를 비용도 없습니다.

대기할 것인가, 거부할 것인가

현재 창의 예산이 소진되면 다음 호출을 어떻게 처리할지 선택할 수 있습니다. 그것이 Behavior이며, 단순한 튜닝 손잡이가 아니라 진짜 결정입니다.

기본값인 rlbWait는 슬롯이 날 때까지 호출을 블로킹했다가 전송합니다. 과거 데이터를 불러오거나 포지션을 대조하는 백그라운드 워커에서 원하는 동작입니다. 작업이 조금 더 오래 걸리지만 전부 완료됩니다.

rlbReject는 즉시 EsgcHTTPRateLimit를 발생시키고 아무것도 보내지 않습니다. 늦은 요청이 아예 없는 요청보다 나쁠 때 원하는 동작입니다. 보내기로 결정한 지 3초 뒤에 도착하는 주문은 이미 원하지 않는 주문일 수 있고, 움직이는 시장에 그것을 쏘아 보내는 것은 시끄럽게 실패하는 것보다 나쁩니다.

uses
  SysUtils, sgcHTTP_API, sgcHTTP_API_Binance;

begin
  oBinance.RateLimit.Enabled     := True;
  oBinance.RateLimit.IntervalMs  := 1000;
  oBinance.RateLimit.MaxRequests := 10;
  oBinance.RateLimit.Behavior    := rlbReject;

  try
    oBinance.NewOrder('BTCUSDT', 'BUY', 'LIMIT', ...);
  except
    on E: EsgcHTTPRateLimit do
      // Budget exhausted. Drop the order rather than send it stale.
      LogWarning('rate limit hit, order not sent: ' + E.Message);
  end;
end;

흔한 구성은 컴포넌트 두 개로 둘 다 쓰는 것입니다. 대량 로딩을 담당하는 컴포넌트에는 rlbWait, 주문을 넣는 컴포넌트에는 rlbReject를 두고, 각각 자체 예산을 갖게 합니다.

이것은 서버 측 레이트 리미터가 아닙니다

이름이 비슷하니 분명히 해 둘 필요가 있습니다. sgcWebSockets 2026.5는 TsgcWSRateLimiter를 제공했는데, 이것은 서버 측 리미터입니다. 인바운드 연결이 여러분에게 보낼 수 있는 양을 제한해서 여러분의 서버를 여러분의 클라이언트로부터 보호합니다. 이번 글의 리미터는 방향이 반대입니다. 애플리케이션이 거래소로 내보내는 양을 제한해서 다른 쪽의 한도로부터 여러분을 보호하는 클라이언트 측 리미터입니다. 둘은 서로 독립적이며, 서버이면서 동시에 거래소 소비자인 애플리케이션이라면 둘 다 편하게 쓰면 됩니다.

암호화폐만을 위한 것은 아닙니다

RateLimit는 기반 HTTP API 클라이언트에 있으므로, 거래소뿐 아니라 라이브러리의 모든 기성 REST 클라이언트가 이를 상속받습니다. AI 클라이언트도 정확히 같은 종류의 요청 한도를 가지고 있으므로 OpenAI, Anthropic, Gemini에서도 사용할 수 있고, Stripe를 비롯한 나머지 API 통합에서도 마찬가지입니다. 프로퍼티는 같은 자리에 같은 네 가지 설정으로 있습니다.

uses
  sgcHTTP_API, sgcHTTP_API_OpenAI;

begin
  oOpenAI.RateLimit.Enabled     := True;
  oOpenAI.RateLimit.IntervalMs  := 60000;
  oOpenAI.RateLimit.MaxRequests := 500;    // your tier's requests per minute
  oOpenAI.RateLimit.Behavior    := rlbWait;
end;

계정이 분당 요청 한도가 낮은 티어에 속해 있다면, 병렬 배치 작업이 한도를 건드리지 않게 막는 가장 저렴한 방법입니다.

제공 범위

자동 재구독과 클라이언트 측 레이트 리미터는 sgcWebSockets 2026.7의 Delphi 7부터 13까지와 C++Builder에서, Win32/Win64, Linux64, macOS, Android, iOS를 대상으로 제공됩니다. 둘 다 기본적으로 꺼져 있으므로 업그레이드는 그대로 끼워 넣기만 하면 됩니다.

활성 구독 중인 고객은 고객 영역에서 새 빌드를 내려받을 수 있습니다. 평가판 사용자는 esegece.com/products/websockets/download에서 업데이트된 설치 파일을 받을 수 있습니다.

질문이나 피드백이 있거나, 트레이딩 앱에 이 기능을 연결하는 데 도움이 필요하신가요? 문의해 주세요. 코드를 직접 작성한 사람들이 답변해 드립니다.