사전 멱등성
개념

멱등성

gabury1

같은 일을 한 번 하든 여러 번 하든 남는 결과가 같은 성질입니다. 그래서 응답을 못 받았을 때 같은 요청을 다시 보낼 수 있습니다. 다시 보내도 값이 두 번 쌓이지 않습니다.

상세

엘리베이터 버튼을 다섯 번 눌러도 엘리베이터는 한 번 누른 것과 같이 옵니다. 누른 횟수는 도착하는 엘리베이터 수를 바꾸지 않습니다.

멱등성은 같은 연산을 한 번 적용하든 여러 번 적용하든 남는 상태가 같아지는 성질입니다. 수학에서 온 말입니다. 어떤 함수를 두 번 겹쳐 적용한 결과가 한 번 적용한 결과와 같으면 그 함수를 멱등이라고 부릅니다. 성질은 연산 자체에 붙습니다. 여러 번 부르지 않게 조심하는 것과는 다릅니다.

멱등한 연산은 결과를 못 박는 꼴입니다. 이 자원을 이 값으로 만들어라, 이 항목을 없애라 같은 것입니다. 멱등하지 않은 연산은 지금 값을 딛고 움직이는 꼴입니다. 지금 값에서 얼마를 빼라, 항목을 하나 더 만들어라 같은 것입니다. 앞쪽은 되풀이해도 도착하는 자리가 같습니다. 뒤쪽은 되풀이한 횟수만큼 자리가 옮겨갑니다.

배경

네트워크 너머로 요청을 보냅니다. 응답을 읽기 전에 연결이 끊기는 일이 생깁니다. 그러면 요청이 상대에게 닿아 처리됐는지, 아예 닿지도 않았는지 보낸 쪽이 구별할 수 없습니다.

sequenceDiagram
    participant 클라이언트
    participant 서버
    클라이언트->>서버: 요청
    Note over 서버: 처리 완료
    서버--x클라이언트: 응답이 유실된다
    Note over 클라이언트: 닿았는지 알 수 없다
    클라이언트->>서버: 같은 요청을 다시 보낸다

보낸 쪽에 남는 길은 둘뿐입니다. 포기하거나 다시 보내는 것입니다. 그런데 다시 보내면 이미 처리된 일이 또 처리될 수 있습니다. 그래서 연산을 두 갈래로 갈라 두게 됩니다. 다시 보내도 남는 상태가 같은 것과 그렇지 않은 것입니다. HTTP(HyperText Transfer Protocol) 명세인 RFC(Request for Comments) 9110 은 이 갈래를 메서드 단위로 못 박았습니다. 여러 번의 동일한 요청이 서버에 의도하는 효과가 한 번의 요청과 같으면 그 메서드를 멱등이라고 부릅니다. 멱등 메서드를 따로 구별하는 이유도 적어 두었습니다. 통신 실패 때문입니다. 클라이언트가 서버 응답을 읽기 전에 연결이 닫히면 새 연결을 맺어 그 요청을 다시 보낼 수 있습니다. 원래 요청이 이미 성공했더라도 의도한 효과는 같습니다. 다만 응답은 다를 수 있다고 곧바로 덧붙입니다.

반대쪽에는 금지가 붙습니다. RFC 9110 은 비멱등 메서드를 클라이언트가 자동으로 재시도하지 않기를 권합니다(SHOULD NOT). 그 요청의 의미가 실제로는 멱등이라는 것을 알 방법이 있거나, 원래 요청이 적용된 적이 없다는 것을 알아낼 방법이 있을 때만 예외입니다. 프록시는 비멱등 요청을 자동으로 재시도해서는 안 됩니다(MUST NOT). 실패한 자동 재시도를 클라이언트가 또 자동으로 재시도하는 것도 권하지 않습니다(SHOULD NOT). 이름은 수학에서 쓰던 말을 그대로 빌려 왔습니다.

예시

HTTP 메서드

