LabHub
배우기 러닝패스 코스

システム間連携 (EAI)

冪等キーで重複受信を防ぐ

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

목표

멱등키를 설계하고 DB 제약으로 중복을 막고, 동시 실행에서도 안전하게 만들고, 저장-후-응답까지 구현해 재전송이 안전한 수신측을 만들 수 있게 됩니다.

왜 중요한가

비동기 연동과 재시도가 있는 세상에서 중복은 예외가 아니라 기본값입니다. 그런데 중복 방어를 애플리케이션의 조회 후 없으면 삽입 으로 구현하면 동시에 두 건이 들어오는 순간 뚫립니다. 조회와 삽입 사이에 틈이 있기 때문입니다. DB 제약은 그 틈을 없애고, 애플리케이션에 버그가 있어도 뚫리지 않습니다. 그리고 한 걸음 더 나가 최초 응답을 저장해 두고 중복 요청에 그대로 돌려주면, 송신측이 타임아웃 후 마음 놓고 재전송할 수 있게 됩니다. 결제 API 들이 Idempotency-Key 를 쓰는 이유가 이것입니다.

단계

  1. /root/i/idem.db (sqlite)에 inbox_log 테이블을 만듭니다. 컬럼은 msg_id, biz_key, status, response, created_at 이고, msg_idPRIMARY KEY 또는 UNIQUE 제약이 있어야 합니다.
  2. /root/i/key.md 를 작성합니다. 아래 세 가지가 본문에 있어야 합니다.
    • 이 인터페이스의 멱등키를 무엇으로 잡을지와 그 이유
    • order_no 하나만으로 잡으면 안 되는 이유
    • 멱등 이력의 보관 기간과 그 근거 멱등키, 보관, 수정 세 단어가 모두 등장해야 합니다.
  3. /root/i/apply.sh 를 만듭니다. 인자 두 개(DB파일 JSON파일)를 받아
    • 신규면 적재하고 첫 줄에 applied 출력, 종료코드 0
    • 이미 있으면 아무것도 하지 않고 첫 줄에 duplicate 출력, 종료코드 0 조회 후 삽입 방식이 아니라 제약을 이용한 방식이어야 합니다.
  4. /opt/lab/fixtures/eai/idem/messages/ 의 모든 JSON 을 apply.sh 로 적재합니다.
    • inbox_log 행 수가 고유 msg_id 수와 같아야 합니다
    • 중복으로 무시된 msg_id/root/i/dup.txt 에 오름차순으로 저장합니다
  5. /root/i/race.sh 를 만듭니다. 인자 두 개(DB파일 JSON파일)를 받아 같은 메시지를 동시에 10번 적재 시도하고, 마지막 줄에 rows=<해당 msg_id 의 행 수> 를 출력합니다. 값은 1 이어야 합니다. 실행 결과를 /root/i/race.txt 에 저장하세요. (sqlite 잠금 경합에 대비해 PRAGMA busy_timeout 을 설정하세요.)
  6. /root/i/purge.sh 를 만듭니다. 인자 두 개(DB파일 보관일수)를 받아 created_at 이 보관일수보다 오래된 행만 삭제하고, 마지막 줄에 deleted=<건수> remain=<건수> 를 출력합니다.
  7. apply.sh 를 확장해 저장-후-응답을 구현합니다.
    • 신규 처리 시 응답 JSON 을 response 컬럼에 저장하고, applied 다음 줄(둘째 줄)에 그 응답을 출력합니다
    • 중복이면 duplicate 다음 줄(둘째 줄)에 저장된 최초 응답을 그대로 출력합니다 같은 메시지를 두 번 적용했을 때 두 번째 출력의 2번째 줄이 첫 번째의 응답과 같아야 합니다. 확인 결과를 /root/i/replay.txt 에 저장하세요.
  8. /root/i/report.md 를 작성합니다. 중복이 발생하는 네 가지 경로를 각각 한 항목으로 적고, 각 경로마다 방어 지점을 함께 적습니다. 재시도, , 수동, 배치 네 단어가 모두 등장해야 합니다.

참고

멱등 이력 테이블

/root/i/idem.db (sqlite)에 inbox_log 테이블을 만듭니다. 컬럼은 msg_id, biz_key, status, response, created_at 이고, msg_idPRIMARY KEY 또는 UNIQUE 제약이 있어야 합니다.

중복 방어의 마지막 방어선은 애플리케이션이 아니라 DB 제약입니다. 어떤 컬럼에 제약을 걸어야 할지 먼저 정하세요.

멱등키 설계 문서

/root/i/key.md 를 작성합니다. 아래 세 가지가 본문에 있어야 합니다.

업무 키만으로는 부족한 경우가 많습니다. 같은 주문에 대한 수정 전문이 있을 수 있고, 전문번호가 일 단위로 순환할 수도 있습니다.

적재 스크립트

/root/i/apply.sh 를 만듭니다. 인자 두 개(DB파일 JSON파일)를 받아

이미 처리한 키면 아무것도 하지 않고 그렇게 알려야 합니다. 조회 후 삽입은 동시 실행에서 뚫리니, 제약을 활용하는 방식을 쓰세요.

일괄 적재와 중복 집계

/opt/lab/fixtures/eai/idem/messages/ 의 모든 JSON 을 apply.sh 로 적재합니다.

적재 결과에서 신규와 중복을 구분해 세어야 합니다. 중복된 키 목록을 남겨 두면 나중에 원인 분석에 씁니다.

동시 실행 방어

/root/i/race.sh 를 만듭니다. 인자 두 개(DB파일 JSON파일)를 받아 같은 메시지를 동시에 10번 적재 시도하고, 마지막 줄에 rows=<해당 msg_id 의 행 수> 를 출력합니다. 값은 1 이어야 합니다. 실행 결과를 /root/i/race.txt 에 저장하세요. (sqlite 잠금 경합에 대비해 PRAGMA busy_timeout 을 설정하세요.)

같은 메시지를 병렬로 여러 번 넣어도 한 건이어야 합니다. sqlite 는 잠금 경합이 잦으니 대기 시간을 설정해야 합니다.

보관 기간 정리

/root/i/purge.sh 를 만듭니다. 인자 두 개(DB파일 보관일수)를 받아 created_at 이 보관일수보다 오래된 행만 삭제하고, 마지막 줄에 deleted=<건수> remain=<건수> 를 출력합니다.

정리하는 순간 그 구간의 중복 방어가 사라집니다. 기간 조건으로만 지워야 하고, 건수 기준으로 지우면 최근 것을 지울 수 있습니다.

저장-후-응답

apply.sh 를 확장해 저장-후-응답을 구현합니다.

중복 요청에 최초 응답을 그대로 돌려주면 송신측 입장에서 재전송이 완전히 안전해집니다. 결제 API 들이 쓰는 방식입니다.

중복 발생 경로 정리

/root/i/report.md 를 작성합니다. 중복이 발생하는 네 가지 경로를 각각 한 항목으로 적고, 각 경로마다 방어 지점을 함께 적습니다. 재시도, , 수동, 배치 네 단어가 모두 등장해야 합니다.

중복은 네 가지 경로로 옵니다. 각 경로에서 어느 지점이 방어선인지 함께 적으세요.