18 KiB
Redis 테스트가 증명하는 것과 증명하지 않는 것
Redis 코드 상세 시리즈 19/20 · 전체 지도 · 이전: 같은 Redis 장애가 DEGRADED와 DOWN으로 갈리는 코드 · 다음: Redis를 켠다는 말의 운영적 의미: 단일 활성화 스위치에서 Sentinel 쓰기 손실 검증까지
이 글이 답하는 코드 질문
기본 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는 soft/hard TTL, envelope category, generation invalidation, conditional writes를 검사합니다. RedisEdgeRateLimitAdapterTest는 세 algorithm과 fail-closed 결과를 고정합니다. RedisDistributedLeaseAdapterTest와 RedisIdempotencyStoreAdapterTest는 owner/reply-loss state를 검사합니다.
default test task는 redis-topology tag를 제외합니다. 따라서 module check가 성공해도 real server lane이 실행됐다는 뜻은 아닙니다. build.gradle default test
composition tests도 서버를 연결하지 않습니다. connection lane은 lazy하게 열리며 ApplicationContextRunner가 확인하는 것은 bean cardinality와 startup validation입니다. RedisCapabilityCompositionTest class contract
topology task가 fail-closed하는 방식
redisTopologyTest는 standalone, sentinel, cluster, tls만 allowlist로 받습니다. TLS는 deployment mode로는 standalone에 매핑하고 tag와 trust-material requirement만 TLS lane으로 유지합니다. REDIS_TOPOLOGY_MODES
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
실행 뒤에는 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에 있습니다. skipped test 하나라도 있으면 task가 실패합니다. class 이름과 count를 함께 쓰므로 trivial test 하나만 남은 lane이 green이 되는 일을 막습니다.
네 fixture가 제공하는 환경
standalone
standalone/compose.yml은 Redis 한 대, AOF/save 없음, 공통 ACL file, published 6379를 사용합니다. persistence나 replication을 검증하는 fixture가 아닙니다.
Sentinel
sentinel/compose.yml은 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은 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은 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
LiveRedisSemanticPortsTest는 standalone과 cluster에서 cache read/write, rate-limit enforcement, idempotency first/second claim, lease contention을 advanced/application ACL account로 호출합니다. LiveRedisSemanticPortsTest tags 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
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
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입니다. workflow는 image tag뿐 아니라 resolved image digest와 commit SHA를 JUnit artifact에 기록하고 90일 보존을 선언합니다. evidence manifest/upload
하지만 workflow YAML에 row가 있다는 사실은 row가 성공했다는 증거가 아닙니다. source 안의 support matrix는 “세 topology는 7.4에서 실행됐고 7.2/8.2는 실행되지 않았다”고 명시합니다. Certified versions
따라서 현행 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 이 문구를 TLS historical evidence로 사용할 수 있지만, 현재 run artifact를 이 작업에서 확인한 것은 아닙니다.
support matrix gate의 범위와 drift
RedisSupportMatrixTest는 구현된 SDK package와 enum capability가 표에 모두 있는지, topology evidence cell이 실제 test class 이름을 가리키는지 검사합니다. RedisSupportMatrixTest
그러나 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 - 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 - 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 순서
- real-server semantic test는 cache/rate-limit/idempotency/lease를 다루지만 Session은 다루지 않습니다.
- rate-limit live test 주석은 evaluation dedupe를 주장하지만 production Lua가 evaluation ID를 소비하지 않습니다. test 자체도 dedupe assertion을 하지 않습니다.
- support-matrix test는 artifact provenance를 조회하지 않으므로 “test class 존재”와 “version certified” 사이에 사람이 유지하는 historical 기록이 남습니다.
- Docker fixtures는 production architecture가 아닙니다. persistence, backup, capacity, multi-region을 증명하지 않습니다.
- minimum test count는 coverage shrink guard이지 statement/branch coverage 수치가 아닙니다.
- 이번 작업은 real-server current qualification을 갱신하지 않았습니다.
build.gradle task → topology workflow → 각 compose → tagged test → support matrix와 gate test 순으로 읽으면 선언, 실행 계약, historical evidence를 분리할 수 있습니다.