RFC 9110 은 이 명세가 정의한 요청 메서드 가운데 PUT, DELETE, 그리고 안전한 요청 메서드를 멱등으로 정합니다. 안전한 메서드는 GET, HEAD, OPTIONS, TRACE 입니다.

PUT 은 요청 메시지 내용에 담긴 표현으로 대상 자원의 상태를 만들거나 갈아치우라는 요청입니다. 대상 자원에 현재 표현이 없는데 PUT 이 새로 만들어 내면 서버는 201(Created)로 알려야 합니다. 이미 현재 표현이 있고 그것이 성공적으로 바뀌면 200(OK) 이나 204(No Content)를 보내야 합니다. 같은 PUT 을 두 번 보내면 첫 응답은 201, 두 번째 응답은 200 이나 204 입니다. 응답 코드는 갈립니다. 자원에 남는 상태는 같습니다.

DELETE 는 대상 자원과 그 현재 기능 사이의 연결을 없애라는 요청입니다. RFC 9110 은 이것을 유닉스의 rm 명령에 견줍니다. 이미 딸려 있던 정보가 지워지기를 기대하는 것이 아니라, 서버의 URI(Uniform Resource Identifier) 대응에 대해 삭제 연산을 표현하는 것입니다. 대상 자원에 현재 표현이 하나 이상 있어도 그것이 실제로 파괴되는지, 저장 공간이 회수되는지는 자원의 성격과 서버 구현에 전적으로 달렸습니다. 명세의 범위 밖이라고 적혀 있습니다.

결제 API — Stripe 의 멱등 키

Stripe API(Application Programming Interface) 문서는 요청을 안전하게 다시 보내기 위한 장치로 멱등 키를 둡니다. 같은 연산이 실수로 두 번 수행되는 것을 막는 것이 목적입니다. 객체를 만들거나 갱신할 때 요청 옵션에 IdempotencyKey 를 하나 더 넣습니다. 그러면 연결 오류가 났을 때 같은 요청을 그대로 되풀이해도 두 번째 객체가 생기거나 갱신이 두 번 일어날 위험이 없습니다.

동작은 저장에 기댑니다. Stripe 는 어떤 멱등 키로 들어온 첫 요청의 상태 코드와 본문을 저장합니다. 그 요청이 성공했든 실패했든 저장합니다. 같은 키로 오는 뒤 요청에는 저장해 둔 같은 결과를 돌려 줍니다. 500 오류도 그대로 돌려 줍니다.

키에는 규격이 붙습니다. 키는 클라이언트가 만듭니다. 길이는 255자까지입니다. 문서는 V4 UUID(Universally Unique Identifier)나 충돌을 피할 만큼 엔트로피가 있는 무작위 문자열을 권합니다. 이메일 주소나 개인 식별자 같은 민감한 값은 키로 쓰지 말라고 적습니다. 키는 만들어진 지 24시간이 지나면 시스템에서 자동으로 지워질 수 있습니다. 원래 키가 정리된 뒤 같은 키가 다시 쓰이면 새 요청으로 처리합니다. 멱등 계층은 들어온 파라미터를 원래 요청의 파라미터와 견줍니다. 같지 않으면 오류를 냅니다.

멱등 키를 받는 것은 POST 요청입니다. 문서는 GET 과 DELETE 에는 멱등 키를 보내지 말라고 적습니다. 그 요청들은 정의상 이미 멱등이라 키를 넣어도 효과가 없습니다.

메시지 큐 — Amazon SQS FIFO 의 중복 제거 ID

MessageDeduplicationId 는 Amazon SQS(Simple Queue Service) 의 FIFO(First-In-First-Out) 큐에서만 쓰는 토큰입니다. 중복 전달을 막습니다. 5분짜리 중복 제거 창 안에서는 같은 중복 제거 ID(Identifier)를 가진 메시지가 하나만 처리되고 전달됩니다.

이미 받아들인 중복 제거 ID 로 메시지가 또 오면 확인 응답은 돌아옵니다. 다만 소비자에게 전달되지는 않습니다. 메시지를 받아서 지운 뒤에도 그 중복 제거 ID 추적은 이어집니다.

