멱등 메시지 처리

멱등 메시지 처리 (Idempotent message processing) (idempotency)

Redis 공식 문서의 idempotency 페이지를 한국어로 풀어드릴게요. 옆에서 하나씩 설명해 드리는 느낌으로 읽으시면 돼요.

출처: Redis 공식 문서 — idempotency

Redis Streams에서 **멱등 메시지 처리(idempotent message processing)**가 어떻게 동작하는지 알려드릴게요. 핵심은 이거예요. 같은 메시지를 여러 번 처리해도 한 번 처리한 것과 같은 시스템 상태를 만들도록 보장해서, 중복 엔트리가 생기는 걸 막는 기능이에요.

왜 멱등 처리가 필요한가요?

Redis 8.6부터 스트림은 **at-most-once 생산(at-most-once production)**을 위한 멱등 메시지 처리를 지원해요. at-least-once 전달 패턴을 쓸 때 중복 엔트리가 생기는 걸 막기 위해서죠. 생산자(producer)가 메시지를 다시 보내야 하는 상황은 크게 두 가지예요.

  1. Producer–Redis 네트워크 문제 (연결 끊김과 재연결): 생산자가 XADD를 실행한 뒤 응답을 받기 전에 연결이 끊기면, 그 메시지가 전달됐는지 알 수 없어요.
  2. 생산자가 크래시 후 재시작: XADD를 호출한 뒤 응답을 받고 메시지를 전달 완료로 표시하기 전에 크래시가 나면, 재시작 후 그 메시지가 전달됐는지 알 수 없어요.

두 경우 모두 메시지가 스트림에 확실히 추가되게 하려면 생산자가 같은 메시지로 XADD를 다시 호출해야 해요. 멱등 처리가 없다면 재시도 때문에 메시지가 두 번 전달될 수 있어요. 멱등 처리가 있으면 이런 시나리오에서도 생산자가 at-most-once 생산을 보장할 수 있죠.

멱등 ID (iid) 할당

스트림에 추가되는 각 메시지에는 **멱등 ID(idempotent ID, 줄여서 iid)**라는 고유 ID가 연결돼요. iid를 할당하는 방법은 두 가지예요.

  1. 생산자가 각 메시지에 고유 iid를 직접 제공. iid는 이미 메시지에 연결된 식별자(트랜잭션 ID, 카운터, UUID 등)일 수 있어요.
  2. Redis가 각 메시지의 내용을 기반으로 iid를 생성.

같은 메시지가 스트림에 두 번 이상 추가되면 같은 iid가 필요해요. (1)의 경우는 생산자가 책임지고, (2)의 경우는 메시지 내용이 바뀌지 않는 한 Redis가 같은 iid를 계산해요.

멱등성 모드 (Idempotency modes)

XADD 명령에 멱등성 파라미터 IDMP 또는 IDMPAUTO를 써요.

XADD mystream IDMP producer-1 iid-1 * field value   # producer-1 (pid)와 iid-1 (iid)을 수동 제공
XADD mystream IDMPAUTO producer-2 * field value     # producer-2 (pid)만 수동 제공, iid는 Redis가 생성

수동 모드 (IDMP)

생산자 ID(pid)와 iid를 둘 다 명시적으로 지정해요.

XADD mystream IDMP producer1 msg1 * field value
  • pid: 메시지 생산자의 고유 식별자.
  • iid: 특정 메시지의 고유 식별자.
  • 성능: 해시 계산이 없어서 더 빠른 처리.
  • 제어: ID 생성과 고유성에 대한 완전한 제어.

자동 모드 (IDMPAUTO)

pid만 지정하면 Redis가 메시지 내용에서 iid를 생성해요.

XADD mystream IDMPAUTO producer1 * field value
  • pid: 메시지 생산자의 고유 식별자.
  • 자동 중복 제거: Redis가 field-value 쌍에서 iid를 계산.
  • 내용 기반: 같은 내용이면 같은 iid가 생성.
  • 성능: 해시 계산 때문에 약간 더 느림.

IDMPIDMPAUTO 모두 각 생산자 애플리케이션이 재시작 후에도 같은 pid를 사용해야 해요.

IDMP의 경우 각 생산자 애플리케이션은 다음을 책임져요.

  • 각 엔트리에 고유한 iid 제공 (전역적으로, 또는 pid마다).
  • 메시지를 다시 보낼 때 같은 (pid, iid)를 재사용 (재시작 후에도).

스트림 구성 (Stream configuration)

스트림의 멱등성 설정은 XCFGSET으로 구성해요.

XCFGSET mystream IDMP-DURATION 300 IDMP-MAXSIZE 1000

파라미터

  • IDMP-DURATION: iid를 보관할 시간(초). (1~86400초, 기본값 100)
  • IDMP-MAXSIZE: 생산자별로 추적할 iid의 최대 개수. (1~10,000개, 기본값 100)

만료 동작

멱등 ID는 다음 조건 중 하나가 충족되면 제거돼요.

  • 시간 기반: 설정된 IDMP-DURATION이 지나면 iid가 만료.
  • 용량 기반: IDMP-MAXSIZE에 도달하면 가장 오래된 iid가 축출(eviction). Redis는 pid당 IDMP-MAXSIZE보다 많은 iid를 절대 유지하지 않아요. 즉 IDMP-MAXSIZEIDMP-DURATION보다 강한 제약이에요.

최적 설정값 결정

