Проект разделен на два репозитория

This commit is contained in:
mi
2026-08-14 15:42:45 +03:00
parent e06a77ee1d
commit bbef7a30c9
521 changed files with 2597 additions and 2302 deletions
@@ -0,0 +1,164 @@
# 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).