# 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:@redis:6379/0 REDIS_REALTIME_URL=redis://api_backend:@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).