멱등성
음량을 20으로 맞추는 버튼과 음량을 1 올리는 버튼은 반복해서 누를 때 차이가 드러난다. 앞의 버튼은 처음 20에 도달한 뒤에는 음량을 더 바꾸지 않고, 뒤의 버튼은 누를 때마다 값을 올린다. 멱등성(idempotence, API 문맥에서는 idempotency)은 이처럼 같은 연산을 거듭 적용했을 때 한 번 적용한 결과가 유지되는 성질이다.
서버 개발에서는 중복 결제를 막거나 요청 키를 저장하는 이야기로 자주 만나는데, 개념의 출발점은 수학에 있다. 수학에서는 연산을 반복했을 때의 관계를, HTTP에서는 요청이 의도한 효과를, 업무 API에서는 같은 작업을 식별하고 재처리하는 계약을 다룬다. 서로 이어지는 이야기지만 무엇을 반복하고 무엇을 같다고 보는지부터 구별해야 한다.
거듭제곱에서 가져온 이름
Benjamin Peirce의 《Linear Associative Algebra》 §25에는 idempotent라는 말과 함께, 별도 언급이 없으면 A² = A인 경우를 가리킨다는 설명이 나온다. 함께 정의한 nilpotent는 거듭제곱하면 0이 되는 대상을 가리킨다. 반복 연산이 어떤 결과를 남기는지에 따라 대수의 대상을 구분한 것이다. 여기서의 Peirce는 철학자로도 알려진 Charles Sanders Peirce의 아버지 Benjamin Peirce다.

(원문 출처)
캡처는 1882년판의 본문 8쪽, PDF 20쪽이다. 같은 판본의 서문은 석판 인쇄본이 1870년에 배포됐다고 밝힌다. 따라서 용어의 이력을 설명할 때는 1870년의 배포와 지금 보고 있는 1882년판을 구별하는 편이 정확하다. 원문은 더 높은 거듭제곱도 언급한 뒤 제곱의 경우를 기본으로 정한다. 이 발췌는 공개 저작권이 만료된 원문 스캔이다. 판본 정보, 서문.
이 성질을 함수의 합성으로 읽으면, 같은 집합에서 같은 집합으로 가는 함수 f에 대해 모든 입력 x가 f(f(x)) = f(x)를 만족하는 경우가 된다. 한 번 처리한 결과를 다시 입력해도 그대로라는 뜻이다. 정수의 절댓값을 구하면 이미 음수가 사라졌으니 다시 절댓값을 구해도 같고, 제곱은 2를 4로, 다시 16으로 바꾸므로 멱등이 아니다.
def square(x):
return x * x
assert abs(abs(-3)) == abs(-3)
assert square(square(2)) != square(2)
print(abs(-3), square(2), square(square(2))) # 3 4 16Python 3에서 그대로 실행할 수 있다. square(2)를 여러 번 호출하면 매번 4가 나온다는 사실은 결정성을 보여주고, 위에서 비교한 것은 그 결과에 다시 square를 적용하는 합성이다. 같은 입력의 반복 호출과 연산 결과에 대한 재적용을 구별하면, 순수 함수와 멱등 함수를 섞어 설명하는 일을 피할 수 있다.
집합의 합집합 A ∪ A = A처럼 이항 연산에도 같은 말을 쓴다. 이때는 두 피연산자를 같게 두었을 때의 법칙을 보는 것이며, 함수 합성과는 연산의 자리가 다르다. 공통점은 반복 때문에 결과가 더 늘어나지 않는다는 관계다.
프로그램에서는 상태에 연산을 적용한다
프로그램의 명령을 볼 때는 함수의 반환값보다 상태의 변화를 먼저 살펴야 한다. 상태 s에 같은 명령 T를 적용했을 때 T(T(s)) = T(s)가 성립하는지 보는 것이다. 앞의 음량 설정이 여기에 해당한다.

