feat: add production capability foundations

This commit is contained in:
donghyeon-ka
2026-07-31 23:50:44 +09:00
parent b3add0162d
commit 567422f2e5
757 changed files with 132385 additions and 2146 deletions
@@ -1,7 +1,7 @@
# Redis Production Capability Deep Design
- Date: 2026-07-26
- Status: 상세 설계 완료, Phase 0 및 Phase 1 일부 standalone R1 구현, R2 미구현
- Status: 상세 설계 완료, 5개 standalone `implemented-candidate`, selected/R2 없음
- Scope: Redis 전용 production capability와 단계적 구현 설계
- Baseline: Java 21, Spring Boot 4.0.0, Gradle multi-module Clean Architecture template
- Parent:
@@ -9,7 +9,7 @@
## 0. 구현 상태
2026-07-28 기준 구현된 범위:
2026-07-30 기준 구현된 범위:
- `application-core`의 provider-neutral `CacheRegionPort`와 hit/negative/miss/schema/unavailable
결과 구분;
@@ -21,9 +21,17 @@
- generic application API가 아닌 package-private `RedisAtomicPrimitives` internal R0 foundation과
compatibility failure;
- managed Lettuce standalone connection lifecycle과 finite command timeout;
- `EVALSHA` 우선, 정확한 `NOSCRIPT`에만 `EVAL` fallback하는 production executor;
- `EVALSHA` 우선, 정확한 `NOSCRIPT`에만 catalog script를 `SCRIPT LOAD`하고 digest를 검증한 뒤
`EVALSHA`를 한 번 재시도하는 production executor;
- versioned digest-protected bounded binary cache envelope, positive/negative TTL, invalidate와
corrupt/future/unavailable 구분을 제공하는 `CacheRegionPort<String,String>` reference adapter;
- envelope v2의 absolute soft/hard expiry, injected clock freshness 판정, deterministic
policy-revision/key jitter, hard minimum과 physical Redis TTL 일치;
- framework-free `CacheAsideExecutor`와 typed source/result/cancellation contract;
- maximum in-flight key/waiter/source concurrency/admission/load deadline을 제한하는 local
single-flight와 source bulkhead, abandoned-flight opportunistic reaping;
- authoritative absence만 negative-cache하고 classified transient failure에만 hard-expiry 전
stale fallback을 허용하는 application policy;
- HMAC key secret/namespace/value bound typed settings와 disabled zero-connection composition;
- `managed`/`external` client mode를 통한 결정적 runtime 선택;
- reconnect command replay 차단, finite Lettuce request queue와 client-side admission;
@@ -32,27 +40,44 @@
- managed runtime 활성화 시 Redis host 누락을 `localhost`로 숨기지 않는 startup fail-fast;
- generic Lua executor/descriptor와 raw-key typed primitive를 package-private collaborator로
닫고 Spring composition에는 semantic cache port만 노출;
- 명시적 Redis 7.4 standalone service lane의 실제 TTL expiry, compare-delete Lua,
oversized bulk-reply 차단 검증.
- 명시적 Redis 7.2/7.4 standalone service lane의 실제 TTL expiry, compare-delete Lua,
oversized bulk-reply 차단 검증;
- `shared-contract`의 provider-neutral edge rate-limit request/policy/decision/outcome/port;
- fixed window, sliding-window counter, token bucket의 versioned one-key Lua와 bounded
structured MULTI reply parser;
- private HMAC key, Redis server time, clock regression clamp, denial-no-consume, finite state
TTL과 pre-send/post-dispatch failure certainty를 보존하는 semantic provider;
- cache와 endpoint/connection/admission/settings를 공유하지 않는 coordination-role 전용
`app.rate-limit` composition과 disabled zero-side-effect gating;
- 세 알고리즘을 실제 standalone Redis에 실행하도록 선택 가능한 service qualification lane;
- request-replay idempotency, cache refresh soft lease, versioned session repository semantic
provider와 각 card-owned standalone/security/fault/compatibility evidence;
- cache generation/revision invalidation, bounded local L1, authenticated invalidation hint,
semantic health/metrics와 standalone TLS+named ACL evidence.
아직 구현되지 않은 범위:
- cache jitter, soft/hard TTL, cache-aside/single-flight/source bulkhead;
- refresh-ahead와 probabilistic early refresh;
- Redis Functions 배포와 program upgrade/rollback compatibility matrix;
- health/metrics/TLS/ACL/secret/topology/eviction 검증;
- distributed rate limit, idempotency, lease/fencing, session;
- Sentinel runtime, Cluster production qualification, k3s/multi-node/failover/rotation,
effective eviction/persistence attestation;
- fenced coordination과 multi-process/pod session 및 L1/L2 distributed qualification;
- Phase 1의 전체 acceptance와 R2/R3 승격 증거.
따라서 standalone runtime/string cache는 R1 evidence를 가지지만 Redis capability 전체 또는
어떤 production topology도 R2가 아니다. raw-key Lua foundation
rate/idempotency/lease/session은 semantic composition이 없어 여전히 R0다.
현재 registry의 cache, edge rate limit, request-replay idempotency, cache refresh soft lease,
session card는 standalone promotion topology`implemented-candidate`다. fenced coordination
`not-implemented`다. `implemented-candidate`는 구현과 card-owned evidence lane을 뜻할 뿐 release
selection이나 R2 qualification이 아니다. checked-in `selected` card가 0개이므로 Redis capability
전체 또는 어떤 production topology에도 R2 release claim을 하지 않는다.
## 1. 설계 판정
설계 착수 당시 `adapter:outbound:cache-redis`는 실제 Redis client, connection, topology, TTL,
codec, atomic program, failure semantics가 없는 R0 extension seam이었다. 2026-07-28 구현으로
standalone managed Lettuce runtime과 semantic string cache는 R1까지 올라왔지만, topology,
TLS/ACL, restart/fault/eviction evidence가 없으므로 여전히 production-ready adapter는 아니다.
codec, atomic program, failure semantics가 없는 R0 extension seam이었다. 2026-07-30 현재 위 5개
semantic provider는 standalone `implemented-candidate`이며 standalone TLS+named ACL과 bounded
fault evidence도 있다. 그러나 selection, Sentinel/Cluster, multi-node/failover/rotation,
effective eviction/persistence attestation과 R3 증거가 없으므로 production-ready/R2라는 단일
label을 붙이지 않는다.
이번 설계는 다음 구조를 선택한다.
@@ -75,16 +100,16 @@ TLS/ACL, restart/fault/eviction evidence가 없으므로 여전히 production-re
| Capability | 현재 | 목표 |
| --- | --- | --- |
| Redis runtime | managed Lettuce standalone R1 + explicit external-client mode | Spring Data Redis + Lettuce 기반 typed runtime |
| Cache | `Optional<String> get`, `void put` | typed region, TTL, negative/stale, invalidate, cache-aside |
| Rate limit | inbound-web single-node fixed window | policy별 fixed/sliding/token/GCRA Redis provider |
| Idempotency | JPA 전제, owner token 없음 | atomic claim, owner-safe complete, execution/replay TTL 분리 |
| Lock | JDBC efficiency lock | Redis efficiency lease + 별도 fenced contract |
| Session | JWT stateless 고정 | JWT 또는 isolated Redis Session의 명시적 profile |
| Atomic helper | 없음 | versioned Function/Lua program registry |
| Topology | 없음 | standalone, Sentinel, Cluster의 typed exclusive profile |
| Failure | 모든 cache exception을 miss로 변환 | capability별 fail-open/closed/degraded/indeterminate |
| CI | fake unit test | real Redis, topology, concurrency, failure, compatibility matrix |
| Redis runtime | canonical role router와 managed/external Lettuce runtime, standalone candidate | Sentinel runtime과 Cluster production qualification |
| Cache | standalone `implemented-candidate`; generation/soft lease/bounded L1과 TLS/ACL/fault lane | multi-process L1/L2와 HA/persistence/eviction attestation |
| Rate limit | fixed/sliding-counter/token-bucket standalone `implemented-candidate` | HA topology, failover와 R3 evidence |
| Idempotency | owner-safe Redis V2 standalone `implemented-candidate`; JDBC provider와 명시적 선택 | actual-used image/event evidence와 selected promotion |
| Lock | Redis efficiency lease candidate; fenced coordination은 `not-implemented` | protected-resource stale fencing-token rejection |
| Session | JWT isolated Redis Session profile; Redis는 standalone `implemented-candidate` | multi-process/pod와 failover/rotation qualification |
| Atomic helper | versioned closed Lua catalog와 typed internal facade | Redis Functions upgrade/rollback matrix |
| Topology | standalone candidate; Cluster code seam; Sentinel runtime 미구현 | Sentinel/Cluster/k3s multi-node qualification |
| Failure | capability별 typed degraded/unavailable/indeterminate와 bounded fault lane | 실제 topology event chain과 persistence/restart evidence |
| CI | strict registry matrix, real candidate lanes, sanitized artifact/reconciler | actual-used image attestation과 actual fault-event capture |
설계가 완료되었다는 뜻은 구현 계약과 단계가 결정되었다는 뜻이다. 현재 Redis runtime이
production-ready가 되었다는 뜻은 아니다.
@@ -145,7 +170,11 @@ production-ready가 되었다는 뜻은 아니다.
표의 링크 대상보다 예시 YAML이나 migration alias가 우선하지 않는다. 상충하는 두 설정이
존재하면 임의 precedence를 선택하지 않고 startup을 실패시킨다.
## 3. 증거 기반 현재 상태
## 3. 설계 착수 당시 증거 기반 baseline
이 절 전체는 구현 전 repository를 조사한 2026-07-26 역사적 baseline이다. 아래의 “현재”는 그
조사 시점을 가리키며 2026-07-30 구현 상태를 설명하지 않는다. 최신 구현/readiness truth는 §0,
§1의 현재 열, checked-in `src/config/redis/readiness-cards.yaml`, Redis leaf README를 따른다.
### 3.1 실제 Redis client가 없다
@@ -5904,8 +5933,8 @@ indexed repository는 Cluster/node-specific event와 orphan index cleanup을 별
최소 실제 topology:
- primary;
- replica;
- independent Sentinel quorum.
- replica 2개;
- 서로 다른 k3s node에 배치한 Sentinel 3개와 quorum 2.
test:
@@ -5921,6 +5950,260 @@ test:
단일 fake Sentinel endpoint로 HA를 증명하지 않는다.
#### 37.13.1 Sentinel discovery와 data runtime 분리
Sentinel discovery channel과 Redis data-node channel은 같은 Lettuce client/SSL context로
합치지 않는다. 각각 독립된 named material과 lifecycle을 갖는다.
| Channel | 책임 | 허용 material |
| --- | --- | --- |
| Sentinel discovery | master name 조회와 quorum 관측 | Sentinel ACL username/password reference, Sentinel CA/trust, discovery timeout |
| Redis data | capability command/program 실행 | data-node ACL username/password reference, data CA/trust, command/admission/drain timeout |
discovery는 다음 조건을 모두 만족할 때만 새 primary 후보를 반환한다.
- 구성된 Sentinel endpoint 최소 3개 중 2개 이상이 같은 master host/port를 보고한다;
- 응답한 Sentinel 수와 동의 수가 각각 bounded deadline 안에서 기록된다;
- master name이 exact configured name과 같다;
- 반환 endpoint가 loopback, wildcard, unspecified address가 아니고 allowlisted deployment
identity/member에 속한다;
- TLS hostname/SAN 검증을 통과한다;
- Sentinel credential 또는 trust를 data connection에, data material을 Sentinel connection에
재사용하지 않는다.
한 Sentinel의 응답, 최초 응답 또는 DNS 문자열 일치만으로 primary를 바꾸지 않는다. discovery
실패 detail에는 endpoint, username, secret reference/value, certificate subject를 남기지 않고
sanitized reason과 동의 수만 남긴다.
#### 37.13.2 bounded rediscovery와 runtime swap
정상 polling은 bounded single-flight로 실행하며, write/read command의 topology failure가
발생하면 같은 single-flight에 bounded immediate rediscovery를 요청한다. 새 primary가
qualification을 통과하면:
1. 새 data runtime을 생성한다;
2. version/program/semantic readiness를 검증한다;
3. 기존 `RedisRoleCommandRouter`에 한 번만 install한다;
4. 기존 runtime은 새 admission을 닫고 in-flight command를 bounded drain한다;
5. drain timeout 뒤에는 강제 close하되 완료되지 않은 mutation을 성공/미실행으로 추정하지 않는다.
failover 직전 또는 도중의 mutation은 자동 replay하지 않는다. transport가 실행 여부를 증명하지
못하면 capability가 `INDETERMINATE`를 반환하고, idempotency/session은 같은 operation token의
inspect/reconcile 또는 재인증 경로를 사용한다. read-only command도 semantic contract가 허용하는
경우에만 새 runtime에서 재시도한다.
`snapshot()`/readiness scrape는 정상 polling의 실행 엔진으로 사용하지 않는다. scrape나 command가
없는 동안에도 primary 변경을 발견해야 하므로, active Sentinel role이 하나 이상일 때만 registry가
다음 bounded poller를 소유한다.
- registry당 daemon worker 1개와 active Sentinel role당 fixed-delay task 1개만 만든다;
- 기본 polling period는 30초이고 typed setting은 5초 이상 5분 이하만 허용한다;
- scheduled poll과 command-failure trigger는 role별 같은 single-flight를 공유하며 한 role에
discovery/install 작업은 최대 1개만 실행하거나 대기한다;
- Standalone/Cluster만 선택되거나 Redis capability가 비활성이면 poller/thread/task를 0개 만든다;
- close는 새 trigger를 거절하고 scheduled task를 취소한 뒤 worker를 bounded shutdown하며,
close와 경합해 늦게 생성된 candidate는 install하지 않고 정확히 한 번 닫는다.
command failure signal은 route lease가 반환된 뒤 발행한다. connection/timeout/topology 계열의
`UNAVAILABLE`만 immediate rediscovery를 요청하고, overload, ACL denial, validation/size rejection은
요청하지 않는다. signal listener의 실패는 원래 command의 `NOT_APPLIED`/`INDETERMINATE` 판정을
절대 덮어쓰지 않는다.
정상 poll은 Sentinel discovery credential/CA만 사용해 endpoint를 조회한다. 현재 route와 같은
primary면 data credential/CA를 해석하거나 새 data connection을 열지 않는다. primary가 달라졌을
때만 이미 quorum-approved/allowlisted 된 exact endpoint로 data candidate를 열어 TOCTOU 성격의
이중 discovery를 피한다. route는 endpoint를 출력하지 않는 package-private identity와 monotonic
generation token을 가진다. candidate qualification 중 다른 rotation이 먼저 완료되면 stale
generation candidate를 닫고 install하지 않는다. 같은 identity도 candidate를 닫고 no-op 처리한다.
#### 37.13.3 replication 보장과 판정
Sentinel은 primary election을 제공하지만 asynchronous replication의 zero-data-loss를 보장하지
않는다. qualification 환경은 correctness role에 `min-replicas-to-write`와 bounded
`min-replicas-max-lag`를 설정하고, 중요한 mutation은 명시된 replica acknowledgement 정책을
사용한다. 이 설정도 strong consistency나 cross-store exactly-once 증거가 아니다.
failover 판정은 다음을 구분한다.
- 응답과 요구된 replica acknowledgement가 확인된 mutation: 새 primary에서 보존되어야 한다;
- response-only cut 또는 acknowledgement 결과를 확인할 수 없는 mutation:
`INDETERMINATE`, blind retry 금지;
- acknowledgement 전 명확한 connection/admission 실패: `NOT_APPLIED`가 wire evidence로
증명되는 경우에만 미실행으로 판정한다.
#### 37.13.4 Sentinel-first R2 qualification lab
이번 Phase 5의 첫 실행 slice는 기존 host k3s를 변경하지 않는 disposable Multipass lab이다.
```text
ca-redis-lab-server 2 CPU / 3 GiB / 12 GiB k3s server
ca-redis-lab-agent-1 2 CPU / 2.5 GiB / 12 GiB k3s agent
ca-redis-lab-agent-2 2 CPU / 2.5 GiB / 12 GiB k3s agent
pod CIDR 10.52.0.0/16
service CIDR 10.53.0.0/16
kube context ca-redis-lab
```
lab kubeconfig와 transient material/raw observation은 Gradle root의 ignored
`src/build/redis-lab` 아래에만 쓰며 사용자의 default kubeconfig에 merge하거나 덮어쓰지 않는다.
host 관측에는 default kubeconfig의 run-scoped copy와 시작 시점의 exact host context를
사용하지만, fingerprint/CIDR 관측이 끝난 즉시 성공/실패와 무관하게 copy를 제거한다. 모든
lab mutating command는 별도 lab kubeconfig와 `ca-redis-lab` context를 함께 요구한다.
VM 이름은 위 exact allowlist만 허용한다. launch 전에 exact name을 run-owned state에
`PENDING`으로 atomic 예약하고 성공 직후 `CREATED`로 승격한다. timeout, partial create,
state 승격 실패는 이 run이 예약한 exact name만 delete/purge한다. global `multipass purge`,
host `kubectl delete`, default-context write는 금지한다.
run-scoped rendered cloud-init은 secret이 아닌 exact `RUN_ID|VM_NAME` ownership marker를
instance에 기록한다. cleanup/down은 bounded marker read가 state owner와 name 일치를
증명할 때만 delete한다. launch timeout/error는 `RECONCILE` tombstone과 bounded late-create
poll로 처리한다. instance가 끝까지 없거나 marker가 unreadable/mismatch면 외부 same-name
instance를 추측해 삭제하지 않고 state를 유지한 채 fail-closed한다.
lifecycle 전체는 nonblocking exclusive lock과 run identity를 사용한다. direct `up`
`run` 모두 첫 launch 전에 emergency cleanup을 활성화하며 signal/concurrent invocation이
다른 run의 state 또는 VM을 채택·삭제하지 못한다. `run -- <command>`에는 lifecycle lock file
descriptor를 상속하지 않는다. K3s는 mutable installer를 pipe로 실행하지 않고 exact release
URL/SHA-256을 repository에 pin한다. host download와 각 VM transfer 뒤 checksum/version을
다시 확인한 후에만 start한다.
기본 bounded external child도 lifecycle lock descriptor를 닫으며 lock acquisition만
명시적인 keep-lock 경로를 사용한다.
`run`의 inner `up` 성공과 user command 시작 사이에도 cleanup-required flag는 연속 유지되며,
signal handler가 ownership을 0으로 보는 handoff gap을 허용하지 않는다.
lab kubeconfig renderer는 one-cluster/context/user schema의 모든 identity-bearing key를
generic count하며 duplicate/extra server, context cluster/user, item/name,
current-context를 last-key-wins로 남기지 않고 fail-closed한다.
#### 37.13.4.1 lab lifecycle 완료 경계와 strict kubeconfig renderer
`Task 11.1A`는 하나의 리뷰 단위로 너무 많은 책임을 가졌으므로 다음 두 하위 작업으로 분리한다.
- `Task 11.1A-1`: VM 이름/소유권 marker, `PENDING|CREATED|RECONCILE` state, lock FD,
signal/handoff cleanup, host fingerprint와 bounded external command를 소유한다.
- `Task 11.1A-2`: pinned K3s admin kubeconfig의 strict validation과 lab 전용 rename/render만
소유한다.
`11.1A-1` 코드는 `11.1A-2` 동안 동결한다. `11.1A-2`가 독립 테스트와 독립 리뷰를 통과하기
전에는 부모 `11.1A`를 완료로 표시하지 않으며 VM 생성도 허용하지 않는다.
`11.1A-2`는 범용 YAML parser가 아니다. 입력은 pinned K3s가 생성하는 admin kubeconfig의
canonical block-style 문서 하나로 제한한다. 별도 tracked
`infra/redis-lab/lib/render-kubeconfig.awk`가 line/indentation/state allowlist를 적용하며,
identity-bearing key를 찾는 denylist나 발견된 mutation별 정규식 패치를 사용하지 않는다.
허용 grammar는 다음을 모두 만족해야 한다.
- top-level `apiVersion`, `clusters`, `contexts`, `current-context`, `kind`, `preferences`,
`users`는 canonical 순서와 exact spelling/indentation으로 한 번만 존재한다;
- cluster/context/user list는 각각 한 항목만 가지며 identity는 모두 exact `default`다;
- cluster는 exact loopback `server: https://127.0.0.1:6443`와 하나의
`certificate-authority-data` scalar만 가진다;
- context는 exact `cluster: default`, `user: default`와 optional single `namespace` scalar만
가진다;
- user는 하나의 `client-certificate-data``client-key-data` scalar만 가진다;
- `preferences: {}`만 유일한 flow collection 예외다. 그 밖의 `{}`, `[]`, quoted/tagged/
explicit key, anchor, alias, merge key, tab, CRLF, YAML document marker, unknown key,
duplicate/reordered identity, trailing content는 fail-closed한다;
- source `server`, cluster/context/user name과 current-context만 변환한다. CA/client material,
namespace와 그 밖의 허용 scalar는 byte-preserving pass-through다;
- renderer source 자체와 destination의 canonical parent/symlink/permission 계약을 lifecycle
static validation에 포함한다. validation 또는 render 실패 시 destination을 제거하고
constant sanitized failure만 출력한다.
정상 fixture는 pinned K3s admin kubeconfig의 certificate-data shape를 사용한다. negative
mutation은 duplicate/extra identity뿐 아니라 canonical item 아래의 sibling
`cluster : {...}`, `context : {...}`, whitespace-before-colon, flow collection, quoted/tagged/
anchor/alias/merge, unknown/reordered/missing key를 포함한다. 모든 실패는 lab `kubectl` 전에
발생하고 현재 invocation이 marker로 증명한 VM만 cleanup하며 prior
`CREATED|RECONCILE` state는 byte-for-byte 보존한다.
tracked `infra/redis-lab`에는 lifecycle script, cloud-init template, Redis/Sentinel config
template, Kubernetes manifest와 secret 없는 contract test만 둔다. 실행 시 생성하는 k3s token,
ACL password, data/Sentinel/untrusted CA와 private key, rendered Secret/config, raw observation은
`umask 077`인 transient directory에만 둔다. `redis-cli --pass`, tracked PEM/Secret data,
`hostPath`/`hostNetwork`/privileged/NodePort/LoadBalancer는 사용하지 않는다.
host isolation은 preflight/postflight의 canonical projection을 비교한다. default kubeconfig
digest, current context/API, sorted node/providerID/podCIDR, controller replica, Service NodePort,
host interface/route CIDR와 Multipass inventory가 대상이다. host service CIDR은 현재 할당된
ClusterIP만 보고 추측하지 않고, 명시적으로 검증한 input 또는 신뢰할 수 있는 host 설정에서
읽는다. 외부 명령과 exact 3-node Ready 대기는 bounded다. 불일치 시 qualification을
실패시키되 script가 host 상태를 추측해 되돌리려고 mutate하지 않는다.
workload는 Redis primary 1 + replica 2, Sentinel 3/quorum 2를 서로 다른 node에 배치한다.
data와 Sentinel은 stable ordinal/headless DNS가 필요한 별도 StatefulSet이며
`kubernetes.io/hostname` required anti-affinity와 `maxSkew=1/DoNotSchedule` topology spread를
사용하고 `podManagementPolicy: Parallel`을 명시한다. data는 PVC와 AOF
`appendfsync everysec`를 사용한다. Sentinel config는 discovery/failover 시 rewrite되므로
bootstrap 원본을 pod별 writable PVC config로 최초 1회 atomic init-copy하되 restart 때 이미
존재하는 rewritten config를 덮어쓰지 않는다. 비어 있거나 손상된 기존 config도 자동으로
덮지 않고 startup을 실패시켜 증거를 보존한다.
data/Sentinel plaintext port는 0이며 TLS port만 연다. `tls-replication yes`, hostname
resolution/announcement와 stable DNS SAN을 사용한다. data plane과 Sentinel plane의 CA/leaf
material은 분리하며 peer 연결에 필요한 root만 explicit trust bundle에 포함한다. ACL은
application data, replica, Sentinel-to-data, Sentinel peer, application Sentinel discovery
identity로 나눈다. Redis data ACL과 Sentinel ACL은 별도 template/projection이며 plane
identity를 서로 노출하지 않는다. default user는 off이며 application/data/discovery
identity에는 `+@all`, `allkeys`, `allchannels`를 주지 않는다. replica는
`+psync +replconf +ping`, Sentinel-to-data identity는 Sentinel control에 필요한 최소
command/channel set만 가진다.
exec probe를 사용하고 default-deny NetworkPolicy 뒤 data 6379, Sentinel 26379, kube-dns,
exact qualification/application pod selector만 허용한다. data/Sentinel PDB는 각각
`minAvailable: 2`이며 non-root, read-only root filesystem, privilege-escalation false,
capability drop ALL, seccomp RuntimeDefault, requests/limits를 요구한다. `hostPath`,
host namespaces, privileged, NodePort/LoadBalancer와 tracked Secret/PEM은 금지한다.
정적 lifecycle contract와 manifest/security contract는 VM 없이 blocking check에서 검증하고,
한 필드씩 제거/변조하는 mutation-negative fixture로 실제 방어력을 확인한다. 이 정적 통과는
TLS handshake, ACL authorization, CNI enforcement, scheduling/failover의 실행 증거가 아니다.
shell contract는 별도 fixture repository만 사용하며 actual `src/build/redis-lab` state를
byte-for-byte 보존한다. fake PATH는 explicit safe wrapper 외 모든 명령을 fail-closed한다.
live lab에서는 TLS/ACL negative test, `SENTINEL CKQUORUM`, writable config rewrite/restart,
exact 3 Ready placement, PDB/NetworkPolicy enforcement와 image ID/digest를 별도로 검증한다.
Redis image는 `src/gradle/redis-test-images.properties``redis.minimum.image` exact
tag+digest를 사용한다.
ordinal bootstrap은 최초 `redis-data-0` primary와 두 replica만 정적으로 증명한다.
failover 동안 죽어 있던 old primary가 재합류할 때 readiness가 stale direct write를 허용하지
않고 새 primary의 replica로 수렴하는지는 live gate다. PDB 선언은 voluntary eviction
제약일 뿐 node/AZ failure 증거가 아니다.
k3s control-plane HA, physical host/AZ failure, Redis Cluster는 이 lab의 증거가 아니다.
hosted GitHub Actions에서는 Multipass를 설치하거나 실행하지 않는다. 실제 lab qualification은
trusted dedicated runner 또는 local explicit execution에서만 허용한다. 외부 PR 코드를
self-hosted lab에서 실행하지 않는다.
초기 test budget은 운영 SLA가 아니라 bounded regression limit이다.
- Sentinel election: 60초 이내;
- client rediscovery와 runtime swap: election 뒤 추가 30초 이내;
- required semantic readiness 복구: fault injection 뒤 총 90초 이내.
실제 측정값을 evidence timeline에 기록하며 limit만 기록한 문서는 증거가 아니다.
#### 37.13.5 Sentinel-first capability acceptance
이 slice는 correctness-sensitive cross-pod state를 우선 검증한다.
- edge rate limit: failover 전 quota state가 조용히 reset되지 않고 evaluation replay가 일관된다;
- request-replay idempotency: claim/start/renew/complete와 terminal replay가 owner-safe하며
불확실 mutation은 중복 실행하지 않는다;
- Redis session: create/read/touch/rotate/revoke가 서로 다른 application pod에서 보이고,
failover 뒤 confirmed state가 유지되며 stale session이 부활하지 않는다;
- cache refresh soft lease와 optional cache는 공통 runtime 회귀를 확인하되 이 slice만으로
Cluster scaling 또는 distributed L1 invalidation R2를 주장하지 않는다.
fault 순서는 baseline qualification 뒤 current primary pod를 kill하고 readiness unavailable,
Sentinel quorum election, client rediscovery, runtime swap/drain, semantic readiness recovery를
실제 timestamp로 수집한다. old primary는 replica로 재합류해야 하고, recovery 뒤 모든 actor가
같은 runtime generation을 관측해야 한다.
evidence bundle은 실제 실행 image digest/image ID, config/program digest, fault/election/recovery
timeline, capability별 outcome/certainty, sanitized Kubernetes/Sentinel observation, lab teardown
결과를 포함한다. manifest의 `NOT_CAPTURED`를 문자열로 바꾸는 것만으로 증거를 만들 수 없다.
### 37.14 Cluster topology
최소 multi-primary Cluster와 replica에서:
@@ -6115,20 +6398,9 @@ canonical card ID와 Gradle task mapping:
registry key, capability descriptor ID, `card-<id>` tag, evidence artifact의 card ID는 이 표와 byte-for-byte
같아야 한다. short alias를 허용하지 않는다.
```yaml
cards:
redis-cache:
state: selected # selected | implemented-candidate | not-implemented
selected-topology: sentinel # standalone | sentinel | cluster
required-evidence:
- standalone
- security
- fault
- compatibility
- selected-topology
redis-session:
state: not-implemented
```
현재 card 상태와 topology/evidence는 이 문서에 복제하지 않으며
`src/config/redis/readiness-cards.yaml`만을 따른다. 현재 `selected` card는 없으며,
`implemented-candidate`는 release selection 또는 R2 qualification을 뜻하지 않는다.
`redis<Card>Readiness` task는 이 registry의 해당 card tag와 required evidence tag의 교집합을
실행하고, category마다 test count > 0, 성공 artifact, image/program/config digest를 요구한다.
@@ -6224,10 +6496,14 @@ nightly `redis-all-candidates`는 `redisAllImplementedCandidates`를 실행한
품질 신호/승격 blocker지만 현재 selected card의 이미 존재하는 release evidence를 다른 card
미구현 때문에 자동 취소하지 않는다.
각 job은 JUnit XML/HTML, container logs, sanitized topology/fault timeline,
`program-set.json`/digest, effective capability card, image digest attestation을 artifact로 올린다.
secret, raw Redis key/value, session/idempotency token은 artifact에 포함하지 않는다. PR artifact
retention은 짧게, release evidence는 조직의 audit retention 정책에 맞춘다.
각 job은 `build/redis-evidence` 아래에서 allowlist schema로 다시 생성한 bounded manifest,
capability card, sanitized test summary만 artifact로 올린다. Gradle의 raw JUnit XML/HTML,
`system-out`/`system-err`, stack trace, container log/inspect, TLS/ACL fixture material은 업로드하지
않는다. 실제 사용 image attestation과 실제 topology/fault event chain을 수집하지 못한 현재
artifact는 각각 `NOT_CAPTURED``releaseQualification=NOT_CLAIMED`를 기록하며, reconciler는
이 상태의 future `selected` 승격을 실패시킨다. secret/reference value, raw endpoint/key/value,
session/idempotency/lease token은 artifact에 포함하지 않는다. Candidate artifact retention은
짧게, 실제 release evidence는 조직의 audit retention 정책에 맞춘다.
### 37.24 no silent skip
@@ -6526,6 +6802,36 @@ Acceptance:
- no silent skip;
- program/ACL/schema conformance.
#### Phase 5A — Sentinel-first R2 qualification slice
Phase 5 전체를 한 번에 구현하지 않는다. 먼저 §37.13의 disposable 3-node k3s Sentinel 환경에서
다음 순서로 진행한다.
1. lab lifecycle/preflight/host-isolation contract를 테스트 우선으로 고정한다;
2. Sentinel discovery와 Redis data runtime을 별도 auth/trust/lifecycle로 구현한다;
3. quorum-consistent discovery, bounded rediscovery, qualified runtime swap와 bounded drain을
구현한다;
4. security positive/negative test 후 rate limit, idempotency, session의 multi-pod 정상 경로를
실행한다;
5. primary kill과 response-loss fault를 주입하고 capability invariant와 `INDETERMINATE`
semantics를 검증한다;
6. image/fault timeline을 실제 관측에서 생성하고 sanitizer/reconciler를 통과시킨다;
7. focused/full Gradle verification과 독립 review를 마친 뒤 이 slice에서 멈춘다.
이번 slice에 포함하지 않는 항목:
- Redis Cluster와 Cluster cache scaling;
- fenced coordination;
- R3 capacity soak/long chaos/reshard;
- k3s control-plane HA, physical host/AZ failure;
- full credential/certificate rotation drill;
- optional cache의 Sentinel release promotion.
이번 slice의 agent-side 종료 상태는 `R2-ready candidate`다. repository가 human-only commit
policy를 사용하므로 clean committed source와 실제 remote GitHub Actions evidence는 사람이
수행하는 최종 promotion gate다. 이 두 증거가 없으면 readiness card를 `selected`로 바꾸거나
R2라고 표시하지 않는다.
### Phase 6 — R3와 split review
- actual Cluster reshard/failover;
@@ -6573,6 +6879,28 @@ Acceptance:
- runbook/capability card;
- LLM Wiki capture.
### 40.2 Sentinel-first slice 종료 게이트
§37.13과 Phase 5A의 작업은 아래가 모두 충족된 경우에만 `R2-ready candidate`로 종료한다.
- exact VM inventory와 dedicated kubeconfig로 lab create/verify/destroy가 반복 가능하다;
- host k3s context, node, workload와 default kubeconfig의 전/후 fingerprint가 같다;
- Sentinel discovery와 Redis data auth/trust가 분리되고 negative security test가 통과한다;
- primary kill 뒤 quorum election, qualified runtime swap, bounded drain과 semantic readiness
recovery의 실제 timeline이 있다;
- rate limit, idempotency, session을 서로 다른 pod에서 검증하고 failover 뒤 invariant가
유지된다;
- confirmed acknowledgement와 `INDETERMINATE`를 구분하며 blind mutation replay가 없다;
- actual image/config/program digest와 sanitized evidence가 reconciler를 통과한다;
- focused test, Redis readiness 관련 task, repository `test`/`check`, architecture/env/public-path
gate와 독립 review가 통과한다;
- exact allowlist VM teardown과 lab resource 정리 결과가 기록된다.
위 조건은 clean committed source와 실제 remote CI를 대신하지 않는다. 두 최종 promotion
증거가 없으면 card 상태는 `implemented-candidate`, `releaseQualification=NOT_CLAIMED`
유지한다. 종료 뒤 Redis Cluster/R3/fenced coordination 또는 fileserver/HTTP client로 자동으로
넘어가지 않고 다음 우선순위를 다시 결정한다.
R3는 추가로:
- failover/partition;