187 lines
22 KiB
Markdown
187 lines
22 KiB
Markdown
# Raw key와 영구 쓰기를 막는 코드: Namespace·Hash Slot·TTL
|
|
|
|
> **Redis 코드 상세 시리즈 08/20** · [전체 지도](/home/donghyeon/workspace/ai-tool/document-haness/.run/redis/redis-backend-policy-boundary.md) · 이전: [YAML 한 줄이 Redis 명령을 거절하기까지: Policy Loader·Catalog·Guard](/home/donghyeon/workspace/ai-tool/document-haness/.run/redis/redis-command-policy-admission.md) · 다음: [Redis 값의 스키마를 코드로 고정하기: Registry·Envelope·Version](/home/donghyeon/workspace/ai-tool/document-haness/.run/redis/redis-codec-schema-evolution.md)
|
|
|
|
## 이 글이 답하는 코드 질문
|
|
|
|
호출자가 Redis key 문자열을 직접 만들지 못하게 하는 경계는 어디이며, expiry 없는 쓰기는 어떤 코드에서 거절됩니까?
|
|
|
|
현행 구현의 답은 둘로 나뉩니다.
|
|
|
|
- typed API는 `QualifiedRedisKey`만 받아 namespace, key grammar, UTF-8 byte 상한, Cluster slot을 검사합니다.
|
|
- ordinary value `SET` 계열·nontransactional increment와 `PERSIST`는 expiry 또는 `PersistentKeyPermit`을 검증하지만, 모든 value·transaction·collection write가 이 경계를 지나지는 않습니다.
|
|
|
|
따라서 “raw key를 typed API에서 막는다”는 주장은 source로 확인되지만, “모든 영구 쓰기를 막는다”는 주장은 현재 구현 전체에는 맞지 않습니다.
|
|
|
|
## 먼저 보는 클래스·리소스 지도
|
|
|
|
| 클래스 | 입력 | 출력 | 다음 호출 |
|
|
|---|---|---|---|
|
|
| [RedisNamespace](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/key/RedisNamespace.java:13) | environment, service, domain | namespace prefix | `QualifiedRedisKey` |
|
|
| [QualifiedRedisKey](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/key/QualifiedRedisKey.java:16) | namespace, name, optional slot tag | 논리 key | renderer·guard |
|
|
| [RedisKeyRenderer](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/key/RedisKeyRenderer.java:16) | qualified key | wire key 또는 slot source | gateway·slot calculator |
|
|
| [RedisKeyRules](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/key/RedisKeyRules.java:16) | key part, rendered key | 검증된 문자열 | key value object |
|
|
| [Expiration](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/operations/Expiration.java:15) | permit, duration, instant | persistent/relative/absolute expiry | value request builder |
|
|
| [RedisOperationContext](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/operations/RedisOperationContext.java:119) | namespace, renderer, verifier, authority, limits | render·encode·permit helper | operation request builder |
|
|
| [KeyOperationRequests](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/operations/KeyOperationRequests.java:113) | key와 expiry 변경 요청 | guarded `CommandRequest` | executor |
|
|
|
|
## Key는 문자열이 아니라 구조입니다
|
|
|
|
`RedisNamespace`는 세 token을 가집니다.
|
|
|
|
```text
|
|
environment : service : domain
|
|
```
|
|
|
|
[prefix](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/key/RedisNamespace.java:21)는 `prod:order:shared` 같은 prefix를 만듭니다. 각 token은 lower-case alphanumeric과 `-`만 허용하며 길이는 1..64자입니다.
|
|
|
|
`QualifiedRedisKey`는 다음을 묶습니다.
|
|
|
|
- `RedisNamespace`
|
|
- entity와 identifier를 가진 `RedisKeyName`
|
|
- 선택적인 `RedisSlotTag`
|
|
|
|
typed operation signature에는 이미 render된 `String key`가 없습니다. [QualifiedRedisKey의 경계](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/key/QualifiedRedisKey.java:6)는 namespace와 slot 검사를 건너뛸 public typed path를 만들지 않습니다.
|
|
|
|
## Renderer가 고정하는 wire 형식
|
|
|
|
[RedisKeyRenderer.render](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/key/RedisKeyRenderer.java:39)는 두 형식만 만듭니다.
|
|
|
|
```text
|
|
plain: environment:service:domain:entity:identifier
|
|
tagged: environment:service:domain:{slotTag}:entity:identifier
|
|
```
|
|
|
|
brace는 caller가 넣지 않고 renderer만 넣습니다. `RedisSlotTag` 자체는 [RedisKeyRules.requireIdentifier](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/key/RedisSlotTag.java:12)을 통과해야 하므로 nested brace나 separator를 넣을 수 없습니다.
|
|
|
|
`slotSource`는 tagged key에서 tag value만 반환하고, plain key에서는 전체 rendered key를 반환합니다. 이 값이 Redis Cluster CRC16 계산 입력입니다.
|
|
|
|
## Key rule이 잡는 것과 잡지 못하는 것
|
|
|
|
[RedisKeyRules](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/key/RedisKeyRules.java:18)은 rendered key의 hard maximum을 512 UTF-8 bytes로 둡니다. 실제 renderer는 deployment가 설정한 `maxKeyBytes`가 1..512 범위인지 먼저 검사합니다.
|
|
|
|
identifier는 다음 조건을 만족해야 합니다.
|
|
|
|
- 1..128자
|
|
- 첫 글자는 alphanumeric
|
|
- 나머지는 `[A-Za-z0-9._~-]`
|
|
- `:` separator 금지
|
|
- 인식 가능한 mail address, JWT, international phone, `bearer`/`eyj` prefix 금지
|
|
|
|
이 검사는 구조적으로 알아볼 수 있는 민감 정보만 거절합니다. `42` 같은 bare digit나 이미 fingerprint된 surrogate id는 개인 정보인지 기계적으로 판별할 수 없으므로 허용합니다. caller가 원본 식별자를 fingerprint해야 하는 책임은 남습니다.
|
|
|
|
## Request-time key 검증 순서
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant A as Application
|
|
participant T as Typed operation
|
|
participant C as RedisOperationContext
|
|
participant G as CommandPolicyGuard
|
|
participant S as Slot calculator
|
|
participant L as Lettuce gateway
|
|
A->>T: ValueKey/HashKey/... 전달
|
|
T->>C: renderKey(QualifiedRedisKey)
|
|
C-->>T: UTF-8 wire bytes
|
|
T->>G: CommandRequest(keys, deferred invocation)
|
|
G->>G: bound namespace 비교
|
|
G->>S: slotSource 계산
|
|
S-->>G: slot
|
|
G-->>T: admission
|
|
T->>L: deferred command 실행
|
|
```
|
|
|
|
operation request builder가 먼저 render하더라도 guard는 [requireNamespace](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/command/CommandPolicyGuard.java:179)에서 각 key의 namespace를 process-bound namespace와 다시 비교하고 render합니다.
|
|
|
|
여러 key가 하나의 slot에 있어야 하는지는 topology에 따라 다릅니다. [requireSameSlot](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/command/CommandPolicyGuard.java:188)은 Cluster에서만 여러 slot을 `RedisCrossSlotException`으로 거절합니다. standalone과 Sentinel은 여러 slot 개념으로 요청을 막지 않습니다.
|
|
|
|
## Expiration은 세 상태를 표현합니다
|
|
|
|
[Expiration](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/operations/Expiration.java:15)은 sealed interface입니다.
|
|
|
|
| variant | 뜻 | constructor 검사 |
|
|
|---|---|---|
|
|
| `Expiration.Persistent` | expiry 없음 | non-null permit 필수 |
|
|
| `Expiration.After` | 상대 TTL | positive `Duration` 필수 |
|
|
| `Expiration.At` | 절대 expiry | non-null `Instant` 필수 |
|
|
|
|
중요한 점은 `Persistent`에 아무 marker permit이나 넣는다고 끝나지 않는다는 것입니다. [requireExpirationPermit](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/operations/RedisOperationContext.java:243)이 `Persistent`를 발견하면 `persistent-key` policy에 대해 verifier를 호출합니다.
|
|
|
|
이 검사는 guard가 아니라 operation context에 있습니다. `SET`과 `PERSIST`는 catalog에서 R1이므로 guard의 R2 permit 검사에 걸리지 않습니다. [그 이유를 적은 코드](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/operations/RedisOperationContext.java:225)가 별도 경계를 둔 이유를 설명합니다.
|
|
|
|
## Value write의 호출 순서
|
|
|
|
`LettuceRedisValueOperations.set`은 [ValueOperationRequests.set](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/operations/ValueOperationRequests.java:80)으로 위임합니다.
|
|
|
|
1. key, value, expiration이 null인지 검사합니다.
|
|
2. `Expiration.Persistent`이면 permit provenance를 검증합니다.
|
|
3. key를 render합니다.
|
|
4. codec으로 value를 encode하고 byte ceiling을 검사합니다.
|
|
5. `SET` `CommandRequest`를 만듭니다.
|
|
6. invocation에는 `gateway.set(..., expiration)`을 지연 저장합니다.
|
|
7. executor가 guard admission 후 invocation을 실행합니다.
|
|
|
|
`setIfAbsent`, `setIfPresent`, `getAndSet`, `getAndExpire`도 같은 expiration 경계를 사용합니다. nontransactional integer/double increment는 persistent면 `INCRBY`/`INCRBYFLOAT`, expiring이면 TTL을 함께 다루는 등록 script로 분기합니다. [increment 분기](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/operations/ValueOperationRequests.java:139)를 보면 expiry를 increment 뒤 별도 명령으로 붙이는 race를 피합니다.
|
|
|
|
이 설명은 value API의 모든 write로 넓힐 수 없습니다. [APPEND request](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/operations/ValueOperationRequests.java:182)와 [SETRANGE request](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/operations/ValueOperationRequests.java:226)는 expiration이나 persistent permit을 받지 않습니다. 두 Redis 명령은 absent key를 새 string으로 만들 수 있으므로 TTL 없는 key가 생길 수 있습니다.
|
|
|
|
transaction queue도 별도 경계입니다. transaction의 `set`은 expiration을 받지만 [queued increment](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/programmability/LettuceRedisTransactionOperations.java:240)은 plain `INCRBY`만 enqueue합니다. 이어지는 hash/list/set/zset write도 expiry나 permit 없이 absent key를 만들 수 있습니다. nontransactional increment가 expiry-aware script로 분기한다는 계약을 transaction increment에 적용하면 안 됩니다.
|
|
|
|
## Expiry 변경 API의 정상·실패 분기
|
|
|
|
[ExpirationCondition](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/operations/ExpirationCondition.java:4)은 `ALWAYS`, `IF_NO_EXPIRY`, `IF_HAS_EXPIRY`, `IF_GREATER`, `IF_LESS`를 노출합니다.
|
|
|
|
[ExpirationResult](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/operations/ExpirationResult.java:4)은 결과를 `APPLIED`, `CONDITION_NOT_MET`, `ABSENT`, `DELETED`로 구분합니다.
|
|
|
|
### 상대 TTL
|
|
|
|
[KeyOperationRequests.expire](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/operations/KeyOperationRequests.java:113)은 0 또는 음수 TTL을 전송하지 않습니다. Redis가 즉시 삭제하도록 맡기는 대신 “삭제는 명시적으로 호출하라”고 SDK에서 거절합니다.
|
|
|
|
### 절대 expiry
|
|
|
|
`expireAt`은 현재 시각보다 과거인지 request builder에서 계산하고, server가 적용했다고 답하면 `DELETED`로 매핑합니다. 이 비교는 [Instant.now 사용 지점](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/operations/KeyOperationRequests.java:134)에 있으며 injected `Clock`을 쓰지 않습니다.
|
|
|
|
### 영구 전환
|
|
|
|
`persist`는 [permit 검증 후 `PERSIST`](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/operations/KeyOperationRequests.java:167)을 만듭니다. 위조 permit이면 server에 가지 않습니다.
|
|
|
|
## Raw gateway에서도 key 검사가 사라지지 않습니다
|
|
|
|
raw surface는 아무 byte sequence나 통과시키는 우회로가 아닙니다. [LettuceRedisRawGateway](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/raw/LettuceRedisRawGateway.java:89)는 policy `KeySpec`으로 key argument 위치를 찾고 `RedisOperationContext.parseKey`로 다시 qualified key를 만듭니다.
|
|
|
|
[parseKey](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/operations/RedisOperationContext.java:165)는 bound namespace prefix가 아니거나 key grammar가 틀리면 거절합니다. movable key 위치를 결정할 수 없는 shape도 best guess하지 않습니다.
|
|
|
|
## 테스트가 고정하는 계약
|
|
|
|
renderer 테스트는 [slot tag의 brace 위치](/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/api/key/RedisKeyRendererTest.java:13), [plain key 형식](/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/api/key/RedisKeyRendererTest.java:24), [tagged key의 공통 slot source](/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/api/key/RedisKeyRendererTest.java:34), [configured byte ceiling 초과 거절](/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/api/key/RedisKeyRendererTest.java:45), [1..512 밖의 maximum 거절](/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/api/key/RedisKeyRendererTest.java:58)을 각각 고정합니다.
|
|
|
|
key rule 테스트는 [mail](/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/api/key/RedisKeyRulesTest.java:11), [JWT/auth material](/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/api/key/RedisKeyRulesTest.java:17), [international phone](/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/api/key/RedisKeyRulesTest.java:30), [separator injection](/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/api/key/RedisKeyRulesTest.java:36), [malformed namespace](/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/api/key/RedisKeyRulesTest.java:46)를 거절하고 [ordinary surrogate identifier](/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/api/key/RedisKeyRulesTest.java:55)는 허용한다고 고정합니다.
|
|
|
|
guard 쪽에서는 [foreign namespace가 전송되지 않는 사례](/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/lettuce/command/CommandPolicyGuardTest.java:158), [Cluster cross-slot의 client-side 거절](/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/lettuce/command/CommandPolicyGuardTest.java:168), [standalone의 slot 불일치 허용](/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/lettuce/command/CommandPolicyGuardTest.java:194)을 서로 다른 테스트가 고정합니다.
|
|
|
|
[RedisRawGatewayContractTest의 namespace 사례](/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/lettuce/operations/RedisRawGatewayContractTest.java:140)는 raw key도 parse-back과 namespace 검사를 통과해야 한다고 고정합니다.
|
|
|
|
이 테스트는 이번 문서 작업에서 실행하지 않았고 정적으로 읽었습니다.
|
|
|
|
## 현재 구현 공백과 잘못 읽기 쉬운 지점
|
|
|
|
1. `Expiration` Javadoc은 “every write”를 말하지만 TTL 의무는 전체 typed write에 완결되지 않았습니다. APPEND, SETRANGE, transaction INCRBY, transaction의 collection write뿐 아니라 [RedisHashOperations.put](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/operations/RedisHashOperations.java:35)과 [RedisListOperations.pushLeft](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/operations/RedisListOperations.java:18)도 expiration이나 persistent permit을 받지 않습니다.
|
|
2. `Expiration.At` constructor는 과거 시각을 거절하지 않습니다. `expireAt` 결과가 `DELETED`일 수 있습니다.
|
|
3. raw gateway는 approved command만 받지만, production raw approvals와 gateway bean 조립은 확인되지 않습니다.
|
|
4. aggregate `RedisOperations` production bean도 확인되지 않으므로 typed key 경계가 실제 application entry point로 조립됐다고 단정할 수 없습니다.
|
|
5. key rule은 인식 가능한 민감 정보만 잡습니다. caller-side pseudonymization 책임이 남습니다.
|
|
|
|
다음에 source를 열 때는 `RedisNamespace`, `QualifiedRedisKey`, renderer, rules, `RedisOperationContext`, value/key request builder 순으로 보면 됩니다.
|
|
|
|
## 시리즈의 관련 문서
|
|
|
|
관련 범위는 command admission, codec schema, typed operations, raw surface입니다.
|
|
|
|
## 시리즈에서 이어 읽기
|
|
|
|
- 이전 글: [YAML 한 줄이 Redis 명령을 거절하기까지: Policy Loader·Catalog·Guard](/home/donghyeon/workspace/ai-tool/document-haness/.run/redis/redis-command-policy-admission.md)
|
|
- 다음 글: [Redis 값의 스키마를 코드로 고정하기: Registry·Envelope·Version](/home/donghyeon/workspace/ai-tool/document-haness/.run/redis/redis-codec-schema-evolution.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)
|
|
|