# Migrare Redis: `didi-cache` local → cluster HA `10.11.50.100` **Autor**: refactor 2026-04-22 **Scop**: mutarea tuturor clienților Redis (agent-v3 + didiFramework) de pe containerul local `didi-cache` pe clusterul HA managed (rag01/02/03 cu VIP HAProxy pe `10.11.50.100`). --- ## Stare actuală (post-refactor cod, pre-cutover) - Tot codul Redis e centralizat prin `createRedisConnection()` — 2 helpers paraleli: - `agent-v3/src/shared/redis/connection.ts` - `didiFramework/src/config/redis.ts` - Zero `new Redis({...})` inline în code — grep confirmă. - Suport pentru ACL (username + password) adăugat: dacă `REDIS_USERNAME` e gol, se face legacy AUTH (doar password). - Auto-reconnect + retryStrategy + reconnectOnError pentru READONLY/MASTERDOWN. - `docker-compose.yml` (agent-v3, didiFramework) au env vars cu fallback la valorile vechi (`didi-cache`, `redis123`) — deci **nimic nu s-a rupt**, rulează identic cu înainte. --- ## Target | Parametru | Valoare | |---|---| | Host | `10.11.50.100` | | Port | `16379` | | Username | `didi` | | Password | `$CLUSTER_PASSWORD` | | DB | `0` | | Sentinel VIP | `10.11.50.100:16380` (master name `ragmaster`) — neutilizat deocamdată, folosim HAProxy VIP | Strategie failover: **HAProxy VIP** (simplu). ioredis reconectează automat la VIP după failover; TCP session se rupe 2-10s. Dacă se dovedește insuficient, putem trece la Sentinel client (cod pregătit să accepte `overrides` în helper). --- ## Pre-cutover checklist 1. **Verificare conectivitate** din host DIDI (10.11.10.12): ```bash docker exec didi-cache redis-cli -h 10.11.50.100 -p 16379 \ --user didi --pass '$CLUSTER_PASSWORD' --no-auth-warning PING # Expected: PONG ``` 2. **Verificare din container agent-v3** (important — rețeaua Docker bridge trebuie să rutează prin host la LAN): ```bash docker exec didi-agent-v3 sh -c 'apk add --no-cache redis 2>/dev/null; redis-cli -h 10.11.50.100 -p 16379 --user didi --pass "$CLUSTER_PASSWORD" --no-auth-warning PING' ``` Dacă eșuează, host-ul nu rutează containerul la LAN. Fix: `docker network inspect didi-network` + verifică iptables DOCKER-USER. 3. **Verificare replicare cluster**: ```bash docker exec didi-cache redis-cli -h 10.11.50.100 -p 16379 \ --user didi --pass '$CLUSTER_PASSWORD' --no-auth-warning INFO replication # Expected: role:master, connected_slaves:2 ``` 4. **Backup Redis local** (paranoia): ```bash docker exec didi-cache redis-cli -a redis123 --no-auth-warning --rdb /data/pre-migration-backup.rdb docker cp didi-cache:/data/pre-migration-backup.rdb ./didi-cache-backup-$(date +%F).rdb ``` 5. **Fereastră de mentenanță** — alege un moment cu trafic minim. Impact estimat: - 5-10s: restart services - <60s: primul `/api/sync-redis` să repopuleze framework config - Sesiuni async în zbor (`didi:queue:session:*`) — se pot pierde. Inventariere: ```bash docker exec didi-cache redis-cli -a redis123 --no-auth-warning --scan --pattern 'didi:queue:session:*' | wc -l ``` Dacă > 0, așteaptă ca cozile RabbitMQ să se golească înainte de cutover. --- ## Pași cutover ### 1. Oprire workeri + API (menține Redis local live pentru rollback) ```bash cd /home/admin365/didi_mono/backend/services/orchestration-layer/agent-v3 docker compose stop agent-v3 worker-media-preprocess worker-techniques \ worker-ai-tampered worker-claims worker-domain verdict-aggregator cd /home/admin365/didi_mono/backend/services/orchestration-layer/didiFramework docker compose stop didi-framework ``` ### 2. Setează env vars noi Editează `/etc/didi.env` (sau altă locație shared) sau adaugă la `.env` din root-ul fiecărui compose: ``` REDIS_HOST=10.11.50.100 REDIS_PORT=16379 REDIS_USERNAME=didi REDIS_PASSWORD=$CLUSTER_PASSWORD REDIS_DB=0 ``` **Recomandat**: un singur `.env` la `/home/admin365/didi_mono/backend/.env` pe care îl referențiază ambele compose-uri prin `env_file:` (necesită mică modificare in compose). **Atenție**: NU committa parola în git. ### 3. Rebuild containere cu noul cod ```bash cd /home/admin365/didi_mono/backend/services/orchestration-layer/agent-v3 docker compose build agent-v3 worker-media-preprocess worker-techniques \ worker-ai-tampered worker-claims worker-domain verdict-aggregator cd /home/admin365/didi_mono/backend/services/orchestration-layer/didiFramework docker compose build didi-framework ``` ### 4. Pornește didiFramework primul + rulează sync-redis Ordinea contează: agent-v3 citește config din Redis la fiecare request. Dacă nu există config, analizele eșuează. ```bash cd /home/admin365/didi_mono/backend/services/orchestration-layer/didiFramework docker compose up -d didi-framework # Așteaptă să fie ready sleep 10 docker compose logs didi-framework | tail -20 # Trigger sync-redis: PG → noul cluster Redis curl -X POST http://localhost:3005/api/sync-redis # Expected: 200 OK, { "success": true, "categories": ["techniques", "sources", ...] } ``` ### 5. Validează că noul cluster are datele framework ```bash docker exec didi-cache redis-cli -h 10.11.50.100 -p 16379 \ --user didi --pass '$CLUSTER_PASSWORD' --no-auth-warning \ --scan --pattern 'didi:framework:*' # Expected: 8 keys (manifest, techniques, sources, claims, verdicts, weights, providers, dimensions_compact) docker exec didi-cache redis-cli -h 10.11.50.100 -p 16379 \ --user didi --pass '$CLUSTER_PASSWORD' --no-auth-warning \ --scan --pattern 'didi:config:*' | wc -l # Expected: ~30 keys ``` ### 6. Pornește agent-v3 + workeri ```bash cd /home/admin365/didi_mono/backend/services/orchestration-layer/agent-v3 docker compose up -d ``` ### 7. Smoke test ```bash # Health curl http://localhost:24803/api/v3/health # Test analiză text scurt (sync) — verifică tot fluxul Redis curl -X POST http://localhost:24803/api/v3/pipeline/analyze \ -H "Content-Type: application/json" \ -d '{"text": "Test migrare redis cluster"}' | jq '.data.session_id, .data.status' # Expected: session_id UUID, status: "completed" # Confirmă că sesiunea e scrisă pe cluster SID= docker exec didi-cache redis-cli -h 10.11.50.100 -p 16379 \ --user didi --pass '$CLUSTER_PASSWORD' --no-auth-warning \ --scan --pattern "didi:pipeline:${SID}:*" ``` ### 8. Monitorizare 30 min ```bash # Live logs — caută "Redis error" sau "ECONNREFUSED" docker compose logs -f --tail 0 agent-v3 worker-techniques worker-claims verdict-aggregator | grep -iE "redis|error" # Keys growth pe noul cluster watch -n 5 'docker exec didi-cache redis-cli -h 10.11.50.100 -p 16379 --user didi --pass "$CLUSTER_PASSWORD" --no-auth-warning DBSIZE' ``` --- ## Rollback plan Dacă ceva crapă în primele 30 min: 1. **Restore env vars**: ```bash # Elimină REDIS_USERNAME și revino la defaults unset REDIS_HOST REDIS_PORT REDIS_USERNAME REDIS_PASSWORD REDIS_DB # sau sterge liniile din /etc/didi.env ``` 2. **Restart containere**: ```bash cd /home/admin365/didi_mono/backend/services/orchestration-layer/agent-v3 docker compose up -d --force-recreate cd /home/admin365/didi_mono/backend/services/orchestration-layer/didiFramework docker compose up -d --force-recreate ``` 3. **Sync-redis pe local** să repopuleze: ```bash curl -X POST http://localhost:3005/api/sync-redis ``` Datele vechi sunt încă pe `didi-cache` (nu le-am șters). Maxim 30 min de analize noi se pierd. --- ## Post-cutover — FALLBACK LOCAL PĂSTRAT **Containerul `didi-cache` e oprit dar NU șters** — poate fi repornit oricând ca fallback pentru dev local sau incident recovery. Volumul `didi-production-cache-data` rămâne intact. ### Script de switch rapid: `services/orchestration-layer/scripts/redis-switch.sh` ```bash # Arată starea curentă ./redis-switch.sh status # Switch la cluster (production) ./redis-switch.sh cluster # Switch înapoi la local (rollback sau dev) ./redis-switch.sh local ``` Ce face: - Rescrie `REDIS_*` în ambele `.env` files (agent-v3 + didiFramework) - Pentru `local`: pornește containerul `didi-cache` dacă e oprit - Restart `didi-framework` + `agent-v3` stack (API + 12 workeri) - Bootstrap auto-populează Redis-ul ales dacă e gol ### Cum rămâne imaginea locală disponibilă: - `didi-cache` definit în `production/docker-compose.yml` — **nu șterge secțiunea** - Image: `redis:7-alpine` (standard, disponibil oricând) - Volum: `didi-production-cache-data` (păstrat cu datele vechi) - Parolă: `redis123` (hardcodată în `REDIS_HOSTS` al Redis Commander pentru vizualizare) Chiar și dacă clusterul cade complet, rollback-ul e **30 secunde**: `./redis-switch.sh local`. ### Cleanup DEFINITIV (când cluster e stable 30+ zile și nu mai vrei fallback): 1. **Oprește și șterge containerul `didi-cache`**: ```bash docker stop didi-cache docker rm didi-cache docker volume rm didi-production-cache-data # păstrează ca backup încă 30 zile dacă ești paranoic ``` 2. **Curăță `production/docker-compose.yml`** — șterge secțiunea `didi-cache`. 3. **Redis Commander** — update `REDIS_HOSTS` în `data-layer/docker-compose.yml`: ```yaml REDIS_HOSTS: "production:10.11.50.100:16379:0:didi:$CLUSTER_PASSWORD" ``` Sau deschide UI-ul nativ al cluster-ului dacă există. 4. **Update documentație**: - `agent-v3/INDEX.md`: înlocuiește `didi-cache:6379` cu `10.11.50.100:16379` - `didiCache/INDEX.md`: marchează drept deprecated / legacy - `project_network_architecture.md`: actualizează diagrama --- ## Riscuri reziduale | Risc | Probabilitate | Mitigare | |---|---|---| | Containerele nu pot rezolva `10.11.50.100` din docker-network | Mică (testat din didi-cache — OK) | Test forțat pre-cutover, fallback network_mode: host pe un worker dacă e nevoie | | Failover cluster exact în timpul cutover-ului | Foarte mică | retryStrategy cu 5s max + reconnectOnError — ioredis recuperează | | Cluster partajat cu alte proiecte și overwrite-uri accidentale | Necunoscut | Toate cheile DIDI încep cu `didi:*` sau `agent:*`. Recomand confirmare de la admin cluster că DB 0 e dedicat | | Parola hardcodată în .env committed în git | Umană | `.gitignore` `.env` + folosește `.env.example` fără parole | | Discrepanță `maxmemory` pe cluster (noeviction) → OOM la volum mare | Mică (13MB actuali) | Monitorizare `INFO memory` săptămânală | --- ## Verificare finală post-migrare Checklist de confirmat înainte de cleanup: - [ ] `DBSIZE` pe cluster nou crește în timp (sesiuni acumulate) - [ ] `didi:framework:*` prezent (8 chei) - [ ] `didi:config:*` prezent (~30 chei) - [ ] Zero `Redis error` în logs în ultimele 24h - [ ] Test analiză text + media + URL — toate completează cu status `completed` - [ ] Admin dashboard `/admin/framework` load-ează normal (citește prin didiFramework → Redis) - [ ] Failover test: oprește temporar master-ul cluster (dacă e permis) → workers reconectează în < 10s