시스템 간 연동 (EAI) · 오류 처리와 재처리 설계 · 실습
멱등키로 중복 수신 방어하기
목표
멱등키를 설계하고 DB 제약으로 중복을 막고, 동시 실행에서도 안전하게 만들고,
저장-후-응답까지 구현해 재전송이 안전한 수신측을 만들 수 있게 됩니다.
왜 중요한가
비동기 연동과 재시도가 있는 세상에서 중복은 예외가 아니라 기본값입니다.
그런데 중복 방어를 애플리케이션의 조회 후 없으면 삽입 으로 구현하면
동시에 두 건이 들어오는 순간 뚫립니다. 조회와 삽입 사이에 틈이 있기 때문입니다.
DB 제약은 그 틈을 없애고, 애플리케이션에 버그가 있어도 뚫리지 않습니다.
그리고 한 걸음 더 나가 최초 응답을 저장해 두고 중복 요청에 그대로 돌려주면,
송신측이 타임아웃 후 마음 놓고 재전송할 수 있게 됩니다.
결제 API 들이 Idempotency-Key 를 쓰는 이유가 이것입니다.
단계
1. /root/i/idem.db (sqlite)에 inbox_log 테이블을 만듭니다.
컬럼은 msg_id, biz_key, status, response, created_at 이고,msg_id 에 PRIMARY KEY 또는 UNIQUE 제약이 있어야 합니다.
2. /root/i/key.md 를 작성합니다. 아래 세 가지가 본문에 있어야 합니다.
- 이 인터페이스의 멱등키를 무엇으로 잡을지와 그 이유
order_no하나만으로 잡으면 안 되는 이유- 멱등 이력의 보관 기간과 그 근거
- 신규면 적재하고 첫 줄에
applied출력, 종료코드 0 - 이미 있으면 아무것도 하지 않고 첫 줄에
duplicate출력, 종료코드 0 inbox_log행 수가 고유 msg_id 수와 같아야 합니다- 중복으로 무시된
msg_id를/root/i/dup.txt에 오름차순으로 저장합니다 - 신규 처리 시 응답 JSON 을
response컬럼에 저장하고, - 중복이면
duplicate다음 줄(둘째 줄)에 저장된 최초 응답을 그대로 출력합니다
멱등키, 보관, 수정 세 단어가 모두 등장해야 합니다.
3. /root/i/apply.sh 를 만듭니다. 인자 두 개(DB파일 JSON파일)를 받아
조회 후 삽입 방식이 아니라 제약을 이용한 방식이어야 합니다.
4. /opt/lab/fixtures/eai/idem/messages/ 의 모든 JSON 을 apply.sh 로 적재합니다.
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 를 확장해 저장-후-응답을 구현합니다.
applied 다음 줄(둘째 줄)에 그 응답을 출력합니다
같은 메시지를 두 번 적용했을 때 두 번째 출력의 2번째 줄이
첫 번째의 응답과 같아야 합니다. 확인 결과를 /root/i/replay.txt 에 저장하세요.
8. /root/i/report.md 를 작성합니다.
중복이 발생하는 네 가지 경로를 각각 한 항목으로 적고,
각 경로마다 방어 지점을 함께 적습니다.재시도, 큐, 수동, 배치 네 단어가 모두 등장해야 합니다.
참고
- 제약 활용 삽입:
INSERT OR IGNORE INTO ...후changes()로 반영 여부 판정 - 동시 실행:
for i in $(seq 10); do ... & done; wait - 잠금 대기:
PRAGMA busy_timeout=5000; - 날짜 비교:
created_at < datetime('now', '-30 days') - 흔한 실수 1:
SELECT로 확인하고INSERT하는 방식. 동시 실행에서 뚫립니다. - 흔한 실수 2: 정리 배치를 '오래된 것부터 N 건' 으로 만드는 것.
- 흔한 실수 3:
busy_timeout없이 병렬 실행해database is locked로 실패하는 것.
유입이 급증하면 최근 것을 지웁니다.
단계 8개
- 멱등 이력 테이블
- 멱등키 설계 문서
- 적재 스크립트
- 일괄 적재와 중복 집계
- 동시 실행 방어
- 보관 기간 정리
- 저장-후-응답
- 중복 발생 경로 정리