선언형 구성 — kubectl apply 와 CREATE TABLE

kubectl apply -f <directory> 는 지정한 디렉터리의 설정 파일들이 정의한 오브젝트를 만듭니다. 이미 있는 것은 빼고 만듭니다. 이때 각 오브젝트에 kubectl.kubernetes.io/last-applied-configuration 애노테이션을 답니다. 그 애노테이션에는 오브젝트를 만들 때 쓴 설정 파일의 내용이 들어갑니다.

같은 명령이 갱신도 합니다. 디렉터리가 정의한 오브젝트가 이미 있어도 그대로 씁니다. 설정 파일에 나오는 필드는 라이브 설정에 넣습니다. 설정 파일에서 지워진 필드는 라이브 설정에서 지웁니다. 같은 파일로 몇 번을 실행해도 도착하는 자리는 그 파일이 적어 둔 상태입니다.

PostgreSQL 의 CREATE TABLE ... IF NOT EXISTS 도 같은 자리에 있습니다. 같은 이름의 릴레이션이 이미 있으면 오류를 던지지 않습니다. 대신 NOTICE 를 냅니다. 다만 공식 문서는 곧바로 못 박습니다. 이미 있는 릴레이션이 만들어졌을 릴레이션과 조금이라도 비슷하다는 보장은 없습니다. 이름이 같으면 넘어가는 것이지 스키마가 같다는 뜻이 아닙니다.

경계

두 번째 DELETE 가 404 를 받으면 그 DELETE 는 멱등이 아닌가. 멱등입니다.

정의가 무엇을 기준으로 삼는지가 근거입니다. RFC 9110 은 여러 번의 동일한 요청이 서버에 의도하는 효과가 한 번의 요청과 같을 때 그 메서드를 멱등이라고 부릅니다. 기준은 효과입니다. 자동 재시도 대목에서도 원래 요청이 성공했더라도 되풀이한 요청의 의도한 효과는 같다고 적습니다. 그리고 응답은 다를 수 있다고 덧붙입니다. 첫 요청이 연결을 없앴습니다. 두 번째 요청은 없앨 것을 못 찾았습니다. 남는 상태는 둘 다 그 연결이 없는 상태입니다.

원자성은 이 표제어에 들어가지 않습니다. 되풀이해도 남는 상태가 같다는 약속일 뿐, 한 연산이 통째로 되거나 통째로 안 된다는 약속이 아니기 때문입니다.

관련 항목

이 성질을 규정하는 HTTP 표준과 메서드

HTTP · RFC 9110 · HTTP 메서드 · 요청 메서드 · 안전한 메서드 · PUT · DELETE · GET · HEAD · OPTIONS · TRACE · 상태 코드

이 예시에서 풀어 쓰는 줄임말

URI(Uniform Resource Identifier, 통합 자원 식별자) · API · UUID(Universally Unique Identifier, 범용 고유 식별자) · FIFO(First-In-First-Out, 선입선출)

이 성질 덕에 안전해지는 재시도 수단

네트워크 · 재시도 · 자동 재시도 · 지수 백오프 · 타임아웃

이 성질을 만들어 내는 장치

멱등 키 · 중복 제거 · 중복 제거 창 · 선언형 구성 · 상태 수렴 · 낙관적 잠금 · MessageDeduplicationId · kubectl apply · CREATE TABLE · 스키마

이것을 실제로 구현·채택한 제품

Stripe · Amazon SQS · Kubernetes · PostgreSQL

이 성질이 필요해지는 메시지 전달 방식

최소 한 번 전달 · 최대 한 번 전달 · 정확히 한 번 전달 · 멱등 소비자 · 메시지

이것과 헷갈리는 이웃 성질

원자성 · 순수 함수 · 부수 효과

이것이 막아 주는 오류·장애

경쟁 상태 · 중복 결제 · 재시도 폭풍

다른 이름: idempotence · idempotent · 멱등