멱등성
Idempotency
개념
같은 요청이 두 번 세 번 도착해도 결과가 한 번 도착했을 때와 같아지는 성질로, 재시도가 일어나도 처리가 중복되지 않게 합니다.
등장 배경
결제 버튼을 눌렀는데 화면이 3초째 돌기만 합니다. 사용자는 눌리지 않았다고 판단하고 한 번 더 누르고, 잠시 뒤 카드값이 두 번 빠져나갑니다. 페이지덱 초기에 수아에게 들어온 결제 문의의 절반이 이랬습니다. 돈은 두 번 나갔는데 받은 플랜은 하나뿐이니 고객에게는 실수로 보이지 않았습니다.
처음 나온 처방은 화면 쪽이었습니다. 버튼을 누르는 순간 회색으로 바꿔 두 번 눌리지 않게 막았지만, 두 번 누른 적이 없다는 고객이 계속 나왔습니다.
원인은 사람이 아니라 통신에 있었습니다. 요청이 서버에 도착해 처리까지 끝났는데 돌아오는 응답만 중간에서 끊기는 일이 생깁니다. 보낸 쪽에서 보면 이 상황과 요청이 아예 도착하지 못한 상황이 구분되지 않습니다. 둘 다 답이 없기 때문입니다. 그래서 통신을 다루는 코드는 대부분 답이 없으면 한 번 더 보내도록 만들어져 있고, 앱이 사용자 몰래 같은 요청을 두 번 보냅니다.
고를 수 있는 길은 둘뿐입니다. 재시도를 없애거나, 재시도가 와도 괜찮은 서버를 만들거나. 앞쪽은 안 됩니다. 멱등성은 뒤쪽을 택한 결과입니다. 같은 요청이 몇 번 오든 처음 한 번만 실제로 처리되고 나머지는 처음의 결과를 그대로 돌려받게 서버를 짜 둡니다.
작동 방식
요청마다 고유한 키를 하나 붙여 보내고, 서버가 그 키를 이미 처리한 적 있는지 먼저 확인하는 순서로 작동합니다. 이 키를 멱등 키라고 부릅니다.
절차는 다섯 단계입니다.
- 보내는 쪽이 시도 하나당 키를 하나 만듭니다. 결제를 시작할 때 무작위 문자열을 하나 만들어 두고, 답이 없어 다시 보낼 때도 그 값을 그대로 씁니다.
- 서버는 처리에 앞서 키를 저장소에 적습니다. 데이터를 저장하고 꺼내 쓰게 해 주는 시스템인 데이터베이스에 이 키를 중복을 허용하지 않는 값으로 두면, 같은 키를 두 번 적으려는 시도는 저장 단계에서 막힙니다.
- 이미 있는 키면 실제 처리를 건너뛰고 첫 요청 때 돌려준 응답을 그대로 내보냅니다. 카드는 긁히지 않고 사용자는 성공 화면을 봅니다.
- 처음 보는 키면 실제로 처리하고 결과를 키와 함께 저장합니다. 이때 처리와 저장은 트랜잭션으로 묶습니다. 전부 성공하거나 전부 취소되는 작업 묶음이라, 결제는 됐는데 키 기록만 빠지는 상태가 생기지 않습니다.
- 키는 기한을 두고 지웁니다. 보통 24시간입니다. 재시도는 길어야 몇 분 안에 끝나므로 그보다 오래 들고 있을 이유가 없습니다.
키를 따로 붙이지 않아도 이미 멱등한 처리가 있습니다. 가르는 기준은 지정이냐 증감이냐입니다. "이 계정의 플랜을 프로로 바꾼다" 는 백 번 실행해도 결과가 프로 하나입니다. "이 계정의 잔여 크레딧에서 10을 뺀다" 는 실행할 때마다 값이 달라집니다.
그래서 프로그램끼리 주고받기로 약속한 창구인 API를 설계할 때 어느 쪽 모양으로 만들지를 먼저 정합니다. 값을 얼마로 지정하는 모양이면 키 없이도 안전하고, 얼마만큼 움직이라고 지시하는 모양이면 키가 필요합니다.
쓰이는 곳
재시도가 일어날 수 있는 자리 전부에 필요하지만, 빠뜨렸을 때 실제로 사고가 나는 자리는 넷입니다.
- 결제와 주문 — 앞에서 본 그대로입니다.
- 웹훅 — 사건이 생기면 상대 서버로 먼저 보내 주는 알림입니다. 결제사가 "이 결제가 승인됐습니다" 를 페이지덱 서버로 밀어 넣는 식입니다. 보내는 쪽은 잘 받았다는 신호가 올 때까지 몇 번이고 다시 보내도록 만들어져 있어서, 같은 알림이 서너 번 도착하는 일이 드물지 않습니다.
- 메시지 큐 — 작업을 줄 세워 나중에 처리하게 하는 장치입니다. 대부분의 큐는 한 건이 적어도 한 번 전달되는 것까지만 보장하고 딱 한 번은 보장하지 않습니다.
- 백그라운드 작업 — 응답과 분리해 뒤에서 도는 작업입니다. 실패한 작업을 자동으로 다시 돌리는 장치가 대개 함께 달려 있어, 가입 환영 메일이 세 통 나가기 쉽습니다.
멱등성과 붙어 다니는 말이 동시성입니다. 여러 작업을 겹쳐 진행하는 처리 방식이라, 같은 키를 든 두 요청이 0.1초 차이로 도착할 수 있습니다. 둘 다 키가 없다고 판단하고 둘 다 결제를 진행하면 키를 붙인 보람이 없습니다. 이렇게 순서에 따라 결과가 달라지는 고장이 경쟁 상태입니다. 키 확인과 키 기록은 코드에서 따로 하지 않고 저장소에 한 번에 맡깁니다.
같은 성질을 데이터 흐름 전체에 적용한 것이 재실행 안전성입니다.
실무 용례
"이 엔드포인트 멱등해요? 여기에 자동 재시도 붙일 건데요."
같은 요청이 두 번 가도 괜찮은지 먼저 확인하는 말입니다.
"웹훅이 중복으로 들어와서 알림이 두 번 나갔어요. 이벤트 아이디로 걸러야 할 것 같습니다."
받는 쪽에 멱등 처리가 빠져 있다는 보고입니다.
"결제 요청에는 멱등 키 붙여 주세요. 서버가 아니라 클라이언트에서 만들어 보내야 합니다."
키를 만드는 자리를 지정하는 말입니다. 서버가 만들면 요청마다 새 키가 생겨 아무것도 걸러지지 않습니다.
페이지덱에서 이 말이 가장 자주 나오는 자리는 코드 리뷰입니다. 태오가 결제나 알림을 건드린 코드를 올리면 리뷰어가 가장 먼저 찾는 것이 키를 어디서 만들어 어디에 기록하는가입니다. 이 질문에 답이 없으면 기능이 아무리 잘 돌아도 머지되지 않습니다.
흔한 오해
중복 요청을 막는 장치로 읽는 경우가 가장 많습니다. 이름이 중복 방지처럼 들리니 그렇게 읽기 쉽습니다. 하지만 이 성질은 요청을 막지 않습니다. 중복이 와도 상관없게 만드는 쪽입니다. 요청을 막는 일은 버튼을 회색으로 바꾸는 화면 쪽 처방이고, 걸러지는 건 사람의 연타뿐입니다. 둘을 같은 것으로 보면 버튼만 고쳐 놓고 해결됐다고 판단하게 되고, 통신이 불안한 곳에서 결제한 고객의 중복 청구만 남아 재현되지 않는 문의로 돌아옵니다.
돈이 걸린 자리에만 필요하다고 보는 것도 흔합니다. 사고가 눈에 띄는 곳이 결제라서 그렇습니다. 그러나 재시도는 통신이 오가는 모든 자리에 있어서, 초대 메일이 세 통 나가는 것도 같은 고장입니다. 결제만 막아 두면 사용자는 받은 편지함에서 이 제품의 인상을 정하게 됩니다.
같이 보면 좋은 페이지
마지막 업데이트 2026-09-16