165 lines
9.6 KiB
Markdown
165 lines
9.6 KiB
Markdown
# module-04-vm1. Redis ВМ1 HAN Chat
|
||
|
||
> Статус: целевая спецификация Redis на ВМ1.
|
||
> Канонический контракт (ключи, TTL, Lua, AOF, ACL, eviction) — [`arch-09-redis.md`](../../architectory/arch-09-redis.md).
|
||
> Обязательный host/container hardening baseline — [`arch-06-service-hosting-security.md`](../../architectory/arch-06-service-hosting-security.md).
|
||
> Redis Safety ВМ2 — [`module-04-redis-vm2.md`](../../VM2_services/documentation/module-04-redis-vm2.md). Hostname Redis ВМ2 не используется.
|
||
|
||
## 1. Назначение и границы
|
||
|
||
Redis ВМ1 обслуживает `api-backend`: DB0 (rate/idempotency) и DB1 (realtime/coordination). OTP counters здесь нет. Message Safety cache/rate/wakeup — на ВМ2.
|
||
|
||
Legacy DB2 на ВМ1 существует только для test stub v1 до cutover и после него удаляется. Production v2 не хранит Safety task state в этом Redis.
|
||
|
||
Durable idempotency/outbox/checkpoint — [`module-01-api-backend.md`](module-01-api-backend.md).
|
||
|
||
## 2. URL и ACL
|
||
|
||
```text
|
||
REDIS_URL=redis://api_backend:<secret>@redis:6379/0
|
||
REDIS_REALTIME_URL=redis://api_backend:<secret>@redis:6379/1
|
||
```
|
||
|
||
Только на ВМ1. `api_backend` ACL: prefixes `han:api:*`, `han:rt:*`, `han:coord:*`, нужные command categories. `SELECT` запрещён. `MESSAGE_SAFETY_REDIS_URL` на ВМ1 после cutover отсутствует.
|
||
|
||
## 3. DB0: API rate limiting
|
||
|
||
| Key | Тип/value | TTL |
|
||
|---|---|---|
|
||
| `han:api:rl:user:{user_id}:{route_hash}:{window}` | ZSET timestamps либо counter | window + jitter |
|
||
| `han:api:rl:ip:{ip_hmac}:{route_hash}:{window}` | ZSET/counter | window + jitter |
|
||
| `han:api:rl:dialog:{dialog_id}:message:{window}` | ZSET/counter | window + jitter |
|
||
| `han:api:rl:service:{service}:{route_hash}:{window}` | counter/token bucket | window + jitter |
|
||
| `han:api:jwks:negative:{kid_hash}` | marker отрицательного lookup | 30s |
|
||
|
||
Алгоритм — atomic Lua/function: удалить старые entries, посчитать, добавить текущий request, установить expiry, вернуть `allowed`, `remaining`, `retry_after_ms`, `reset_at`. Для fixed window `INCR` и первый `EXPIRE` выполняются в одном script, чтобы не оставить бессрочный key.
|
||
|
||
Clock — Redis `TIME`. Route labels — bounded allow-list/hash, исключающий cardinality attack.
|
||
|
||
## 4. DB0: idempotency
|
||
|
||
| Key | Значение | TTL |
|
||
|---|---|---|
|
||
| `han:api:idem:{scope}:{user_id}:{key_hmac}` | HASH/MessagePack: state, fingerprint, status, sanitized response, resource id, version | 24h |
|
||
| `han:api:idemlock:{scope}:{user_id}:{key_hmac}` | random owner token | 30s + heartbeat |
|
||
|
||
State transitions `absent → in_progress → completed`; fingerprint mismatch возвращает conflict. Создание/сравнение/lock выполняется Lua. Unlock/extend разрешены только если owner token совпадает (`compare-and-delete/expire` script).
|
||
|
||
Response не содержит tokens, cookies, presigned URL или PII. Transient 503/504 не фиксируется как окончательный completed. PostgreSQL `idempotency_records` — durable fallback; Redis — ускоритель. При cache loss API читает durable row и прогревает key.
|
||
|
||
## 5. DB1: realtime
|
||
|
||
| Key/channel | Формат | TTL |
|
||
|---|---|---|
|
||
| `han:rt:conn:{connection_id}` | HASH: user_id, instance, last_seen, subscriptions_count | 90s |
|
||
| `han:rt:user:{user_id}:connections` | ZSET connection_id → heartbeat | 120s |
|
||
| `han:rt:dialog:{dialog_id}` | Pub/Sub channel | нет хранения |
|
||
| `han:rt:user:{user_id}` | Pub/Sub channel | нет хранения |
|
||
|
||
Heartbeat атомарно обновляет connection и membership; cleanup удаляет stale ZSET entries bounded batches. Pub/Sub — at-most-once notification. Payload содержит только event id/type/entity UUID и DTO, допустимый realtime контрактом; DB остаётся source of truth. После reconnect frontend всегда делает REST reconciliation.
|
||
|
||
Redis Streams не используются как бизнес queue. Если позже понадобится durable realtime replay, сначала меняется архитектура и выбирается PostgreSQL outbox/event broker.
|
||
|
||
## 6. DB1: coordination locks
|
||
|
||
| Key | TTL |
|
||
|---|---|
|
||
| `han:coord:lock:safety-recovery:{task_id}` | 30s |
|
||
| `han:coord:lock:delivery:{message_id}` | 30s |
|
||
| `han:coord:lock:settings-refresh:{instance}` | 30s |
|
||
| `han:settings:snapshot:{version}` | 5m |
|
||
|
||
Acquire: `SET key owner NX PX ttl`; extend/release — Lua compare owner. Worker обязан опираться также на PostgreSQL row lease/`FOR UPDATE SKIP LOCKED`; Redis lock — оптимизация, не единственная защита. Fencing token рекомендуется для внешнего side effect, а уникальные DB constraints/idempotency остаются финальной защитой.
|
||
|
||
Lock `safety-recovery` на ВМ1 относится к recovery caller/`api-backend`, не к Redis Safety ВМ2.
|
||
|
||
## 7. Legacy DB2 stub
|
||
|
||
До cutover test stub v1 может временно использовать DB2 ВМ1 для random task state. Этот namespace не используется production v2 и удаляется вместе со stub.
|
||
|
||
Если stub ещё жив: Safety task TTL в DB2 должен превышать poll/recovery budget; Lua `safety task get+increment poll` допустим только здесь. После cutover keys, ACL и DB2 удаляются.
|
||
|
||
## 8. Lua scripts ВМ1
|
||
|
||
Обязательные:
|
||
|
||
- rate-limit evaluate;
|
||
- idempotency reserve/complete/conflict;
|
||
- lock release/extend;
|
||
- realtime heartbeat/cleanup membership.
|
||
|
||
Правила хранения/тестов — arch-09 §6.
|
||
|
||
## 9. Sizing ВМ1
|
||
|
||
```text
|
||
DB0 rate = peak identities × routes × active windows × bytes/key
|
||
DB0 idem = mutating requests/24h × avg sanitized record
|
||
DB1 = peak connections × connection metadata + Pub/Sub buffers
|
||
total × 1.5 allocator/fragmentation × 1.3 growth reserve
|
||
```
|
||
|
||
При memory pressure eviction idempotency не создаёт дубль благодаря PostgreSQL fallback. Если instances разделят (R6), DB0 idempotency может получить `noeviction`.
|
||
|
||
## 10. Degraded behavior ВМ1
|
||
|
||
При Redis недоступен:
|
||
|
||
- message send, attachment init и download URL `api-backend` fail-closed `503`, если нельзя безопасно применить лимит/idempotency;
|
||
- completed idempotency восстанавливается из PostgreSQL;
|
||
- profile/history GET могут работать под edge limits;
|
||
- public GET использует bounded local conservative limiter/cache;
|
||
- realtime cross-instance publish/coordination деградирует; REST/polling остаётся source of truth;
|
||
- internal inbox не теряется из-за Redis, так как durable receipt в PostgreSQL;
|
||
- legacy stub v1 может стать недоступным при потере своей DB2 до cutover.
|
||
|
||
Production Safety на ВМ2 при этом продолжает PostgreSQL claim; это не runbook Redis ВМ1.
|
||
|
||
Restore: `api-backend` прогревает idempotency по durable records, realtime восстанавливается reconnect/polling.
|
||
|
||
## 11. Metrics ВМ1
|
||
|
||
Общие — arch-09 §14 и [`module-09-observability-vm1.md`](module-09-observability-vm1.md). Дополнительно: rate limit decisions, idempotency hit/conflict/fallback, Pub/Sub subscribers/output buffer.
|
||
|
||
## 12. Тесты ВМ1
|
||
|
||
- ACL: `api_backend` видит только свои prefix/commands; Safety prefixes отсутствуют;
|
||
- порт 6379 недоступен с host/public network;
|
||
- rate Lua concurrency и exact Retry-After;
|
||
- idempotency same/different fingerprint, lock ownership, expiry, Redis loss + PostgreSQL fallback;
|
||
- realtime heartbeat cleanup, duplicate disconnect, Pub/Sub loss + REST recovery;
|
||
- locks expiry/late owner/fencing;
|
||
- `NOSCRIPT` reload;
|
||
- all application keys имеют TTL;
|
||
- max value/invalid serialization;
|
||
- restart with AOF/RDB, corrupted AOF rehearsal, empty restore;
|
||
- memory pressure/eviction и no duplicate business side effect;
|
||
- network partition, latency, reconnect backoff;
|
||
- logs/metrics не содержат secret/value/PII;
|
||
- после cutover DB2/stub keys отсутствуют.
|
||
|
||
## 13. Definition of Done ВМ1
|
||
|
||
Дополнительно к arch-09 §15:
|
||
|
||
- DB0/DB1 roles и prefixes реализованы;
|
||
- idempotency 24h и durable fallback доказаны;
|
||
- realtime loss восстанавливается REST;
|
||
- health/degraded policies §10 реализованы в `api-backend`;
|
||
- dashboards/alerts Redis ВМ1 готовы;
|
||
- до cutover: Safety DB2 task TTL превышает poll/recovery budget, если stub ещё включён;
|
||
- после cutover: DB2 и stub namespace удалены.
|
||
|
||
## 14. TBD ВМ1
|
||
|
||
- R1/R2: maxmemory и eviction после load profile ВМ1.
|
||
- R6: момент разделения DB0/DB1 на instances.
|
||
- R3: имена credential env — arch-04.
|
||
|
||
## 15. Ссылки
|
||
|
||
- Контракт: [`arch-09-redis.md`](../../architectory/arch-09-redis.md).
|
||
- ВМ2: [`module-04-redis-vm2.md`](../../VM2_services/documentation/module-04-redis-vm2.md).
|
||
- Указатель: [`module-04-redis.md`](module-04-redis.md).
|
||
- API: [`module-01-api-backend.md`](module-01-api-backend.md).
|