9.6 KiB
module-04-vm1. Redis ВМ1 HAN Chat
Статус: целевая спецификация Redis на ВМ1.
Канонический контракт (ключи, TTL, Lua, AOF, ACL, eviction) —arch-09-redis.md.
Обязательный host/container hardening baseline —arch-06-service-hosting-security.md.
Redis Safety ВМ2 —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.
2. URL и ACL
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
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-backendfail-closed503, если нельзя безопасно применить лимит/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. Дополнительно: 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;
NOSCRIPTreload;- 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. - ВМ2:
module-04-redis-vm2.md. - Указатель:
module-04-redis.md. - API:
module-01-api-backend.md.