승인 버튼을 두 번 눌러도 안전한 이유: CAS와 멱등키
승인 버튼을 두 번 눌러도 중복 처리되지 않게 막는 백엔드 패턴 — 멱등키(nonce)로 같은 요청을, CAS로 같은 상태 변경을 한 번만 처리하는 방법을 정리했어요.
승인 버튼을 두 번 눌러도 안전한 이유: CAS와 멱등키
느린 네트워크에서 "승인" 버튼을 눌렀는데 반응이 없어서 한 번 더 누른 적, 혹시 없나요? 사용자 입장에선 당연한 행동인데, 서버 입장에선 같은 승인이 두 번 처리될 수 있는 위험한 순간이에요.
문제는 버튼이 아니라 서버예요
플랜비(plan-b)에서 회비 납부를 승인하는 버튼을 만들면서 이 문제를 직접 겪었어요. 응답이 오기 전 0.5초 사이에 한 번 더 누르면, 회비가 두 번 차감되거나 승인 알림이 중복으로 나가더라고요.
버튼을 비활성화하는 방법도 있지만, 그건 화면 한 곳만 막을 뿐이에요. 새로고침 후 재요청, 모바일 앱의 자동 재시도, 네트워크 타임아웃 재전송까지 막으려면 결국 서버에서 "이미 처리한 요청"을 걸러내야 해요.
첫 번째 방어선: 멱등키(nonce)
클라이언트가 요청마다 한 번만 쓰는 고유 키를 함께 보내요. 서버는 이 키를 유니크 제약이 걸린 테이블에 저장하고요. 같은 키가 또 들어오면 삽입이 실패하니까, 그때는 새로 처리하지 말고 저장해 둔 이전 결과를 그대로 돌려줘요.
직접 돌려 보니 핵심은 저장과 처리를 한 트랜잭션으로 묶는 거였어요. 키만 먼저 넣고 처리를 나중에 하면, 그 사이 두 번째 요청이 끼어들 틈이 생기거든요.
두 번째 방어선: CAS로 상태를 딱 한 번만 바꾸기
CAS(Compare-And-Swap)는 "지금 상태가 내가 아는 그 상태일 때만 바꿔줘"라는 조건부 갱신이에요. SQL로 치면 이런 식이에요.
UPDATE payments SET status = 'approved' WHERE id = ? AND status = 'pending'
승인 대기 상태일 때만 바뀌니까, 두 번째 요청이 와도 이미 approved라서 조건에 안 맞아요. 여기서 중요한 건 반환값을 확인하는 거예요. 바뀐 행이 1이면 내가 승인한 거고, 0이면 이미 처리된 거예요. 테스트해 보면 이 affected rows 하나로 중복을 깔끔하게 갈라낼 수 있었어요.
두 개를 같이 쓰면
멱등키는 같은 요청을 막고, CAS는 같은 상태 변경을 막아요. 역할이 달라서 겹치는 게 아니라 층이 달라요. 멱등키가 재시도 3회를 걸러내고, 혹시 새어 나온 동시 요청은 CAS가 상태 단계에서 한 번 더 잡아주는 식이죠.
- 멱등키로 중복 요청 차단
- CAS로 상태 이중 변경 차단
- 둘 다 한 트랜잭션 안에서
혼자 운영하면서 배운 건, 이런 안전장치는 화려하지 않지만 없으면 나중에 데이터를 손으로 고치게 된다는 거예요. 승인·결제·정산처럼 한 번만 일어나야 하는 일에는, 버튼을 잠그기 전에 서버가 먼저 두 번을 견디게 만들어 두면 마음이 편해져요.
관련 글
댓글
아직 댓글이 없어요. 첫 댓글을 남겨주세요.