lifecarelog
백엔드

승인 버튼을 두 번 눌러도 안전한 이유: CAS와 멱등키

승인 버튼을 두 번 눌러도 중복 처리되지 않게 막는 백엔드 패턴 — 멱등키(nonce)로 같은 요청을, CAS로 같은 상태 변경을 한 번만 처리하는 방법을 정리했어요.

5분 읽기

승인 버튼을 두 번 눌러도 안전한 이유: CAS와 멱등키

느린 네트워크에서 "승인" 버튼을 눌렀는데 반응이 없어서 한 번 더 누른 적, 혹시 없나요? 사용자 입장에선 당연한 행동인데, 서버 입장에선 같은 승인이 두 번 처리될 수 있는 위험한 순간이에요.

문제는 버튼이 아니라 서버예요

플랜비(plan-b)에서 회비 납부를 승인하는 버튼을 만들면서 이 문제를 직접 겪었어요. 응답이 오기 전 0.5초 사이에 한 번 더 누르면, 회비가 두 번 차감되거나 승인 알림이 중복으로 나가더라고요.

버튼을 비활성화하는 방법도 있지만, 그건 화면 한 곳만 막을 뿐이에요. 새로고침 후 재요청, 모바일 앱의 자동 재시도, 네트워크 타임아웃 재전송까지 막으려면 결국 서버에서 "이미 처리한 요청"을 걸러내야 해요.

같은 요청 도착멱등키 확인이미 있으면 이전 결과 반환없으면 CAS로 상태 변경

첫 번째 방어선: 멱등키(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로 상태 이중 변경 차단
  • 둘 다 한 트랜잭션 안에서

혼자 운영하면서 배운 건, 이런 안전장치는 화려하지 않지만 없으면 나중에 데이터를 손으로 고치게 된다는 거예요. 승인·결제·정산처럼 한 번만 일어나야 하는 일에는, 버튼을 잠그기 전에 서버가 먼저 두 번을 견디게 만들어 두면 마음이 편해져요.

#멱등성#CAS#동시성 제어#백엔드

라이프케어로그 서비스가 궁금하신가요?

AI 기반 건강·일정·재활 관리 앱을 직접 써보세요.

서비스 살펴보기

관련 글

댓글

아직 댓글이 없어요. 첫 댓글을 남겨주세요.