두 줄은 같은 초기 음량 5에서 출발하며, 각 줄에서는 동일한 명령을 두 번 적용한다. 그림은 상태 변환의 차이를 설명하기 위해 직접 그렸다.
def set_volume(state):
return {**state, "volume": 20}
def increase_volume(state):
return {**state, "volume": state["volume"] + 1}
initial = {"volume": 5}
assert set_volume(set_volume(initial)) == set_volume(initial)
assert increase_volume(increase_volume(initial)) != increase_volume(initial)
print(set_volume(initial), increase_volume(increase_volume(initial)))새 딕셔너리를 반환해 상태 변환을 눈에 보이게 만든 예제다. 실제 음량 장치를 조작한 코드는 아니며, 두 함수가 순수하게 작성됐다는 점과 둘 중 하나만 멱등이라는 점을 함께 확인할 수 있다. 반대로 실제 장치에 설정값을 쓰는 명령은 부수 효과가 있어도, 관찰하는 음량에 대해서는 멱등일 수 있다.
이 때문에 다음 성질들은 별도로 다룬다.
성질 | 비교하는 것 | 예 |
|---|---|---|
결정성 | 같은 입력에 같은 결과가 나오는가 | 정수의 제곱 |
순수성 | 결과가 입력에만 의존하고 관찰 가능한 부수 효과가 없는가 | 새 값을 계산하는 제곱 함수 |
멱등성 | 한 번 적용한 뒤 다시 적용해도 같은가 | 절댓값, 음량을 20으로 설정 |
대합성(involution) | 두 번 적용하면 원래 입력으로 돌아오는가 | 불리언 반전 |
- 성질
결정성
- 비교하는 것
같은 입력에 같은 결과가 나오는가
- 예
정수의 제곱
- 성질
순수성
- 비교하는 것
결과가 입력에만 의존하고 관찰 가능한 부수 효과가 없는가
- 예
새 값을 계산하는 제곱 함수
- 성질
멱등성
- 비교하는 것
한 번 적용한 뒤 다시 적용해도 같은가
- 예
절댓값, 음량을 20으로 설정
- 성질
대합성(involution)
- 비교하는 것
두 번 적용하면 원래 입력으로 돌아오는가
- 예
불리언 반전
불리언을 두 번 반전하면 원래 값으로 돌아오지만, 한 번 반전한 결과와 두 번 반전한 결과는 다르다. 또 어떤 연산이 특정 입력 하나에서 우연히 값을 유지했다고 해서 전체 입력 범위에서 멱등인 것도 아니다. 제곱 함수도 0과 1에서는 값을 유지한다.
통신이 끊기면 반복의 의미가 달라진다
원격 호출에서는 호출자가 같은 명령을 다시 보낼 이유가 생긴다. 서버가 주문을 저장한 다음 응답이 사라지면, 호출자는 요청이 도착하지 않은 경우와 처리됐지만 응답만 잃은 경우를 구별하기 어렵다. 재전송은 통신 실패를 견디게 해주지만, 주문 추가 같은 효과까지 반복되면 별개의 문제가 된다.
1984년 Birrell과 Nelson의 논문 《Implementing Remote Procedure Calls》는 §3.2에서 호출 식별자와 순서 번호를 사용해 중복 호출 패킷을 구별하는 방식을 설명한다. 이미 이 시기의 RPC 설계에서 재전송과 중복 실행은 따로 다뤄야 할 문제였다. 다만 이 논문을 멱등성의 발명으로 볼 근거는 없고, 수학의 성질과 통신 프로토콜의 중복 억제 기법이 만나는 사례로 읽으면 된다. 논문과 서지, 논문 PDF.
1997년의 HTTP/1.1 RFC 2068은 §9.1에서 안전한 메서드와 멱등인 메서드를 나눴다. 사용자가 읽기를 요청하는지, 같은 요청을 다시 보내도 효과가 누적되지 않는지를 구분한 것이다. 이 구분은 연결이 끊겼을 때 클라이언트가 어떤 요청을 재시도할 수 있는지 판단하는 데 쓰였다. RFC 2068 §8.1.4·§9.1.
이후 2014년 RFC 7231과 2022년 RFC 9110에서도 이 구분을 이어간다. 다음 표는 HTTP 메서드가 약속하는 의미이며, 메서드 이름만 붙이면 서버 구현이 자동으로 그 약속을 지키게 된다는 뜻은 아니다. RFC 7231 §4.2.
메서드 | 안전한가 | 멱등인가 | 구분의 이유 |
|---|---|---|---|
GET·HEAD | 예 | 예 | 요청의 의미가 조회다 |
PUT | 아니오 | 예 | 대상 자원의 상태를 지정하며 반복 효과가 누적되지 않는다 |
DELETE | 아니오 | 예 | 대상과의 연결을 제거하며 같은 삭제를 반복해도 삭제 효과가 늘지 않는다 |
POST | 아니오 | 일반적인 보장은 없음 | 생성·처리의 의미를 자원과 애플리케이션이 정한다 |
- 메서드
GET·HEAD
- 안전한가
예
- 멱등인가
예
- 구분의 이유
요청의 의미가 조회다
- 메서드
PUT
- 안전한가
아니오
- 멱등인가
예
- 구분의 이유
대상 자원의 상태를 지정하며 반복 효과가 누적되지 않는다
- 메서드
DELETE
- 안전한가
아니오
- 멱등인가
예
- 구분의 이유
대상과의 연결을 제거하며 같은 삭제를 반복해도 삭제 효과가 늘지 않는다
- 메서드
POST
- 안전한가
아니오
- 멱등인가
일반적인 보장은 없음
- 구분의 이유
생성·처리의 의미를 자원과 애플리케이션이 정한다
첫 DELETE가 204이고 다음 DELETE가 404여도, 의도한 삭제 효과는 유지될 수 있다. RFC 9110은 요청마다 로그나 이력을 남기는 것까지 금지하지 않으며, 멱등인 요청의 응답도 달라질 수 있다고 설명한다. HTTP에서 비교하는 대상은 요청이 의도한 서버의 효과다. RFC 9110 §9.2.2.
주문 생성에 요청 키를 붙이는 이유
주문을 하나 추가하는 명령은 보통 실행할 때마다 주문을 하나 더 만든다. 이런 API에서는 같은 작업을 다시 보낸 것인지부터 식별하고, 이미 처리한 작업의 효과를 추가로 만들지 않도록 설계한다.

