didi-lot2-backend/backend/services/orchestration-layer/agent-v3/MIGRATION_RABBITMQ.md
2026-07-10 03:39:53 -07:00

168 lines
5.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Migrare RabbitMQ: `staging-dataLayer-rabbitmq` local → cluster HA `10.11.50.100`
**Autor**: refactor 2026-04-22 (după Redis migration)
**Scop**: mutarea cozilor async (cele 31 de cozi analysis.*) de pe containerul RabbitMQ local pe clusterul HA managed cu vhost dedicat `/didi`.
---
## Stare actuală (post-cutover)
- Codul RabbitMQ era **deja centralizat** în 3 fișiere:
- `src/queue/connection.ts` — manager singleton conexiune + channels
- `src/shared/queue/constants.ts``getRabbitMQConfig()` + `getRabbitMQUrl()`
- `src/scripts/init-priority-queues.ts` — script standalone
- Zero refactor pe cod aplicație. Un singur punct de schimbat.
- URL-encoding corect pentru vhost `/didi` adăugat în `getRabbitMQUrl()` (folosește `encodeURIComponent` pentru user/pass/vhost).
- `CONNECTION_RETRY_DELAY` tuned de la 5s la 2s pentru failover HA mai rapid.
- Logging îmbunătățit: arată host+vhost+user la conectare/eroare/close.
---
## Target
| Parametru | Valoare |
|---|---|
| AMQP | `10.11.50.100:16672` |
| Management UI | `http://10.11.50.100:16673` |
| Username | `didi` |
| Password | `$CLUSTER_PASSWORD` |
| Vhost dedicat | `/didi` (izolat de alte proiecte) |
| URI encoded | `amqp://didi:...@10.11.50.100:16672/%2Fdidi` |
| Cluster HA | 3 noduri rag01/02/03 + HAProxy VIP |
---
## Diferență critică față de Redis
**Nu a fost nevoie de migrare de date**. Cozile RabbitMQ sunt efemere — topologia (exchange + 31 queues) se creează automat de workeri la `assertExchange()` / `assertQueue()` pe prima conectare. Mesajele în zbor la cutover se pierd (erau 0 la momentul migrării).
---
## Pași executați la cutover
### 1. Refactor cod (minim)
- `src/queue/connection.ts`:
- Retry: 5s → 2s
- Logging: `[RabbitMQ] Connecting to HOST:PORT/VHOST (user: USER)...`
- Error messages: includ host+vhost pentru debug
- Add `blocked/unblocked` handlers (broker flow control)
- `src/shared/queue/constants.ts`:
- `getRabbitMQUrl()` folosește `encodeURIComponent()` pentru user/pass/vhost
- Fix critical: vhost `/didi` acum devine `%2Fdidi` în URL (fără asta, amqplib parsa ca vhost `didi` fără slash)
### 2. docker-compose (agent-v3)
`x-worker-env` schimbate din hardcoded la fallbacks:
```yaml
RABBITMQ_HOST: ${RABBITMQ_HOST:-staging-dataLayer-rabbitmq}
RABBITMQ_PORT: ${RABBITMQ_PORT:-5672}
RABBITMQ_USER: ${RABBITMQ_USER:-admin}
RABBITMQ_PASS: ${RABBITMQ_PASS:-rabbitmq123}
RABBITMQ_VHOST: ${RABBITMQ_VHOST:-/}
```
### 3. `.env` agent-v3
Adăugat bloc:
```
RABBITMQ_HOST=10.11.50.100
RABBITMQ_PORT=16672
RABBITMQ_USER=didi
RABBITMQ_PASS=$CLUSTER_PASSWORD
RABBITMQ_VHOST=/didi
```
### 4. Rebuild + restart
- `docker compose build` (toate 8 imagini, cache fast)
- `docker compose up -d --force-recreate` pe agent-v3 stack
- Workerii s-au conectat în < 1s și au creat automat toate 31 cozile
### 5. Verificare
- 31/31 cozi prezente pe `/didi` cu consumerii corespunzători (claims×3, rest×2)
- 14 conexiuni active de la workeri prin HAProxy VIP
- Smoke test: analiză async plan 4 = pro, status `completed` în 3.9s, risk MOSTLY_RELIABLE (22)
---
## Script de verificare: `scripts/verify-rabbitmq-cluster.ts`
Mirror al `verify-redis-cluster.ts`. Rulează cu:
```bash
RABBITMQ_HOST=10.11.50.100 RABBITMQ_PORT=16672 \
RABBITMQ_USER=didi RABBITMQ_PASS='...' RABBITMQ_VHOST=/didi \
node_modules/.bin/ts-node --transpile-only scripts/verify-rabbitmq-cluster.ts
```
Verifică:
1. Conectivitate + auth pe vhost
2. Channel creation
3. Assert exchange `analysis` (topic, durable)
4. Write privilege (creează + șterge coadă temporară)
5. Topologie expected (31 cozi) WARN dacă workerii nu au rulat încă
Exit codes: `0` = OK sau WARN (topology lipsă pre-cutover), `1` = FATAL.
---
## Fallback LOCAL păstrat
Containerul `staging-dataLayer-rabbitmq` e **oprit** dar **nu șters** rollback rapid prin script.
### Switch rapid: `services/orchestration-layer/scripts/redis-switch.sh`
Scriptul acoperă ACUM ambele servicii (Redis + RabbitMQ):
```bash
# Arată status curent
./redis-switch.sh status
# Switch ambele (Redis + Rabbit) la cluster
./redis-switch.sh cluster
# Switch ambele la local
./redis-switch.sh local
# Doar unul:
./redis-switch.sh cluster redis # Redis → cluster, Rabbit neschimbat
./redis-switch.sh local rabbit # Rabbit → local, Redis neschimbat
```
Script-ul:
- Pornește containerul local dacă e oprit (`docker start` sau `docker compose up -d`)
- Rescrie blocul corespunzător în `.env` (fără atingă celelalte)
- Restart `didi-framework` + `agent-v3` stack
### Rollback în 30s
```bash
./redis-switch.sh local rabbit # doar RabbitMQ înapoi la local
```
---
## Cleanup DEFINITIV (după 30+ zile stabil)
1. Stop + remove container local:
```bash
docker stop staging-dataLayer-rabbitmq # deja oprit
docker rm staging-dataLayer-rabbitmq
docker volume rm didi-staging-rabbitmq-data
```
2. Șterge secțiunea din `data-layer/docker-compose.yml`
3. Update `didiQueue/INDEX.md` marcat ca deprecated
4. Admin dashboard nginx — șterge `/health-check/rabbitmq` (pointa la container local)
---
## Riscuri cunoscute / TODO
1. **amqplib nu are Sentinel/discovery nativ** — la failover cluster, ioredis-style "învață" singur noul master. amqplib doar se deconectează și se reconectează la VIP (care rutează la noul master). Downtime client: 5-15s acceptabil.
2. **init-priority-queues.ts** — neinvestigat în detaliu dacă mai e rulat. Cozile se creează oricum prin workeri.
3. **Monitoring**: management UI-ul e pe `10.11.50.100:16673` accesibil doar din LAN. Admin dashboard nu-l embedă direct; user poate deschide browser separat.