IDMP-DURATION은 운영 보장이에요. Redis는 지정된 기간 동안 이전에 보낸 iid를 버리지 않아요(그 생산자에 대해 IDMP-MAXSIZE에 도달하지 않는 한). 생산자 앱이 크래시해 메시지 전송을 멈추면, Redis는 각 iid를 IDMP-DURATION초 동안 유지한 뒤 버려요. 생산자 앱이 크래시에서 복구해 메시지를 다시 보내는 데 걸리는 시간을 알고 있어야 하니, 그에 맞춰 IDMP-DURATION을 설정해야 해요. 너무 높게 잡으면 필요 이상으로 오래 iid를 보관해 메모리를 낭비하게 되죠.

예시: 생산자가 크래시 후 복구·재시작하는 데 최대 1,000초가 걸린다면 IDMP-DURATION을 1000으로 설정해요.

생산자 앱은 Redis에서 XADD 응답을 가져오면 보통 트랜잭션 DB나 로그 파일에 메시지를 *delivered(전달 완료)*로 표시해요. 크래시가 나면 복구 후 미전달 메시지를 다시 보내야 해요. 크래시 때문에 일부 메시지가 delivered로 표시되지 않았을 수 있으니, 앱은 그 메시지들을 다시 보낼 가능성이 높아요. iid를 쓰면 Redis가 이런 중복 메시지를 감지해 걸러낼 수 있어요. IDMP-MAXSIZE를 올바르게 설정하면 충분한 수의 최근 iid를 유지하게 돼요. 너무 높게 잡으면 iid를 너무 많이 보관해 메모리를 낭비해요. 보통 이 숫자는 아주 작을 수 있고, 종종 한 개면 충분해요. 앱이 메시지를 비동기로 delivered로 표시한다면, Redis에서 XADD 응답을 가져온 시점부터 메시지가 delivered로 표시될 때까지 걸리는 시간을 알아야 해요. 이 시간을 mark-delay라고 불러요. IDMP-MAXSIZE는 다음처럼 설정하세요.

mark-delay [msec] * (messages / msec) + some margin

예시: 생산자가 1K msgs/sec(1 msg/msec)를 보내고 각 메시지를 delivered로 표시하는 데 최대 80 msec가 걸린다면, IDMP-MAXSIZE1 * 80 + margin = 100 iid로 설정하면 돼요.

생산자 격리 (Producer isolation)

각 생산자는 독립적인 멱등성 추적을 유지해요.

XADD mystream IDMP producer-1 iid-1 * field value   # producer-1이 추적
XADD mystream IDMP producer-2 iid-1 * field value   # producer-2가 추적 (독립적)

pid가 다르기만 하면 생산자들은 같은 iid를 충돌 없이 쓸 수 있어요.

모니터링 (Monitoring)

XINFO STREAM으로 멱등성 메트릭을 모니터링할 수 있어요.

XINFO STREAM mystream

멱등성을 사용 중일 때 추가 필드를 반환해요.

  • idmp-duration: 현재 duration 설정.
  • idmp-maxsize: 현재 maxsize 설정.
  • pids-tracked: 스트림에서 현재 추적 중인 pid 수.
  • iids-tracked: 현재 추적 중인 iid 총수.
  • iids-added: 멱등 ID가 있는 메시지의 총생성 수.
  • iids-duplicates: 방지된 중복의 총수.

모범 사례 (Best practices)

생산자 ID 선택

전역적으로 고유하고 영구적인 생산자 ID를 사용하세요.

  • 권장: 메모리를 아끼고 성능을 높이기 위해 짧은 프로듀서 ID 사용.
  • 영구성: 재시작 후에도 같은 프로듀서 ID를 사용해 멱등성 추적 유지.

설정 조정

  • Duration: 재시도 타임아웃 패턴에 맞춰 설정.
  • Maxsize: 메모리 사용량과 중복 제거 윈도우 필요성의 균형.
  • 모니터링: iids-duplicates를 추적해 중복 제거 효과 확인.

오류 처리

이런 오류 조건을 처리하세요.

  • WRONGTYPE: 키가 존재하지만 스트림이 아님.
  • ERR no such key: 스트림이 존재하지 않음 (NOMKSTREAM 사용 시).
  • ERR syntax error: 잘못된 명령 구문.

성능 특성 (Performance characteristics)

멱등성은 최소한의 오버헤드만 추가해요.

  • 처리량(Throughput): 표준 XADD 대비 2~5% 감소.
  • 메모리: 추가 메모리 사용 <1.5%.
  • 지연(Latency): 작업별 지연에는 거의 영향 없음.

수동 모드(IDMP)는 해시 계산을 피하므로 자동 모드(IDMPAUTO)보다 약간 빨라요.

영속성 (Persistence)

멱등성 추적은 Redis 재시작을 가로질러 유지돼요.

  • RDB/AOF: 모든 생산자-멱등 ID 쌍이 저장돼요.
  • 복구: 재시작 후에도 추적이 계속 활성 상태.
  • 구성: IDMP-DURATIONIDMP-MAXSIZE 설정이 유지돼요.
  • 중요: 특정 키에 대해 현재 값과 다른 IDMP-DURATION이나 IDMP-MAXSIZE 값으로 XCFGSET을 실행하면 그 키의 IDMP 맵이 비워져요.

더 알아보기 (Learn more)

Redis Streams와 멱등 처리에 대해 더 알고 싶다면 아래를 참고하세요.