Files
tech-log-backend/docs/runbooks/redis-capability-incident.md
T

230 lines
16 KiB
Markdown

---
title: Runbook — Redis capability incident
category: TRANSIENT_DEPENDENCY
error_codes: []
severity: P1
owner: oncall
last_updated: 2026-07-29
status: active
---
# Runbook: Redis capability incident (`runbook://redis/capability-incident`)
이 runbook은 Redis 전체를 하나의 상태로 취급하지 않는다. 먼저 영향받은 capability와 role을
식별한다.
| Role | Capability | 기본 안전 결정 |
| --- | --- | --- |
| `CACHE` | cache, cache refresh soft lease | source fallback 예산 안에서 degraded serving 허용 |
| `COORDINATION` | edge rate limit, request-replay idempotency, efficiency lease | 새 mutation/claim을 fail closed하고 결과 불확실성을 보존 |
| `SESSION` | Redis session | 인증을 fail open하지 않고 재인증 또는 503으로 전환 |
Redis liveness 실패만으로 pod를 재시작하지 않는다. 재시작 폭주는 reconnect와 source fallback
부하를 키울 수 있다.
## Detection
- readiness detail에서 affected role과 `required` 여부를 확인한다. endpoint, key, token, secret
reference는 detail에 포함되면 안 된다.
- semantic reason을 구분한다: read/write failure, program ACL denial, unsupported server
version, program failure, command saturation, recent command failure, probe-in-progress,
stale observation, closed route, command unavailable. `semanticObservedAt`,
`semanticAgeMillis`, `semanticStale`를 함께 확인한다. PING 성공만으로 role이 ready라는 뜻은
아니다.
- `evictionValidation=CONFIGURED_EXPECTATION_ONLY`
`externalEvictionAttestation=INCOMPLETE`는 effective server policy가 증명되지 않았다는
뜻이다. 이를 정상 attestation으로 해석하지 않는다.
- `redis.capability.operations.total``redis.capability.duration.seconds`에서 affected
capability/role/operation의 실제 반환 outcome을 확인한다. mutation의
`certainty=indeterminate`는 timeout이나 연결 끊김을 미실행 증거로 바꾸지 않는다.
- `redis.capability.admission.rejected.total`에서 `rejected_saturated`
`rejected_closed`를 구분하고, `redis.capability.inflight.total`의 같은 role에 대해 현재 0이
아닌 state를 확인한다. in-flight gauge는 bounded command count이며 byte 수나 queue depth가
아니다.
- `redis.capability.readiness.total`은 현재 상태 gauge가 아니라 exact sanitized
`RoleHealth` 관측 횟수다. 최신 health detail의 state/reason/requirement와 함께 해석한다.
optional cache의 degraded serving과 required coordination/session의 fail-closed 결정을
같은 availability 의미로 합치지 않는다.
- 종료 시 `redis.capability.lifecycle.drain.total`에서 `drained`,
`forced_after_timeout`, `interrupted`를 구분한다. repeated close는 새 drain을 시작하거나
중복 관측을 만들지 않는다.
- reconnect, cache source-load, session repository error 지표의 변화를 함께 본다.
- Redis server 측에서는 memory/eviction, rejected clients, replication link/lag,
persistence error, Cluster coverage를 operator dashboard에서 확인한다.
- `NOSCRIPT`, result-schema mismatch, ACL denial, TLS/auth failure, OOM, timeout을 서로 다른
incident category로 분류한다. timeout은 command 미실행 증거가 아니다.
### Observability and lifecycle boundaries
- 여섯 `redis.capability.*` meter의 tag는 닫힌 enum에서만 생성된다. key, subject, session id,
token, endpoint, exception text, script/SHA, value 같은 identity/wire material을 metric이나
ticket에 복사하지 않는다.
- semantic operation 계측은 logical provider가 실제로 반환한 hit/miss/denied/conflict/
unavailable/indeterminate 결과를 기록한다. cache의 `stale`/`skipped`, session의
`tombstoned`/`absolute_expired`도 정상 hit/miss와 분리한다. meter registry, classifier,
monotonic ticker 장애는 command 결과나 원래 exception instance를 바꾸지 않는다.
- route 응답이 설정된 byte/collection bound를 넘으면 동일 logical operation을
`unavailable`로 종료한다. GET/read-only 응답은 `not_applied`, mutation VALUE/MULTI 응답은
서버 실행 여부를 되돌릴 수 없으므로 `indeterminate`다. 앞선 `success` 표본과 이 실패를 두
operation으로 합산하지 않는다.
- Spring 종료의 dependency order는 invalidation subscription 같은 capability dependent를 먼저
닫고, capability bean을 닫은 다음 canonical registry가 router admission을 닫아 in-flight를
bounded drain하고 마지막에 runtime을 닫는 순서다. 종료 중 새 command를 허용하거나 drain
timeout 뒤 무기한 기다리지 않는다.
- 현재 composition에는 active Redis scheduler나 dormant credential-rotation coordinator가 없다.
존재하지 않는 lifecycle coordinator를 복구 절차에서 찾거나 수동 호출하지 않는다.
- 이 meter와 단일-process lifecycle test는 Sentinel/Cluster failover, TLS/ACL 배포 적합성,
k3s multi-node, L1/L2 분산 일관성, distributed session 동작의 qualification 증거가 아니다.
해당 label은 별도 topology/conformance lane의 실제 증거가 있어야 한다.
## Immediate mitigation
1. 새 배포나 credential/program 전환 직후라면 해당 rollout을 중지한다. 이미 실행된 mutation을
무조건 재시도하지 않는다.
2. optional cache만 영향을 받으면 source bulkhead와 stale/source fallback 예산을 확인한 뒤
degraded serving을 유지한다. source가 포화되면 cache miss를 더 많은 source 요청으로
증폭시키지 않는다.
3. rate limit이 불확실하면 정책에 정의된 fail-closed 또는 bounded local-emergency만 사용한다.
local provider를 조용한 primary fallback으로 바꾸지 않는다.
4. idempotency claim/complete 응답이 유실됐으면 같은 operation token으로 inspect/reconcile한다.
record를 삭제하거나 새 owner를 추측하지 않는다.
5. lease 결과가 불확실하면 소유권이 있다고 가정하지 않는다. fencing 없는 efficiency lease를
correctness lock으로 승격하지 않는다.
6. session repository 장애에서는 기존 요청을 인증된 것으로 간주하지 않는다. fail closed 또는
재인증으로 전환하고 JWT와 Redis Session filter를 동시에 활성화하지 않는다.
## Diagnosis
### Connectivity, TLS, ACL
- 배포 설정이 올바른 role을 참조하고 TLS, hostname verification, explicit trust bundle, named ACL
user를 사용하는지 확인한다.
- runtime identity로 `CONFIG`, `KEYS`, `FLUSH*`, arbitrary program deployment를 시도하지
않는다. Catalog digest로 닫힌 recovery 외 ACL 점검은 별도 operator/deployer identity의
`ACL DRYRUN` 또는 동등한 관리 절차로 수행한다.
- runtime readiness identity에는 bounded probe namespace `~ca-health:*`, SET/GET/DEL,
PING/EVALSHA와 catalog recovery에 필요한 SCRIPT LOAD, 그리고 선택 capability manifest의 exact
command set이 필요하다. broad `~*`/`+@all`로 장애를 우회하지 않는다.
- readiness probe는 5초 TTL의 opaque key만 사용한다. `ca-health:*` key가 5초를 넘겨 남는다면
cleanup/expiry 이상으로 분류하되 key나 value를 ticket/log에 복사하지 않는다.
- 기본 semantic cadence는 minimum interval 5초, maximum staleness 15초다. refresh follower는
blocking하지 않는다. maximum staleness를 넘은 관측을 backend 정상으로 해석하지 말고,
probe 부하를 줄이기 위해 interval을 1초 미만으로 낮추지 않는다.
- optional CACHE의 typed temporary connect/PING outage만 dormant degraded startup과
health-triggered reconnect를 허용한다. reconnect 후보는 full semantic qualification 뒤에만
설치된다. auth/TLS/material/version/ACL/schema mismatch를 transient로 재분류하거나 required
role에 같은 fallback을 적용하지 않는다.
- credential rotation 중이라면 new credential 검증, traffic switch, old connection drain,
old credential revoke 순서를 확인한다. secret 값은 ticket, log, shell history에 복사하지 않는다.
### Program or schema
- checked-in program manifest digest와 배포 artifact digest를 대조한다.
- `semantic-capability-acl-v1` contract와 Redis minimum 7.2를 확인한다. 이 프로그램은
Redis Lua API의 `redis.acl_check_cmd`로 선택 capability의 exact command/key 권한을
비변경 방식으로 검사하고 `redis.REDIS_VERSION_NUM`의 explicit >=7.2 gate를 먼저 적용한다.
두 API는 7.0부터 존재하지만 repository support policy minimum은 7.2다.
- `NOSCRIPT`는 bounded `SCRIPT LOAD -> digest verify -> EVALSHA` recovery가 수행됐는지 확인한다.
arbitrary `EVAL`로 우회하지 않는다.
- result-schema/key/codec future version은 장애가 아니라 호환성 위반으로 분류하고 writer rollout을
중지한다.
- `BUSY` 또는 slow program이면 affected capability admission을 줄이고 isolated environment에서만
재현한다. shared Redis에 장시간 script를 추가 실행하지 않는다.
### Memory and eviction
- `CACHE` 배포와 `COORDINATION`/`SESSION` 배포가 물리적으로 분리됐는지 확인한다.
- correctness role에서 eviction이 관측되면 P1이다. 새 write를 중지하고 record loss를 전제로
idempotency/session reconciliation 또는 재인증 범위를 산정한다.
- noeviction OOM은 성공으로 변환하지 않는다. cache write는 degraded/indeterminate, coordination
mutation은 unavailable/indeterminate로 유지한다.
- big key를 찾을 때 production request path에서 `KEYS`나 unbounded collection read를 사용하지
않는다. 승인된 operator job의 bounded `SCAN`/sampling을 사용한다.
### Topology and persistence
- 현재 구현 후보 card의 promotion topology는 readiness registry의 `selected-topology`가 정본이다.
이는 selection 또는 R2 qualification을 뜻하지 않는다. Sentinel/Cluster evidence가 없는
상태에서 standalone 증거를 HA 증거로 재사용하지 않는다.
- Cluster same-slot semantic probe는 해당 hash slot owner 한 노드만 검증한다. 이를 cluster-wide
또는 failover target version/ACL/program 증거로 해석하지 말고, promotion 전에 모든 target을
별도 conformance lane으로 검증한다.
- failover 뒤에는 in-flight mutation의 certainty, primary role, program availability, replication
offset/lag, persistence status를 각각 확인한다.
- restore 후 session/idempotency/lease record를 자동으로 신뢰하지 않는다. security epoch,
tombstone, durable receipt/fencing high-watermark가 필요한 capability는 별도 reconciliation을
수행한다.
### Sentinel failover
1. affected role의 semantic readiness가 unavailable인지 확인하고 단순 PING success로 정상 판정하지
않는다. required coordination/session은 새 mutation admission을 닫는다.
2. 세 Sentinel 중 응답 수와 같은 master에 동의한 수를 확인한다. 2-of-3 동의 전에는 임의 endpoint,
최초 응답 또는 DNS 추측으로 data runtime을 바꾸지 않는다.
3. Sentinel discovery credential/CA와 Redis data credential/CA가 분리되어 있는지 확인한다.
장애 우회를 위해 trust-all, hostname verification off, plaintext 또는 broad ACL을 열지 않는다.
4. election, discovered primary qualification, new runtime install, old runtime admission close/drain의
순서를 확인한다. old runtime을 강제로 닫아야 했다면 그 시점의 mutation을 성공/미실행으로
추정하지 않는다.
5. response-only cut, timeout, disconnect가 있었던 rate/idempotency/session mutation은
`INDETERMINATE`를 보존한다. rate evaluation replay, 같은 idempotency/session operation token의
inspect/reconcile 또는 재인증을 사용하고 blind retry하지 않는다.
6. semantic readiness 복구 전에는 traffic을 정상화하지 않는다. 복구 뒤 old primary의 replica
재합류, replication lag/acknowledgement, program digest, actor runtime generation을 확인한다.
Sentinel은 asynchronous replication의 zero-data-loss나 strong consistency를 보장하지 않는다.
`min-replicas-to-write`, lag bound, replica acknowledgement가 설정돼도 acknowledgement 결과가
불명확한 mutation은 여전히 `INDETERMINATE`다.
`min-replicas-to-write 1` + `min-replicas-max-lag 1`은 선택이 아니라 **필수**다. 미설정 시
promotion 중 교체된 구 primary가 계속 `+OK`를 반환하고 그 write는 resync에서 폐기된다. 7.4
레인 실측: 승격 후 강등까지 11초, 그 사이 **2,086건이 acknowledge된 뒤 소실**, 실패한 명령은
1건. 클라이언트는 이를 감지할 수단이 없다 — 서버가 응답했으므로 driver·SDK·호출자 모두
정상 성공으로 기록한다. 설정 후 동일 promotion에서 소실 1건, 나머지 2,020건은 `NOREPLICAS`
명시 거부됐다. 근거: `docs/redis/operations.md`, `LiveRedisSentinelPromotionTest`.
### Disposable Multipass k3s qualification safety
qualification lab은 host k3s incident 조치 도구가 아니다. VM exact allowlist는
`ca-redis-lab-server`, `ca-redis-lab-agent-1`, `ca-redis-lab-agent-2`이며 전용 kubeconfig와
`ca-redis-lab` context만 사용한다.
- 시작 전 host context/API/node/CIDR/NodePort와 Multipass inventory fingerprint를 기록한다.
- lab pod/service CIDR `10.52.0.0/16`, `10.53.0.0/16`이 host와 겹치면 생성하지 않는다.
- default kubeconfig를 merge/overwrite하거나 host context에 write command를 실행하지 않는다.
- cleanup은 exact 세 VM만 대상으로 한다. global `multipass purge`, wildcard delete를 사용하지
않는다.
- 성공/실패 뒤 postflight fingerprint와 VM resource 0을 확인한다. local retain-on-failure가
명시적으로 활성화됐으면 보존 이유와 exact inventory를 기록하며 CI에서는 보존하지 않는다.
- 이 한 물리 host의 3 VM 결과를 k3s control-plane HA, physical host/AZ failure 또는
multi-region 증거로 승격하지 않는다.
## Recovery and verification
1. affected role의 connection/auth/TLS와 `ca-health:` SET/GET/cleanup probe가 정상인지
확인한다. probe 잔여 key가 있으면 최대 TTL 5초 뒤 소멸하는지도 확인한다.
2. 선택 capability의 대표 program digest/result schema, semantic ACL contract와 Redis minimum
version 7.2를 재확인한다.
3. capability별 smoke를 수행한다: cache generation guarded write, rate evaluation replay,
idempotency same-operation inspect, lease stale-owner reject, session create/read/logout.
4. queue saturation, indeterminate outcome, source fallback, re-auth 지표가 incident 전 범위로
돌아온 뒤에만 rollout을 재개한다.
5. `CONFIGURED_EXPECTATION_ONLY`인 eviction은 operator/deployer identity의 외부 conformance
job 또는 서명 attestation으로 effective policy를 별도 검증한다. runtime user에 CONFIG/ACL
권한을 추가하지 않는다.
6. production label을 변경하기 전 repository readiness task를 실행한다. Sentinel/Cluster task가
zero-evidence로 실패한다면 topology를 낮춰 표기하거나 실제 evidence를 먼저 추가한다.
7. Sentinel qualification에서는 actual image ID/digest와 fault/election/runtime-swap/readiness
timeline, capability certainty, teardown 결과가 sanitizer/reconciler를 통과했는지 확인한다.
clean committed source와 실제 remote CI가 없으면 `implemented-candidate`,
`releaseQualification=NOT_CLAIMED`를 유지한다.
## Escalation
- `COORDINATION` 또는 `SESSION` required role이 5분 이상 unavailable이면 P1로 Redis/platform,
application on-call을 동시에 호출한다.
- data loss, stale session resurrection, conflicting idempotency completion, duplicate correctness
side effect가 의심되면 security/business owner까지 즉시 확대한다.
- 한 물리 host의 VM 세 개 또는 standalone container 결과를 AZ/host failure 증거로 해석하지
않는다. 그 증거가 필요한 release는 별도 disposable multi-node qualification을 요구한다.