RESTful API와 멱등성, 낙관적 락

양성준·2025년 3월 31일

스프링

목록 보기
23/49

멱등성

"같은 요청을 여러 번 반복해도 결과는 한 번만 수행된 것과 같아야 한다."

PUT /products/123/stock
{
  "stockQuantity": 10
}
  • 이 요청은 몇 번을 보내든 stockQuantity는 항상 10으로 유지된다.
  • 이처럼 같은 요청을 여러 번 보내도 시스템의 상태가 바뀌지 않으면 → 그 API는 멱등하다고 할 수 있다.

RESTful API에서 멱등성

  • REST에서는 각 HTTP 메서드가 갖는 멱등성 여부를 명확하게 정의하고 있다.
    • GET : 조회만 수행. 몇 번 요청해도 동일한 응답
    • POST : 새로운 리소스 생성. 매번 다른 결과 발생
    • PUT : 리소스를 "설정"함. 같은 데이터로 여러 번 보내도 결과 동일
    • DELETE : 삭제는 여러 번 해도 동일한 상태 유지

=> 이 중에서도 특히 PUT 메서드는 멱등성의 대표적인 예라고 할 수 있다.

Patch?

  • 실무에서는 보통 Patch를 사용하지 않고, 대신 Put 메서드를 Patch처럼 사용
  • 수정하려는 내용만 받은 뒤, null이 아닌 경우만 업데이트하는 식으로 사용한다.

PUT 메서드에서 멱등성이 중요한 이유

재고 관리

PUT /products/123/stock
{
  "stockQuantity": 6
}
  • 이 요청을 몇 번 보내든 재고 수량은 바뀌지 않으며, 같은 상태를 유지한다.
    → 따라서 이 API는 멱등성을 지키는 API입니다.
  • 이러한 특성은 요청이 중복되었을 때 서버의 상태가 꼬이지 않도록 보장해준다.
    • 클라이언트가 타임아웃으로 재요청했는데,
    • 서버가 두 번 처리하지 않고 한 번만 적용되도록 할 수 있음
  • 예를 들어, 만약 재고 감소 처리를 현재 값에서 -1을 해주는 식으로 처리한다면,
    request만 들어가고 response가 전달이 안 된 경우, 클라이언트가 request를 한 번 더 보냈을 때,
    -1을 하라는 요청이 두 번 들어가 결과적으로 -2가 되는 경우가 발생한다.

    => 현재값 -1이 아닌, stockQuantity: 6 이렇게 멱등성을 지켜서 PUT 메서드를 사용하면 이런 일을 방지할 수 있다.

번외: 재고 관리에서 낙관적 락(Optimistic Lock)이 중요한 이유

  • 재고 관리는 단순히 숫자만 변경하는 작업이 아니기 때문에,
    판매자 측 재고 수정 요청과 실제 구매 요청이 동시에 발생할 수 있는 경쟁 상황이 자주 발생한다.

=> 이런 일을 방지하기 위해, 낙관적 락(Optimistic Lock)을 활용할 수 있다!

낙관적 락이란?

  • 각 재고 데이터에 version 값을 함께 저장하고, 클라이언트는 수정 요청 시 version도 함께 전달!
  • 서버는 version이 일치할 때만 업데이트를 수행하고, 누군가 먼저 수정한 경우에는 409 Conflict 오류를 반환.
PUT /products/123/stock
{
  "stockQuantity": 6,
  "version": 1
}

충돌 응답:

{
  "status": 409,
  "message": "다른 사용자가 이미 이 상품을 수정했습니다.",
  "currentVersion": 2
}
  • 낙관적 락을 통해 클라이언트는 최신 데이터를 다시 받아와 재시도할 수 있고,
    중요한 재고 변경 로직의 정합성과 안정성을 유지할 수 있다.

  • 오른쪽이 먼저 실행되어, 재고의 버전이 V2로 바뀌었다면, 왼쪽에서는 conflict가 일어남

  • 그럼 다시 최신 V2값을 가져와서 그걸 기준으로 다시 재고 업데이트!
    (Version의 경우 어차피 하나만 허용하므로 멱등성을 지키지 않고 +1해줘도 됨)

  • 클라이언트 쪽에서는 250개에서 20개를 살거니까, 재고의 최신값을 서버에서 불러와서 -20을 해서 230을 요청으로 보내주는 것 (-20을 요청하는게 아닌, 230개를 요청!)

비관적 락 vs 비관적 락**

비관적 락 (Pessimistic Lock)

  • “이거 충돌 날 수도 있으니까, 아예 막아놓고 나 혼자 쓸게.”
  • 리소스를 사용하는 동안 다른 트랜잭션이 접근하지 못하게 막음
  • DB 수준에서는 SELECT ... FOR UPDATE 같은 방식으로 구현
  • 특징
  • 충돌(데이터 경합)이 자주 발생하는 환경에 적합
  • 동시성은 떨어지지만, 데이터 무결성은 확실하게 보장
  • 병목이나 대기시간이 생길 수 있음 (줄 세우기 방식)

낙관적 락 (Optimistic Lock)

  • “충돌은 잘 안 날 거야. 일단 처리하고, 나중에 검증하자.”
  • 트랜잭션을 마치기 직전에 버전 검사 등을 통해 충돌을 감지
  • 보통 version 필드를 통해 동시성 제어
  • 특징
    • 충돌이 드물게 발생하는 환경에 적합
    • 락을 걸지 않아서 성능은 좋지만, 충돌 시 재시도 필요
    • 실무에서는 JPA @Version 등으로 사용

결론

  • 충돌 가능성이 높다면 → 비관적 락
  • 충돌 가능성이 낮다면 → 낙관적 락
    → 트래픽/동시성/업데이트 빈도 등을 고려해 선택해야 함!
profile
백엔드 개발자

0개의 댓글