튜토리얼약 5분 읽기

주문 체결 확인과 멱등성 설계 — 중복 주문을 막는 법

응답이 안 왔을 뿐인데, 주문이 두 번 나갔다

에러코드 처리에서 일시적 에러는 재시도해도 된다고 했다. 그런데 재시도가 항상 안전한 건 아니다. 주문 요청을 보냈는데 타임아웃이 나면, 서버가 요청을 못 받은 건지 처리는 했는데 응답만 안 온 건지 클라이언트는 구분할 수 없다. 여기서 그냥 재시도하면, 실제로는 주문이 이미 들어갔는데 똑같은 주문이 한 번 더 나가는 사고가 생길 수 있다.

이 글은 특정 증권사의 실제 응답 형식을 다루지 않는다. 어떤 주문 API든 적용할 수 있는 중복 방지 설계 원칙을 다룬다.

멱등성이란 — 같은 요청을 여러 번 보내도 결과가 같아야 한다

멱등성(idempotency)은 같은 요청을 여러 번 반복해도 실제 효과는 한 번만 일어나는 성질이다. 조회 요청은 원래 멱등하다 — 몇 번을 조회해도 잔고가 변하지 않는다. 문제는 주문처럼 상태를 변경하는 요청이다. 재시도가 필요한 상황은 반드시 생기는데, 그 재시도가 새 주문을 또 만들면 안 된다.

클라이언트 주문번호로 중복을 막는다

가장 흔한 해법은 요청을 보내는 쪽에서 고유한 주문번호(client order id)를 미리 만들어 요청에 포함시키는 것이다. 서버가 같은 주문번호를 이미 처리한 적이 있다면, 새 주문을 만드는 대신 기존 주문의 상태를 돌려주도록 약속한다.

python 코드 보기
import uuid

def place_order_idempotent(order_payload, seen_orders):
    client_order_id = order_payload.get("client_order_id")
    if client_order_id is None:
        client_order_id = str(uuid.uuid4())
        order_payload["client_order_id"] = client_order_id

    if client_order_id in seen_orders:
        # 이미 보낸 요청 — 새로 전송하지 않고 기존 결과를 조회
        return check_order_status(client_order_id)

    seen_orders[client_order_id] = "sent"
    response = send_order(order_payload)
    seen_orders[client_order_id] = response
    return response

핵심은 client_order_id를 요청 시점에 한 번만 생성하고, 재시도할 때는 같은 값을 그대로 재사용한다는 점이다. 증권사 API가 클라이언트 주문번호를 지원하지 않는다면, 이 패턴을 온전히 쓸 수 없으니 문서에서 지원 여부부터 확인해야 한다.

응답이 없으면 재시도 전에 먼저 확인한다

클라이언트 주문번호를 지원하지 않는 API도 있다. 이런 경우 재시도 전에 먼저 "혹시 이미 들어갔는지" 체결·미체결 조회로 확인하는 절차를 끼워 넣는 편이 안전하다.

python 코드 보기
def send_with_confirmation(order_payload, get_recent_orders):
    try:
        return send_order(order_payload)
    except TimeoutError:
        recent = get_recent_orders()
        matched = find_matching_order(recent, order_payload)
        if matched:
            return matched  # 이미 들어간 주문 — 재전송하지 않는다
        return send_order(order_payload)  # 정말 안 들어갔을 때만 재시도

find_matching_order는 종목·수량·주문 유형·시간창(예: 최근 수십 초)을 기준으로 방금 보낸 요청과 같은 주문이 이미 있는지 대조하는 함수다. 완벽하게 100% 구분되진 않지만, 아무 확인 없이 바로 재전송하는 것보다는 훨씬 안전하다.

오늘의 정리

  1. 주문처럼 상태를 바꾸는 요청은 재시도 자체가 중복 주문의 위험을 만든다. 재시도 로직을 넣기 전에 멱등성을 먼저 설계해야 한다.
  2. 클라이언트 주문번호를 요청에 포함시키고 재시도 시 재사용하면, 서버가 중복 요청을 걸러줄 수 있다. API가 이를 지원하는지 먼저 확인한다.
  3. 클라이언트 주문번호를 못 쓰는 환경이라면, 재시도 전에 최근 주문 내역을 조회해 이미 들어간 주문인지 확인하는 절차를 끼워 넣는다.

이 글의 코드는 멱등성 설계 개념을 설명하기 위한 의사코드이며, 특정 증권사 API의 실제 스펙이나 실거래 시스템의 구현이 아닙니다. 실제 지원 여부는 반드시 해당 증권사 공식 문서에서 확인하세요. 투자 조언이 아니며 특정 종목이나 매매기법을 추천하지 않고, 투자 손실에 대한 책임은 투자자 본인에게 있습니다.

← 전체 글 목록