11 KiB
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.tsdidiFramework/src/config/redis.ts
- Zero
new Redis({...})inline în code — grep confirmă. - Suport pentru ACL (username + password) adăugat: dacă
REDIS_USERNAMEe 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
-
Verificare conectivitate din host DIDI (10.11.10.12):
docker exec didi-cache redis-cli -h 10.11.50.100 -p 16379 \ --user didi --pass '$CLUSTER_PASSWORD' --no-auth-warning PING # Expected: PONG -
Verificare din container agent-v3 (important — rețeaua Docker bridge trebuie să rutează prin host la LAN):
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. -
Verificare replicare cluster:
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 -
Backup Redis local (paranoia):
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 -
Fereastră de mentenanță — alege un moment cu trafic minim. Impact estimat:
- 5-10s: restart services
- <60s: primul
/api/sync-redissă repopuleze framework config - Sesiuni async în zbor (
didi:queue:session:*) — se pot pierde. Inventariere:
Dacă > 0, așteaptă ca cozile RabbitMQ să se golească înainte de cutover.docker exec didi-cache redis-cli -a redis123 --no-auth-warning --scan --pattern 'didi:queue:session:*' | wc -l
Pași cutover
1. Oprire workeri + API (menține Redis local live pentru rollback)
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
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ă.
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
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
cd /home/admin365/didi_mono/backend/services/orchestration-layer/agent-v3
docker compose up -d
7. Smoke test
# 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=<session_id_de_mai_sus>
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
# 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:
-
Restore env vars:
# 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 -
Restart containere:
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 -
Sync-redis pe local să repopuleze:
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
# 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.envfiles (agent-v3 + didiFramework) - Pentru
local: pornește containeruldidi-cachedacă e oprit - Restart
didi-framework+agent-v3stack (API + 12 workeri) - Bootstrap auto-populează Redis-ul ales dacă e gol
Cum rămâne imaginea locală disponibilă:
didi-cachedefinit înproduction/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ă înREDIS_HOSTSal 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):
-
Oprește și șterge containerul
didi-cache: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 -
Curăță
production/docker-compose.yml— șterge secțiuneadidi-cache. -
Redis Commander — update
REDIS_HOSTSîndata-layer/docker-compose.yml:REDIS_HOSTS: "production:10.11.50.100:16379:0:didi:$CLUSTER_PASSWORD"Sau deschide UI-ul nativ al cluster-ului dacă există.
-
Update documentație:
agent-v3/INDEX.md: înlocuieștedidi-cache:6379cu10.11.50.100:16379didiCache/INDEX.md: marchează drept deprecated / legacyproject_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:
DBSIZEpe 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/frameworkload-ează normal (citește prin didiFramework → Redis) - Failover test: oprește temporar master-ul cluster (dacă e permis) → workers reconectează în < 10s