API 유량 제어 고민

yFinDev·2025년 4월 14일

고민

목록 보기
1/1

문제 파악

API 초당 호출 가능 유량을 안내드립니다.
제한된 유량을 인지하시고 API 호출 사용하시기를 부탁드립니다.
 
REST API
(실전투자) 1초당 20건
(모의투자) 1초당 2건
※ 계좌 단위로 유량 제한(실전투자의 경우, 1계좌당 1초당 20건)
※ 접근토큰발급(/oauth2/tokenP)의 경우 1초당 1건(23.10.27 시행)
 
WEBSOCKET : 1세션
실시간체결가 + 호가 + 예상체결 + 체결통보 등 실시간 데이터 합산 41건까지 등록 가능**
※ 국내주식/해외주식/국내파생/해외파생 모든 상품 실시간 합산 41건
※ 체결통보의 경우 HTS ID 단위로 등록하여, ID에 연결된 모든 계좌의 체결 통보 수신
※ 계좌(앱키) 단위로 유량 제한(1개의 계좌(앱키)당 1세션)
※ 1개의 PC에서 여러 개의 계좌(앱키)로 세션 연결 가능

OO투자증권 API를 이용도중
실전투자 환경에서 개발하기 이전에 모의투자 환경에서 개발 도중
유량 제한이 있다는 사실을 알게 되었다.

data.put("price", getPriceInquiry(stockCode));
data.put("balance", getBalanceInquiry());
data.put("trades", getInquireCcnl(stockCode));
data.put("order", getPsblOrder(stockCode));

getInquireCcnl() 실행하기 이전에 API 호출을 두번 했기 때문에 getInquireCcnl에서 초당 거래건수를 초과하는 문제가 발생했습니다.

data.put("price", getPriceInquiry(stockCode));
data.put("balance", getBalanceInquiry());
Thread.sleep(1000);
data.put("trades", getInquireCcnl(stockCode));
data.put("order", getPsblOrder(stockCode));

문제를 처음 접했을 당시에는 Api 4개만을 사용하고 있었기에
2회 요청 후 -> Thread.sleep(1000) 1초 지연 -> 2회 요청을 하여 결과를 가져와도 괜찮을 것이라고 생각하였었다.

그러나 Api 호출-응답 시간에 1초 지연이 더해져 결과를 가져오는데에 큰 시간 지연이 발생하는 문제가 있었다.

유량 제한 문제를 해결할 수 있는 방법보다 Api 호출 시간을 줄이기위해 각 함수들을 비동기방식으로 변경하고 병렬실행하여 시간을 조금이라도 줄이고자 하였다.

public CompletableFuture<Map<String, String>> getPortfolioData(String stockCode){
            CompletableFuture<String> priceFuture = CompletableFuture.supplyAsync(() -> {
                try {
                    return getPriceInquiry(stockCode);
                }catch (Exception e){
                    throw new CompletionException(e);
                }
            });

            CompletableFuture<String> balanceFuture = CompletableFuture.supplyAsync(()->{
                try {
                    return getBalanceInquiry();
                }catch (Exception e){
                    throw new CompletionException(e);
                }
            });

            CompletableFuture<String> tradesFuture = CompletableFuture.supplyAsync(()->{
                try {
                    return getInquireCcnl(stockCode);
                }catch (Exception e){
                    throw new CompletionException(e);
                }
            });

            CompletableFuture<String> orderFuture = CompletableFuture.supplyAsync(()->{
                try {
                    return getPsblOrder(stockCode);
                }catch (Exception e){
                    throw new CompletionException(e);
                }
            });
            return CompletableFuture.allOf(priceFuture, balanceFuture, tradesFuture, orderFuture)
                    .thenApply(v -> {
                        Map<String, String> data = new HashMap<>();
                        try {
                            data.put("price", priceFuture.get());
                            data.put("balance", balanceFuture.get());
                            Thread.sleep(1000); 
                            data.put("trades", tradesFuture.get());
                            data.put("order", orderFuture.get());
                            log.info("포트폴리오 데이터 조회 완료");
                        } catch (InterruptedException | ExecutionException e) {
                            throw new RuntimeException(e);
                        }
                        return data;
                    });
        }

하지만 1초에 2회 호출할 수 있다는 유량제한은 변하지 않으므로 강제대기를 넣는순간 동기 방식과 실질적인 속도 차이는 없고 오히려 병렬성에 제한을 걸게된다는 것을 깨달았다.

해결 방법 탐구

Resilience4j 라이브러리로 RateLimiter사용하여
주어진 시간 내에 일정 호출 횟수를 제한하기 위하여 해결하고자 하였습니다.

public class RateLimiterConfigFactory {
    private static final RateLimiterConfig config = RateLimiterConfig.custom()
            .limitForPeriod(2) // 초당 2개 요청 허용
            .limitRefreshPeriod(Duration.ofSeconds(1))
            .timeoutDuration(Duration.ofSeconds(2)) // 2초 대기 후 실패
            .build();

    public static RateLimiter create() {
        return RateLimiter.of("stock-api", config);
    }
}
public InquirePriceDTO getPriceInquiry(String stockCode) throws Exception {
        try {
            return RateLimiter.decorateSupplier(rateLimiter, () -> {
                try {
                    String endpoint = domain + "/uapi/domestic-stock/v1/quotations/inquire-price";
                    String queryString = new QueryStringBuilder()
                            .addParam("fid_cond_mrkt_div_code", "J")
                            .addParam("fid_input_iscd", stockCode)
                            .build();
                    String responseJson = HttpClientUtil.sendGetRequest(endpoint, queryString, "FHKST01010100");
                    log.debug("현재가 체결 조회 응답: {}", responseJson);
                    InquirePriceDTO response = objectMapper.readValue(responseJson, InquirePriceDTO.class);
                    return response;
                } catch (Exception e) {
                    log.error("현재가 체결 조회 실패: {}", e.getMessage());
                    throw new RuntimeException("현재가 체결 조회 중 오류 발생", e);
                }
            }).get();
        } catch (RequestNotPermitted e) {
            log.error("API 호출 제한 초과: {}", e.getMessage());
            throw new Exception("API 호출 한도 초과");
        }
    }

RateLimiter를 사용하여 1초마다 버킷에 최대 2개 토큰이 생성하고

요청 시마다 토큰이 1개씩 소모되도록 하였다.

한계점

이전보다 안정성은 확보할 수 있었나 좀 더 본질적인 해결방법이 있지않을까 고민하던 도중 OO투자증권에서 API 유량 제한을 두는 것은 서버의 부하를 막기 위한 서버 정책임을 깨달았습니다.

가상자산 거래소 OO의 open API 유량 제한
사용자 급증 등 과도한 트래픽이 발생할 경우 안정적인 서비스 제공을 위해 
API 요청량을 별도의 공지 없이 제한 또는 하향 조정 할 수 있습니다.

비록 문제라고 생각했던 것을 해결하지는 못했으나 해결 방법을 고민하는 과정에서 OO투자증권과 open API를 제공하는 회사들의 API 유량 제한 정책을 살펴보며
비용적인 측면 또한 고려해볼 수 있었습니다.

0개의 댓글