172 lines
18 KiB
Markdown
172 lines
18 KiB
Markdown
# Redis 테스트가 증명하는 것과 증명하지 않는 것
|
|
|
|
> **Redis 코드 상세 시리즈 19/20** · [전체 지도](/home/donghyeon/workspace/ai-tool/document-haness/.run/redis/redis-backend-policy-boundary.md) · 이전: [같은 Redis 장애가 DEGRADED와 DOWN으로 갈리는 코드](/home/donghyeon/workspace/ai-tool/document-haness/.run/redis/redis-health-readiness-observability.md) · 다음: [Redis를 켠다는 말의 운영적 의미: 단일 활성화 스위치에서 Sentinel 쓰기 손실 검증까지](/home/donghyeon/workspace/ai-tool/document-haness/.run/redis/redis-platform-sre-operations.md)
|
|
|
|
## 이 글이 답하는 코드 질문
|
|
|
|
기본 `check`, topology-tagged test, Docker fixture, GitHub Actions matrix, support matrix 문서는 각각 어떤 사실을 증명합니까? Redis 7.2·7.4·8.2와 standalone·Sentinel·Cluster·TLS를 모두 “현재 인증됨”이라고 말할 수 있습니까?
|
|
|
|
아닙니다. 현행 source가 선언하는 CI matrix와 repository가 기록한 historical certification은 구분해야 합니다.
|
|
|
|
- production topology는 standalone, Sentinel, Cluster 세 가지입니다.
|
|
- TLS는 topology가 아니라 standalone shape의 transport qualification lane입니다.
|
|
- historical evidence는 Redis 7.4의 세 topology입니다.
|
|
- TLS 7.4 실행 기록은 infra README에 있습니다.
|
|
- 7.2와 8.2는 workflow에 선언되어 있지만 repository evidence상 declared-only입니다.
|
|
- 이번 문서 작성에서는 어느 real-server lane도 실행하지 않았습니다.
|
|
|
|
## 테스트 층 지도
|
|
|
|
| 층 | 진입점 | 실제로 묻는 질문 | 증명하지 않는 것 |
|
|
| --- | --- | --- | --- |
|
|
| deterministic unit/contract | module `test`·`check` | policy, key rendering, codec, typed outcome, in-memory state transition | Lettuce wire behavior, ACL, failover, redirects, TLS handshake |
|
|
| composition test | `ApplicationContextRunner` | property selector가 어떤 bean을 만들고 startup을 거절하는가 | server connection, command success |
|
|
| topology test | `redisTopologyTest` | real Redis·Lettuce·ACL·topology behavior | 실행하지 않은 version/lane, production SLO |
|
|
| Docker fixture | `infra/redis-sdk/*/compose.yml` | repeatable standalone/Sentinel/Cluster/TLS environment | production persistence·backup·capacity architecture |
|
|
| CI workflow | `redis-sdk-topology.yml` | 어떤 trigger에서 어떤 lane/version을 실행하도록 선언했는가 | 과거 또는 현재 run 성공 자체 |
|
|
| support matrix | `docs/redis/support-matrix.md` | package/version/topology와 historical evidence 기록 | artifact digest의 현재 보존·최근 재실행 |
|
|
|
|
## 기본 test가 사용하는 deterministic gateway
|
|
|
|
cache, rate-limit, lease, idempotency adapter tests는 `InMemoryGatewayAccess`에서 얻은 `RedisCommandGateway`를 `RedisRuntimeOwner`에 넣습니다. 실제 Redis process나 Lettuce socket을 사용하지 않습니다. 이 구조는 state transition과 typed outcome을 빠르고 결정적으로 검사하지만 서버 parser, ACL, replication, cluster redirect는 재현하지 않습니다.
|
|
|
|
예를 들어 [`RedisCacheRegionAdapterTest`](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/cache/RedisCacheRegionAdapterTest.java:36)는 soft/hard TTL, envelope category, generation invalidation, conditional writes를 검사합니다. [`RedisEdgeRateLimitAdapterTest`](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/ratelimit/RedisEdgeRateLimitAdapterTest.java:37)는 세 algorithm과 fail-closed 결과를 고정합니다. [`RedisDistributedLeaseAdapterTest`](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/lease/RedisDistributedLeaseAdapterTest.java:39)와 [`RedisIdempotencyStoreAdapterTest`](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/idempotency/RedisIdempotencyStoreAdapterTest.java:43)는 owner/reply-loss state를 검사합니다.
|
|
|
|
default `test` task는 `redis-topology` tag를 제외합니다. 따라서 module `check`가 성공해도 real server lane이 실행됐다는 뜻은 아닙니다. [`build.gradle` default test](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/build.gradle:44)
|
|
|
|
composition tests도 서버를 연결하지 않습니다. connection lane은 lazy하게 열리며 `ApplicationContextRunner`가 확인하는 것은 bean cardinality와 startup validation입니다. [`RedisCapabilityCompositionTest` class contract](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/redis/RedisCapabilityCompositionTest.java:21)
|
|
|
|
## topology task가 fail-closed하는 방식
|
|
|
|
`redisTopologyTest`는 `standalone`, `sentinel`, `cluster`, `tls`만 allowlist로 받습니다. TLS는 deployment mode로는 standalone에 매핑하고 tag와 trust-material requirement만 TLS lane으로 유지합니다. [`REDIS_TOPOLOGY_MODES`](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/build.gradle:68)
|
|
|
|
```mermaid
|
|
flowchart TD
|
|
A[redisTopologyTest selected] --> B{mode allowlist?}
|
|
B -->|no| X[Gradle failure]
|
|
B -->|yes| C{required endpoint properties?}
|
|
C -->|no| X
|
|
C -->|yes| D{lane tag class exists?}
|
|
D -->|no| X
|
|
D -->|yes| E[run redis-topology AND lane-mode]
|
|
E --> F{executed count >= floor?}
|
|
F -->|no| X
|
|
F -->|yes| G{required classes all ran?}
|
|
G -->|no| X
|
|
G -->|yes| H{skipped == 0?}
|
|
H -->|no| X
|
|
H -->|yes| I[pass]
|
|
```
|
|
|
|
필수 property는 모든 lane의 host/port, Sentinel의 master, TLS의 trust material입니다. unknown mode, missing endpoint, 해당 tag class 없음, 0 tests 모두 실행 전에 실패합니다. [`doFirst`](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/build.gradle:131)
|
|
|
|
실행 뒤에는 required class와 minimum test count를 검사합니다.
|
|
|
|
| lane | required class | 최소 실행 수 |
|
|
| --- | --- | --- |
|
|
| standalone | `LiveRedisCompositionTest`, `LiveRedisSemanticPortsTest`, `RedisTopologyContractTest`, `LiveRedisGuardrailTest` | 20 |
|
|
| sentinel | `LiveRedisCompositionTest`, `LiveRedisSentinelPromotionTest`, `RedisTopologyContractTest` | 20 |
|
|
| cluster | `LiveRedisCompositionTest`, `LiveRedisClusterTest`, `LiveRedisClusterTransactionTest`, `LiveRedisSemanticPortsTest` | 24 |
|
|
| tls | `LiveRedisTlsTest` | 4 |
|
|
|
|
이 선언은 [`REDIS_TOPOLOGY_REQUIRED_CLASSES`와 `MINIMUM_TESTS`](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/build.gradle:74)에 있습니다. skipped test 하나라도 있으면 task가 실패합니다. class 이름과 count를 함께 쓰므로 trivial test 하나만 남은 lane이 green이 되는 일을 막습니다.
|
|
|
|
## 네 fixture가 제공하는 환경
|
|
|
|
### standalone
|
|
|
|
[`standalone/compose.yml`](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/infra/redis-sdk/standalone/compose.yml:7)은 Redis 한 대, AOF/save 없음, 공통 ACL file, published 6379를 사용합니다. persistence나 replication을 검증하는 fixture가 아닙니다.
|
|
|
|
### Sentinel
|
|
|
|
[`sentinel/compose.yml`](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/infra/redis-sdk/sentinel/compose.yml:28)은 data node 두 대와 sentinel 세 대를 host network에 둡니다. data node는 role이 바뀌어도 같은 설정을 쓰도록 anchor를 공유하고 `min-replicas-to-write 1`, `min-replicas-max-lag 1`을 적용합니다. sentinel quorum은 2이며 down-after 2000ms, failover timeout 10000ms입니다.
|
|
|
|
host network가 필요한 이유는 Sentinel이 proxy가 아니라 새 primary address를 알려 주고 client가 직접 연결하기 때문입니다. bridge 내부 address를 반환하면 host의 test client가 접근할 수 없습니다.
|
|
|
|
### Cluster
|
|
|
|
[`cluster/compose.yml`](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/infra/redis-sdk/cluster/compose.yml:24)은 primary 3, replica 3인 6-node cluster입니다. 7100~7105와 cluster bus를 host network에 열고, init helper가 `--cluster-replicas 1`로 slot을 배치합니다. 별도 `ready` service가 authenticated `cluster_state:ok`까지 기다립니다. node health만으로는 slot assignment 완료를 증명할 수 없기 때문입니다.
|
|
|
|
### TLS
|
|
|
|
[`tls/compose.yml`](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/infra/redis-sdk/tls/compose.yml:11)은 standalone shape입니다. ephemeral CA/server certificate를 만들고 plaintext `--port 0`, TLS port만 켭니다. 따라서 client가 plaintext로 fallback하면 lane이 통과할 수 없습니다. client certificate authentication은 끄고 server certificate/trust/hostname path를 검증합니다.
|
|
|
|
## real-server test가 맡는 증거
|
|
|
|
`RedisTopologyContractTest`는 real server에서 PING, ACL account 존재, blocked command denial, RAW_ONLY/Admin/TYPED/script account 분리를 검사합니다. 특히 advanced account는 `EVALSHA`만 허용하고 `EVAL`은 허용하지 않습니다. [`scriptPathIsAdvancedOnly`](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/RedisTopologyContractTest.java:197)
|
|
|
|
`LiveRedisSemanticPortsTest`는 standalone과 cluster에서 cache read/write, rate-limit enforcement, idempotency first/second claim, lease contention을 advanced/application ACL account로 호출합니다. [`LiveRedisSemanticPortsTest` tags](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/LiveRedisSemanticPortsTest.java:68) Session은 이 class에 없습니다.
|
|
|
|
`LiveRedisSentinelPromotionTest`는 promotion과 acknowledged-write-loss 경계를 관찰합니다. support matrix의 historical 기록에 따르면 guardrail 적용 전에는 superseded primary가 2,086 writes를 success로 응답한 뒤 잃었고, `min-replicas-*` 적용 후 같은 유형의 loss가 1로 줄었습니다. 이는 현행 코드를 이번에 재실행해 얻은 수치가 아니라 repository에 남은 historical evidence입니다. [`support matrix Sentinel evidence`](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/docs/redis/support-matrix.md:104)
|
|
|
|
`LiveRedisClusterTest`는 client slot 계산과 server `CLUSTER KEYSLOT`, cross-slot 양방향 refusal, MOVED/ASK/TRYAGAIN 관찰을 맡습니다. `LiveRedisClusterTransactionTest`는 cluster transaction lane의 slot 제약을 맡습니다.
|
|
|
|
`LiveRedisTlsTest`는 filesystem/classpath CA로 handshake 후 PING, unreadable trust material startup failure, TLS-only server에 plaintext로 연결 실패를 검사합니다. [`LiveRedisTlsTest`](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/LiveRedisTlsTest.java:33)
|
|
|
|
## CI version matrix: 선언과 증거를 분리합니다
|
|
|
|
GitHub Actions workflow는 trigger에 따라 matrix를 계산합니다.
|
|
|
|
- pull request: standalone 7.4 한 lane
|
|
- schedule: standalone/Sentinel/Cluster 각각 7.2, 7.4, 8.2와 TLS 7.4, 8.2
|
|
- manual release-candidate: schedule과 같은 full matrix
|
|
- manual normal: 입력한 topology/version 한 조합
|
|
|
|
근거는 [`redis-sdk-topology.yml matrix selection`](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/.github/workflows/redis-sdk-topology.yml:65)입니다. workflow는 image tag뿐 아니라 resolved image digest와 commit SHA를 JUnit artifact에 기록하고 90일 보존을 선언합니다. [`evidence manifest/upload`](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/.github/workflows/redis-sdk-topology.yml:164)
|
|
|
|
하지만 workflow YAML에 row가 있다는 사실은 row가 성공했다는 증거가 아닙니다. source 안의 support matrix는 “세 topology는 7.4에서 실행됐고 7.2/8.2는 실행되지 않았다”고 명시합니다. [`Certified versions`](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/docs/redis/support-matrix.md:53)
|
|
|
|
따라서 현행 qualification 표현은 다음과 같이 제한해야 합니다.
|
|
|
|
| 대상 | 현재 말할 수 있는 상태 |
|
|
| --- | --- |
|
|
| standalone 7.4 | historical certified evidence 기록 있음 |
|
|
| Sentinel 7.4 | historical certified evidence 기록 있음 |
|
|
| Cluster 7.4 | historical certified evidence 기록 있음 |
|
|
| TLS 7.4 | infra README에 실행 기록 있음; support matrix certified topology table에는 별도 row 없음 |
|
|
| 7.2 | CI declared-only |
|
|
| 8.2 | CI declared-only |
|
|
| TLS 8.2 | CI declared-only |
|
|
|
|
infra README는 “all four have now run on Redis 7.4”라고 기록합니다. [`infra/redis-sdk/README.md`](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/infra/redis-sdk/README.md:7) 이 문구를 TLS historical evidence로 사용할 수 있지만, 현재 run artifact를 이 작업에서 확인한 것은 아닙니다.
|
|
|
|
## support matrix gate의 범위와 drift
|
|
|
|
`RedisSupportMatrixTest`는 구현된 SDK package와 enum capability가 표에 모두 있는지, topology evidence cell이 실제 test class 이름을 가리키는지 검사합니다. [`RedisSupportMatrixTest`](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/RedisSupportMatrixTest.java:42)
|
|
|
|
그러나 test class가 존재한다고 해당 version의 run artifact가 존재하는 것은 아닙니다. 이 gate는 evidence claim의 형식과 source reference를 검사하지만 workflow history는 조회하지 않습니다.
|
|
|
|
문서 drift도 있습니다.
|
|
|
|
- support matrix는 Lettuce `6.8.2`라고 쓰지만 lockfile은 `6.8.1.RELEASE`입니다. [`gradle.lockfile`](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/gradle.lockfile:44)
|
|
- support matrix module row는 connection을 “five lanes”라고 쓰지만 현행 `RedisConnectionKind`에는 REGULAR/BLOCKING/TRANSACTION/SCRIPT/PUBSUB/ADMIN 여섯 lane이 있습니다.
|
|
- CI quality gate 주석은 real-server lane이 “아직 없다”고 하지만 별도 topology workflow가 이미 존재합니다. [`ci-quality-gates.yml`](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/.github/workflows/ci-quality-gates.yml:98)
|
|
- topology workflow는 nightly 7.2/7.4/8.2를 선언하지만 support matrix의 Sentinel/Cluster declared versions에는 7.2가 빠져 있습니다.
|
|
|
|
이런 drift 때문에 README나 table 하나만으로 current implementation을 판정하면 안 됩니다. production/test/lock/workflow를 먼저 보고 historical 문서는 qualification label에만 사용해야 합니다.
|
|
|
|
## 이번 작업에서 실행한 것과 실행하지 않은 것
|
|
|
|
이번 문서 작성은 source HEAD `3b5aee50e33c44c02d08c94bb39ad34814482010`을 정적으로 조사했습니다. root의 이전 세션에서 기본 module test가 성공했다는 공통 전제는 있지만, 이 작성자가 default Gradle tests나 standalone/Sentinel/Cluster/TLS lane을 새로 실행하지 않았습니다.
|
|
|
|
따라서 이 글은 test code가 고정한 계약, fixture와 CI가 선언한 실행 방식, repository에 기록된 historical evidence를 설명합니다. 현재 외부 CI run의 green 상태나 image digest는 확인하지 않았습니다.
|
|
|
|
## 현재 공백과 다음 source 순서
|
|
|
|
1. real-server semantic test는 cache/rate-limit/idempotency/lease를 다루지만 Session은 다루지 않습니다.
|
|
2. rate-limit live test 주석은 evaluation dedupe를 주장하지만 production Lua가 evaluation ID를 소비하지 않습니다. test 자체도 dedupe assertion을 하지 않습니다.
|
|
3. support-matrix test는 artifact provenance를 조회하지 않으므로 “test class 존재”와 “version certified” 사이에 사람이 유지하는 historical 기록이 남습니다.
|
|
4. Docker fixtures는 production architecture가 아닙니다. persistence, backup, capacity, multi-region을 증명하지 않습니다.
|
|
5. minimum test count는 coverage shrink guard이지 statement/branch coverage 수치가 아닙니다.
|
|
6. 이번 작업은 real-server current qualification을 갱신하지 않았습니다.
|
|
|
|
`build.gradle` task → topology workflow → 각 compose → tagged test → support matrix와 gate test 순으로 읽으면 선언, 실행 계약, historical evidence를 분리할 수 있습니다.
|
|
|
|
## 시리즈에서 이어 읽기
|
|
|
|
- 이전 글: [같은 Redis 장애가 DEGRADED와 DOWN으로 갈리는 코드](/home/donghyeon/workspace/ai-tool/document-haness/.run/redis/redis-health-readiness-observability.md)
|
|
- 다음 글: [Redis를 켠다는 말의 운영적 의미: 단일 활성화 스위치에서 Sentinel 쓰기 손실 검증까지](/home/donghyeon/workspace/ai-tool/document-haness/.run/redis/redis-platform-sre-operations.md)
|
|
- 전체 흐름: [Redis를 범용 클라이언트가 아니라 정책 경계로 다루기](/home/donghyeon/workspace/ai-tool/document-haness/.run/redis/redis-backend-policy-boundary.md)
|
|
- 운영 흐름: [Redis를 켠다는 말의 운영적 의미: 단일 활성화 스위치에서 Sentinel 쓰기 손실 검증까지](/home/donghyeon/workspace/ai-tool/document-haness/.run/redis/redis-platform-sre-operations.md)
|
|
|