(원문 출처)
Malcolm Featonby의 《Making retries safe with idempotent APIs》 PDF 4쪽·본문 3쪽 도해다. 응답을 잃은 호출자가 같은 식별자를 유지해 재요청하는 API 계약을 보여준다. 이 그림은 요청 중복 처리의 예이며, 대수에서 출발한 멱등성의 모든 쓰임을 나타내지는 않는다. © Amazon Web Services.
이 글이 요청 본문을 해시하는 방식보다 호출자가 정한 식별자를 중요하게 다루는 이유도 여기에 있다. 같은 상품을 같은 수량으로 두 번 주문할 수 있으니, 본문이 같다는 사실만으로 같은 의도라고 결정하기 어렵다. 호출자와 요청 키의 범위, 같은 키에 다른 내용을 보냈을 때의 처리, 기록 보존 기간을 정해야 같은 작업의 뜻이 유지된다. AWS 원문.
여기에는 두 선택이 있다. 연산 자체를 원하는 상태를 지정하는 형태로 만들 수도 있고, 추가 연산을 유지하면서 같은 요청의 재처리를 기록으로 제어할 수도 있다. 후자는 기록과 실제 효과를 원자적으로 다뤄야 하며, 외부 결제처럼 다른 시스템에서 일어나는 효과까지 로컬 DB 트랜잭션 하나로 묶을 수는 없다. 응답을 재사용할지까지 정하는 것도 이 API의 계약에 속한다.
멱등성은 효과를 반복해도 유지되는 성질이고, 중복 제거는 이미 본 작업을 알아내는 기법이다. 처리 기록이 유지되는 범위에서는 후자를 이용해 전자를 제공할 수 있지만, 기록이 사라진 뒤의 재요청이나 다른 시스템의 효과에는 별도의 근거가 필요하다. '정확히 한 번'이라는 표현을 쓸 때도 전달, 실행, 업무 효과 중 무엇을 보장하는지 밝혀야 한다.
같은 이름 아래 남아 있는 구분
문맥 | 반복하는 대상 | 같다고 보는 결과 |
|---|---|---|
대수 | 같은 원소의 곱 | 원소 자체 |
함수 합성 | 앞선 함수의 결과에 같은 함수를 적용 | 함수값 |
상태를 바꾸는 프로그램 | 같은 명령을 같은 조건에서 재적용 | 관찰하기로 한 상태 |
HTTP | 같은 요청의 재전송 | 요청이 의도한 서버의 효과 |
요청 키를 쓰는 API | 같은 작업 식별자의 재처리 | 계약에서 정한 업무 효과와 재응답 |
- 문맥
대수
- 반복하는 대상
같은 원소의 곱
- 같다고 보는 결과
원소 자체
- 문맥
함수 합성
- 반복하는 대상
앞선 함수의 결과에 같은 함수를 적용
- 같다고 보는 결과
함수값
- 문맥
상태를 바꾸는 프로그램
- 반복하는 대상
같은 명령을 같은 조건에서 재적용
- 같다고 보는 결과
관찰하기로 한 상태
- 문맥
HTTP
- 반복하는 대상
같은 요청의 재전송
- 같다고 보는 결과
요청이 의도한 서버의 효과
- 문맥
요청 키를 쓰는 API
- 반복하는 대상
같은 작업 식별자의 재처리
- 같다고 보는 결과
계약에서 정한 업무 효과와 재응답
수학에서의 법칙이 통신과 업무 처리에서 쓰이면서, 같음을 판단하는 범위와 그 성질을 제공하는 방법이 함께 중요해졌다. 반복할 작업과 관찰할 효과의 범위를 밝혀야 어떤 보장을 하는지도 분명해진다. 주문 중복 방지는 그 가운데 한 적용 사례이고, 요청 키와 결과 저장은 그 성질을 제공하기 위해 선택한 구현이다.