“멱등성” 이란 연산을 반복적으로 적용해도 변하지 않는 성질을 말한다. 컴퓨터 과학에서 “멱등하다” 는 의미는, 첫 번째 수행을 한 뒤 여러 차례 적용해도 결과를 변경시키지 않는 작업 또는 기능의 속성을 말한다.
예를 들어, 어떤 숫자에 1을 곱하는 연산은 여러 번 수행해도 처음 1을 곱한 것과 같은 숫자가 되기 때문에 해당 연산은 멱등하다고 한다. 마찬가지로 숫자의 절댓값을 계산하는 절댓값 함수는 같은 값에 대해 여러 번 수행해도 처음과 항상 같은 숫자가 돌아오기 때문에 멱등 함수라고 부른다.
HTTP 메서드는 캐시 가능 여부(Cacheable), 안전(Safety), 멱등성(Idempotency)이 있다.
HTTP 멱등성이 필요한 이유는 요청의 재시도 때문이다. 만약 HTTP 요청이 멱등하다면, 요청이 실패한 경우에 주저없이 재시도 요청을 하면 된다. 하지만 만약 HTTP 요청이 멱등하지 않다면, 리소스가 이미 처리되었는데 중복 요청을 보낼 수 있다. 예를 들어 이미 결제된 요청인데, 중간에 연결이 끊겨서 다시 결제 요청을 보내서 문제를 일으킬 수 있는 것이다. 그래서 클라이언트는 무지성으로 재시도 요청을 보내면 안되고, 멱등성을 고려하여 재시도 요청을 해야 한다.
그럼 안전 == 멱등인가? HTTP 메서드의 안전성과 멱등성은 어떻게 다를까?
안전성이 보장된 메서드는 리소스를 변경하지 않는다. GET, HEAD, OPTIONS는 안전한 메서드다. 안전성이 보장된 메서드는 멱등성도 보장하지만, 멱등성을 지닌 메서드가 항상 안전성을 보장하지는 않는다. 예를 들어 PUT과 DELETE는 멱등한 메서드지만, 리소스에 변화를 일으키기 때문에 안전한 메서드는 아닌 것이다.
여러 번 실행에도 같은 시스템 상태를 보장하는 멱등성은 HTTP 프로토콜에서 요청의 안전성과 예측 가능성을 보장하는 핵심 속성이다.
| 메서드 | 설명 | Cacheable | Safety | Idempotency |
|---|---|---|---|---|
| CONNECT | 요청된 리소스와 양뱡향 통신을 시작 | X | X | X |
| DELETE | 리소스 삭제 | X | X | O |
| GET | 리소스 조회 | O | O | O |
| HEAD | 콘텐츠 다운로드 없이 자원의 메타데이터 조회 | O | O | O |
| OPTIONS | 대상 리소스에 대한 커뮤니케이션 옵션 설명 | X | O | O |
| POST | 리소스 생성 | O | X | X |
| PUT | 리소스 대체 | X | X | O |
| PATCH | 리소스 부분 업데이트 | O | X | X |
| TRACE | 대상 리소스 경로 테스트하는 ‘메시지 루프백 테스트’ 에 사용 | X | O | O |
…
"멱등성은 응답이 항상 같은 것을 말하는 게 아니라, 시스템의 상태가 불변한 성질" 을 말하는 것이다.
첫 번째 DELETE 요청으로 해당 리소스를 삭제하면, 그 리소스는 더 이상 존재하지 않는다. 두 번째 DELETE 요청을 한다면, 그 리소스는 이미 삭제되었기 때문에 삭제할 리소스가 없다. 계속 DELETE 요청해도 마찬가지다.
DELETE 요청을 여러 번 하면 응답 코드는 다르지만, 서버에서는 리소스가 없는 상태를 유지하고 있기에 DELETE 메서드가 멱등하다고 하는 것이다.
위와 같이 id1 사용자가 이미 존재할 때 해당 사용자의 상태를 교체하는 동작을 3번 수행해도, 리소스의 상태는 변경되지 않는다.
동일한 사용자 user1이 3번 생성된다. 사실은 동일한 사용자이지만 id=1, id=2, id=3인 사용자가 생성되는 것이다. 이처럼 N번 요청하면 N개의 리소스가 서버에 생성되므로 서버의 상태가 계속 변경되는 것이다. 그렇기 때문에 멱등하지 않은 거다.
만약, 장바구니에 물건을 추가한다고 가정해보자.
첫 번째 요청으로 장바구니의 상태는 {item1} 일 것이다. 두 번째 요청으로는 {item1, item1} 이 되고, 세 번째 요청까지 가면, {item1, item1, item1} 이 된다. N번 요청하면 N번의 변화가 생기므로 멱등하지 않은 것이다.
이제 멱등하다는 의미가 어떤 건지 알 것 같다. 그래서 이렇게 멱등하지 않다면 어떤 문제들이 발생한다는 걸까?
온라인으로 제품을 구입해보자.
그런데 만약, 2번 과정에서 일시적인 네트워크 문제로 클라이언트가 응답을 받지 못했다면? 그럼 클라이언트가 구매에 실패했기 때문에 서버에 다시 동일한 POST 요청을 보낼 것이다. 이때 POST 요청이 멱등하지 않기 때문에 서버는 동일한 두 번째 요청을 처리할 것이다. 결국… 클라이언트는 동일한 제품을 2번 구매하게 되고, 카드값에 지출 내역이 2번 찍히게 되는 참사가 발생하는 것이다.

