−새 딕셔너리를 반환해 상태 변환을 눈에 보이게 만든 예제다. 실제 음량 장치를 조작한 코드는 아니며, 두 함수가 순수하게 작성됐다는 점과 둘 중 하나만 멱등이라는 점을 함께 확인할 수 있다. 반대로 실제 장치에 설정값을 쓰는 명령은 부수 효과가 있어도, 관찰하는 음량에 대해서는 멱등일 수 있다.
이 때문에 다음 성질들은 별도로 다룬다.
성질 | 비교하는 것 | 예
결정성 | 같은 입력에 같은 결과가 나오는가 | 정수의 제곱
순수성 | 결과가 입력에만 의존하고 관찰 가능한 부수 효과가 없는가 | 새 값을 계산하는 제곱 함수
멱등성 | 한 번 적용한 뒤 다시 적용해도 같은가 | 절댓값, 음량을 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 | 아니오 | 일반적인 보장은 없음 | 생성·처리의 의미를 자원과 애플리케이션이 정한다
첫 DELETE가 204이고 다음 DELETE가 404여도, 의도한 삭제 효과는 유지될 수 있다. RFC 9110은 요청마다 로그나 이력을 남기는 것까지 금지하지 않으며, 멱등인 요청의 응답도 달라질 수 있다고 설명한다. HTTP에서 비교하는 대상은 요청이 의도한 서버의 효과다. RFC 9110 §9.2.2.
주문 생성에 요청 키를 붙이는 이유
주문을 하나 추가하는 명령은 보통 실행할 때마다 주문을 하나 더 만든다. 이런 API에서는 같은 작업을 다시 보낸 것인지부터 식별하고, 이미 처리한 작업의 효과를 추가로 만들지 않도록 설계한다.
[이미지]
+두 함수는 실제 음량 장치를 조작하는 대신 새 딕셔너리를 반환한다. 둘 다 입력으로부터 새 값을 계산하지만, set_volume만 멱등이다. 실제 장치에 값을 쓰는 명령이라면 부수 효과를 일으키면서도 음량에 대해서는 같은 성질을 가질 수 있다.
성질 | 확인하는 관계 | 예
결정성 | 같은 입력에 같은 결과를 주는가 | 정수의 제곱
순수성 | 결과가 입력에만 의존하고 관찰 가능한 부수 효과가 없는가 | 새 값을 계산하는 제곱 함수
멱등성 | 한 번 적용한 결과에 다시 적용해도 같은가 | 절댓값, 음량을 20으로 설정
대합성(involution) | 두 번 적용하면 원래 입력으로 돌아오는가 | 불리언 반전
불리언을 한 번 반전한 값과 두 번 반전한 값은 다르다. 또 제곱 함수는 0과 1을 유지하지만 2에서는 멱등 법칙을 만족하지 않는다. 이런 구분은 한 번의 출력만 보고 성질을 붙이거나, 몇 개의 입력에서 맞았다는 이유로 전체 입력 범위에 같은 보장을 붙이는 일을 막아준다.
실제 프로그램에서는 관찰하는 상태의 범위도 정한다. 음량과 별도로 버튼을 누른 횟수까지 기록한다면, 음량은 유지돼도 전체 기록은 매번 달라진다. HTTP가 나중에 '요청이 의도한 효과'를 기준으로 삼은 것도 이런 구분과 관련이 있다.
다른 명령과 섞어 실행할 때
멱등성은 같은 연산의 재적용을 설명한다. 서로 다른 연산의 순서는 교환법칙으로 따진다. 음량을 20으로 설정하는 명령과 50으로 설정하는 명령은 각각 멱등이지만, 어느 쪽을 마지막에 실행하느냐에 따라 최종 음량이 달라진다.
여러 멱등 연산을 합쳐도 합성 결과가 항상 멱등인 것은 아니다. 정수 쌍에서 F(x, y) = (x, x), G(x, y) = (y + 1, y)라고 해보자. F와 G는 각각 두 번 적용해도 한 번 적용한 값과 같지만, F 다음 G를 실행하는 한 묶음을 반복하면 다음처럼 바뀐다.
적용 | 상태
처음 | (0, 0)
F 다음 G | (1, 0)
같은 묶음을 다시 실행 | (2, 1)
두 멱등 연산이 서로 교환하기까지 한다면 합성도 멱등이 된다. 합성을 두 번 펼쳤을 때 순서를 바꿔 같은 연산끼리 모으고, 각 연산의 멱등 법칙을 적용할 수 있기 때문이다. 따라서 여러 명령을 포함한 업무 전체를 재시도하려면 그 묶음의 효과도 살펴야 한다.
동시에 다른 사람이 상태를 바꾸는 경우도 마찬가지다. 클라이언트가 음량을 20으로 설정한 뒤 다른 사용자가 50으로 바꿨는데, 앞선 클라이언트가 예전 요청을 다시 보내면 음량은 다시 20이 된다. 같은 설정 명령이 멱등이라는 사실과 다른 사용자의 변경을 보존하는 일은 별개다. 뒤의 문제는 버전 조건이나 동시성 제어로 다룬다.
응답이 오지 않을 때의 재시도
클라이언트가 서버에 주문 요청을 보냈는데 응답이 오지 않는다. 서버가 요청을 받기 전에 연결이 끊겼을 수도 있고, 주문을 저장한 뒤 돌아오는 응답만 사라졌을 수도 있다. 클라이언트가 같은 요청을 다시 보내면 앞의 경우에는 주문을 만들 수 있지만, 뒤의 경우에도 서버가 새 주문을 만들면 주문이 두 개가 된다.
Birrell과 Nelson은 1984년 논문 《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도 이 구분을 이어간다. 아래 표는 메서드가 정한 의미이며, 서버 구현자는 그 효과를 반복 요청에서도 유지하도록 코드를 작성한다. RFC 7231 §4.2.
메서드 | 안전한가 | 멱등인가 | 구분의 이유
GET·HEAD | 예 | 예 | 클라이언트가 조회를 요청한다
PUT | 아니오 | 예 | 대상 자원의 상태를 지정한다
DELETE | 아니오 | 예 | 대상과의 연결을 제거한다
POST | 아니오 | 일반적인 보장은 없음 | 자원과 애플리케이션이 생성·처리의 의미를 정한다
PATCH | 아니오 | 일반적인 보장은 없음 | 적용할 변경의 내용과 형식에 따라 반복 효과가 달라진다
2010년 RFC 5789는 PATCH를 부분 변경용 메서드로 정의하면서, 특정 PATCH 요청은 멱등하게 구성할 수 있다고 설명했다. 값을 20으로 지정하는 변경과 현재 값에 1을 더하는 변경은 같은 '부분 수정'이어도 반복 효과가 다르다. 기준 버전에 의존하는 변경에는 강한 ETag와 If-Match 같은 조건을 권고한다. RFC 5789 §2.
첫 DELETE가 204를, 다음 DELETE가 404를 반환해도 서버는 같은 삭제 효과를 유지할 수 있다. RFC 9110은 요청마다 로그나 이력을 남길 수 있고 응답도 달라질 수 있다고 설명한다. 클라이언트가 비교해야 할 것은 요청이 의도한 서버의 효과다. RFC 9110 §9.2.2.
서버에서 같은 자원을 조회하는 동안에도 다른 사용자가 내용을 바꾸면 GET 응답은 달라질 수 있다. 응답을 저장해 재사용해도 되는지는 캐시 가능성과 신선도 규칙으로 따로 정한다. 안전성, 멱등성, 캐시 가능성을 나눠 적는 까닭이다. RFC 9110 §9.2.1·§9.2.3.
같은 주문을 구별하는 요청 키
클라이언트가 주문을 하나 추가하라고 보낼 때마다 서버가 새 주문을 만든다면, 재시도에도 주문이 늘어난다. 이를 피하려면 클라이언트와 서버가 어떤 요청들을 같은 작업으로 볼지 먼저 정한다.
[이미지]