92 lines
9.4 KiB
Markdown
92 lines
9.4 KiB
Markdown
---
|
||
title: "official-doc / Redis INCR — Atomicity & Rate Limiter Pattern Race Condition"
|
||
source_type: official-doc
|
||
url: https://redis.io/docs/latest/commands/incr/
|
||
archive_url:
|
||
related_branches: [feature-redis-atomic-program-catalog-contract]
|
||
related_projects: [ca-skeleton]
|
||
tags: [official-doc, ca-skeleton, caching, redis, rate-limit]
|
||
created: 2026-07-28
|
||
last_reviewed: 2026-07-28
|
||
status: raw
|
||
confidence: high
|
||
vendor: Redis
|
||
author: Redis (official docs)
|
||
published:
|
||
---
|
||
|
||
# Redis INCR — Atomicity & Rate Limiter Pattern Race Condition
|
||
|
||
> Layer: `raw/official-docs/` — Redis 공식 커맨드 레퍼런스(`INCR`) 원문 발췌·출처 기록.
|
||
> 검증된 요약은 `/ingest` 후 `wiki/concepts/`에 `source-summary-template` 형식으로 별도 작성. 원본은 raw에 영구 보관.
|
||
|
||
## Parent / 활용 branch
|
||
|
||
| Branch | 이 자료가 정당화하는 결정 |
|
||
|---|---|
|
||
| [[raw/branch-notes/feature-redis-atomic-program-catalog-contract]] | 각 atomic program helper 는 흔한 unsafe multi-command recipe 와 그 안전한 atomic 대체를 쌍으로 문서화한다 — 대표 사례 `GET → 판단 → INCR → EXPIRE` 의 race condition 을 공식 문서가 명시적으로 서술한다는 근거 |
|
||
|
||
## 출처 / Source
|
||
|
||
- 원본 URL: https://redis.io/docs/latest/commands/incr/
|
||
- 아카이브 URL: (미제공)
|
||
- 저자 / 조직: Redis (공식 커맨드 레퍼런스)
|
||
- 발행일: (페이지에 명시 없음 — `since: 1.0.0` 커맨드 메타데이터만 존재)
|
||
- 마지막 확인일: 2026-07-28
|
||
|
||
## 왜 저장했는지 / Why archived
|
||
|
||
`feature-redis-atomic-program-catalog-contract` branch 는 "다단계 read-decide-write recipe 는 항상 unsafe 버전과 atomic 대체를 쌍으로 문서화한다"는 프로젝트 결정(`DEC-CA-SKELETON-OPERATIONAL-CONTRACT-REDIS-PROGRAM-001`)을 구현해야 한다. Redis 공식 `INCR` 문서의 "Pattern: rate limiter" 절이 바로 이 다단계 recipe (`GET` → 판단 → `INCR` → 조건부 `EXPIRE`)의 race condition 을 공식적으로 서술하고, `MULTI`/`EXEC` 및 Lua(`EVAL`) 대체를 명시적으로 권고하므로 1차 근거로 확보한다.
|
||
|
||
## 핵심 인용 / Key quotes (verbatim)
|
||
|
||
> [§Details > Pattern: counter] "The counter pattern is the most obvious thing you can do with Redis atomic increment operations."
|
||
> (lines 508–509 in fetched text)
|
||
|
||
> [§Details > Pattern: rate limiter > Pattern: rate limiter 1] "Note the used of `MULTI` and `EXEC` in order to make sure that we'll both increment and set the expire at every API call."
|
||
> (lines 565–566 in fetched text)
|
||
|
||
> [§Details > Pattern: rate limiter > Pattern: rate limiter 2] "**In the above code there is a race condition**."
|
||
> (line 593 in fetched text)
|
||
|
||
> [§Details > Pattern: rate limiter > Pattern: rate limiter 2] "If for some reason the client performs the `INCR` command but does not perform the `EXPIRE` the key will be leaked until we'll see the same IP address again."
|
||
> (lines 594–595 in fetched text)
|
||
|
||
> [§Details > Pattern: rate limiter > Pattern: rate limiter 2] "This can be easily fixed by turning the `INCR` with optional `EXPIRE` into a Lua script that is then sent using the `EVAL` command (only available since Redis version 2.6)."
|
||
> (lines 597–599 in fetched text)
|
||
|
||
## Claims Extracted / 추출된 주장
|
||
|
||
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|
||
|---|---|---|---|---|---|
|
||
| REDIS-INCR-C1 | `INCR` 은 "atomic increment operation" 으로 서술된다 — 단일 `INCR` 호출 자체는 원자적이다 | [§Pattern: counter] "The counter pattern is the most obvious thing you can do with Redis atomic increment operations." | `official-vendor-doc` | 단일 `INCR` 커맨드 호출 자체의 원자성 (per-command atomicity) | 여러 명령(`GET`→판단→`INCR`→`EXPIRE`)을 조합한 시퀀스 *전체* 가 원자적이라는 것 — 오히려 본 문서가 뒤에서 이를 반박(REDIS-INCR-C2) |
|
||
| REDIS-INCR-C2 | "GET 으로 현재값 확인 → 조건부 `INCR` → 첫 증가 시에만 `EXPIRE`" 형태의 rate limiter 구현("Pattern: rate limiter 2")에 대해 공식 문서가 명시적으로 race condition 존재를 선언한다 | [§Pattern: rate limiter 2] "**In the above code there is a race condition**." | `official-vendor-doc` | `GET`/`INCR`/조건부 `EXPIRE` 다단계 recipe 에 race 가 실재한다는 사실 자체 | 다른 언어·클라이언트 구현에서도 항상 동일 race 가 발생한다는 일반화 — 문서는 이 특정 pseudocode 예시에 대해서만 명시함 |
|
||
| REDIS-INCR-C3 | 위 race 의 구체적 실패 모드는 "클라이언트가 `INCR` 은 수행했지만 `EXPIRE` 를 수행하지 못한 경우" 이며, 이 경우 같은 IP 를 다시 볼 때까지 key 가 TTL 없이 leak 된다 | [§Pattern: rate limiter 2] "If for some reason the client performs the `INCR` command but does not perform the `EXPIRE` the key will be leaked until we'll see the same IP address again." | `official-vendor-doc` | `INCR` 성공 후 `EXPIRE` 미실행(크래시·네트워크 단절 등) 시 key 누수 시나리오 | 이 leak 이 rate-limit *판정 정확성* 자체를 깨뜨린다는 것 — 문서는 메모리/키 누수로 서술하지, 카운트 오판으로 서술하지 않음 |
|
||
| REDIS-INCR-C4 | 공식 문서는 두 가지 다른 recipe 에 두 가지 다른 대체를 권고한다 — (a) 고정 타임스탬프 키 카운터("rate limiter 1")에는 `INCR`+`EXPIRE` 를 `MULTI`/`EXEC` 로 묶을 것을, (b) 조건부 분기가 있는 단일 카운터("rate limiter 2")에는 `INCR`+조건부 `EXPIRE` 를 Lua 스크립트로 옮겨 `EVAL` 로 실행할 것을 권고한다 | [§rate limiter 1] "Note the used of `MULTI` and `EXEC` in order to make sure that we'll both increment and set the expire at every API call." + [§rate limiter 2] "This can be easily fixed by turning the `INCR` with optional `EXPIRE` into a Lua script that is then sent using the `EVAL` command (only available since Redis version 2.6)." | `official-vendor-doc` | 두 recipe 각각에 맞는 공식 대체 메커니즘 선택 근거 | `MULTI`/`EXEC` 가 "rate limiter 2" 처럼 읽은 값을 기준으로 분기(`IF value == 1 THEN EXPIRE`)하는 recipe 의 race 도 없앤다는 것 — 문서는 그 recipe 의 fix 로 `MULTI`/`EXEC` 가 아니라 Lua 를 명시적으로 별도 제시함 (조건부 분기 recipe 와 무분기 recipe 는 다른 처방) |
|
||
|
||
## Usage Boundaries / 적용 경계
|
||
|
||
- 이 자료가 직접 증명하는 것:
|
||
- `REDIS-INCR-C1`: `INCR` 단일 커맨드 자체가 원자적이라는 공식 서술
|
||
- `REDIS-INCR-C2`: `GET`→판단→`INCR`→조건부 `EXPIRE` recipe 에 공식 문서가 인정하는 race condition 이 존재한다는 사실
|
||
- `REDIS-INCR-C3`: 그 race 의 구체적 실패 모드(EXPIRE 유실 시 key leak)
|
||
- `REDIS-INCR-C4`: recipe 종류에 따라 공식이 권고하는 두 가지 다른 대체 메커니즘(MULTI/EXEC vs Lua/EVAL)
|
||
- 이 자료가 증명하지 않는 것:
|
||
- `MULTI`/`EXEC` 가 조건부 분기(읽은 값에 따라 다음 커맨드를 결정)가 있는 recipe 의 race 도 없앤다는 것 — 문서는 이 경우 Lua 를 명시적으로 제시
|
||
- `EVAL`/Lua 스크립트가 모든 Redis 배포 모드(clustered, Redis Functions 등)에서 동일하게 동작한다는 것 — 버전 제약("only available since Redis version 2.6")만 명시
|
||
- key leak 이 실제 운영 환경에서 관측 가능한 심각도(메모리 규모·발생 빈도)를 가진다는 것 — 문서는 정성적 서술만 제공, 정량적 근거 없음
|
||
- ca-tmpl/ca-skeleton 의 `ScriptDescriptor`/EVALSHA/NOSCRIPT 재적재 같은 구체적 구현 메커니즘 — 이는 본 branch 의 로컬 결정 사항이지 이 공식 문서의 주장이 아님
|
||
- 내 프로젝트에 적용하려면 추가 확인이 필요한 것:
|
||
- `feature-redis-atomic-program-catalog-contract` 의 unsafe recipe 카탈로그에 이 `GET`→`INCR`→조건부 `EXPIRE` 패턴을 등재할 때, 대응 atomic 대체를 Lua 스크립트(EVALSHA)로 명시하고 MULTI/EXEC 로 오인하지 않도록 §구현 가이드에서 구분해야 함
|
||
- 경합 재현 test 시나리오는 REDIS-INCR-C2/C3 의 실패 모드(첫 INCR 성공 후 EXPIRE 실패)를 인위적으로 유발할 수 있는 fault-injection 지점 확인 필요
|
||
|
||
## 메모 / Notes
|
||
|
||
- 본 문서의 "Pattern: rate limiter" 절은 rate limiter 1(고정 타임스탬프 키, 분기 없음 → MULTI/EXEC 로 충분)과 rate limiter 2(단일 카운터, `IF value == 1` 분기 있음 → MULTI/EXEC 로 불충분, Lua 필요)를 **구분**해서 서술한다. branch 의 unsafe-recipe 카탈로그를 작성할 때 이 두 변형을 하나의 "INCR+EXPIRE" 항목으로 뭉뚱그리면 REDIS-INCR-C4 의 구분을 잃는다 — 별개 카탈로그 항목으로 분리 권장.
|
||
- 문서는 "Pattern: rate limiter 2" 의 세 번째 대안으로 Redis list(`RPUSH`/`RPUSHX`/`LLEN`) 기반 구현도 제시하며, 여기에도 `EXISTS` 확인과 `MULTI`/`EXEC` 사이의 race 가 있지만 "rare conditions 에서 API call 하나를 놓치는 정도"로 무해하다고 서술한다 — 이는 이번 branch 의 핵심 인용 범위(GET→INCR→EXPIRE) 밖이라 인용에는 포함하지 않았으나, "모든 race 가 치명적인 것은 아니다"라는 대비 사례로 §메모에만 남긴다(검증 안 된 해석이므로 claim 화하지 않음).
|
||
|
||
## Related / 관련
|
||
|
||
- 같은 프로젝트 내 Redis 원자성/락 관련 다른 official-doc: [[raw/official-docs/cache-redisson-rlock-vs-setnx]]
|
||
- 이 자료를 인용한 wiki 요약: (미생성 — `/ingest` 후 `wiki/concepts/` 예정)
|