돈으로 연결 시키니까 확 온다. 이처럼 멱등하지 않을 경우, 시스템은 중복 데이터를 생성 하기 때문에 시스템 신뢰성이 떨어진다. 심지어, 저런 중복된 데이터를 처리하기 위해 수동으로 작업을 해야 하기 때문에 운영 및 유지보수 비용도 발생 한다.
위처럼 API 맥락에서 멱등한 연산은 "같은 요청을 여러 번 해도 한 번 한 것과 다름 없는 시스템 상태(State)를 생성하는 것" 이다. 멱등성이 보장되면 시스템이 복원력 있고 신뢰성도 높아진다.

POST 메서드와 같이 멱등하지 않은 HTTP 메서드에 관해서는 멱등성을 개발자가 부여해줘야 한다. 이때 많이 사용되는 방법으로 “멱등키” 를 사용하는 방법이 있다.
<클라이언트 측 멱등키 전송 및 재시도>
public class CancelClient {
private static final RestTemplate restTemplate = new RestTemplate();
public static void main(String[] args) {
String idempotencyKey = UUID.randomUUID().toString();
try {
CancelRequest request = new CancelRequest("ORDER-1234", 100);
CancelResponse response = cancelPayment(idempotencyKey, request);
System.out.println("응답: " + response.getMessage());
} catch (Exception e) {
System.err.println("실패: " + e.getMessage());
}
}
public static CancelResponse cancelPayment(String idempotencyKey, CancelRequest request) throws InterruptedException {
String url = "http://.../cancel-payment";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set("Idempotency-Key", idempotencyKey);
HttpEntity<CancelRequest> entity = new HttpEntity<>(request, headers);
for (int i = 0; i < 3; i++) { // 최대 3번 재시도
try {
ResponseEntity<CancelResponse> response = restTemplate.exchange(
url,
HttpMethod.POST,
entity,
CancelResponse.class
);
return response.getBody();
} catch (ResourceAccessException e) {
System.out.println("요청 타임아웃, 재시도 중...");
Thread.sleep(1000); // 1초 대기 후 재시도
}
}
throw new RuntimeException("최대 재시도 횟수를 초과했습니다.");
}
}
<서버 측 멱등키 처리>
@RestController
public class CancelController {
private final InMemoryIdempotencyStore idempotencyStore;
public CancelController(InMemoryIdempotencyStore store) {
this.idempotencyStore = store;
}
@PostMapping("/cancel-payment")
public ResponseEntity<?> cancelPayment(
@RequestHeader(value = "Idempotency-Key", required = false) String idempotencyKey,
@RequestBody CancelRequest cancelReq) {
// 멱등키가 있고 멱등 응답도 저장되어 있다면 실제 처리하지 않고 저장된 응답을 내보낸다.
if (idempotencyKey != null && idempotencyStore.contains(idempotencyKey)) {
return idempotencyStore.get(idempotencyKey);
}
// 실제 결제 취소 처리
CancelResponse responseBody = new CancelResponse("결제 취소 성공");
ResponseEntity<?> response = ResponseEntity.ok(responseBody);
// 멱등키가 있다면 멱등응답 저장
if (idempotencyKey != null) {
idempotencyStore.save(idempotencyKey, response);
}
return response;
}
}
<정리>
💡 멱등성이란?
멱등성은 같은 요청을 여러 번 보내도 결과적으로 시스템 상태가 변하지 않는 성질이다.
DELETE, PUT은 멱등하지만, POST, PATCH는 기본적으로 멱등하지 않다.
⚠️ 멱등성이 중요한 이유
멱등성이 없는 POST 요청은 중복 처리 문제를 일으킬 수 있다.
시스템에 불필요한 중복 데이터가 생기고, 운영·유지보수 비용이 늘어날 수 있다.
🔐 멱등성을 지키는 실전 방법
멱등하지 않은 POST 요청에 개발자가 직접 멱등성을 부여해야 한다.
가장 널리 사용되는 방법은 “멱등키(Idempotency-Key)”를 사용하는 방식이다.
이처럼 "멱등성은 시스템의 신뢰성과 복원력을 높여주는 필수 속성이다." 특히, 결제처럼 중요한 POST 요청에서 멱등성을 직접 구현하지 않으면, 사용자 피해와 신뢰 저하로 이어질 수 있다. 멱등키를 통해 HTTP 요청에 멱등성을 보장하면, 시스템은 더 예측 가능하고 안전하게 동작할 수 있다.
<참고 자료>
멱등성이 뭔가요?
HTTP 메소드의 멱등성(Idempotence)과 Delete 메소드가 멱등한 이유
RFC 7231 스펙 문서