# 🏗️ Архітектура для 150 нод — DAARION **Дата:** 2026-01-10 **Версія:** 1.0.0 --- ## 🎯 Принципи архітектури ### Ключова ідея **Не стандартизувати GPU**, а стандартизувати **контракт виконання**: - Як задачі розподіляються - Як воркери звітують - Як відбувається retry/ack - Як гарантується однакова якість (модель/версія) та безпека Різне "залізо" стає різними **tiers** воркерів. --- ## 🎛️ Control-plane vs Data-plane ### Control-plane (людський + командний) **Matrix Rooms** = контроль, команди, статуси, аудит **Призначення:** - Команди користувачів/агентів - Статуси виконання (progress + summary) - Ручне керування - Аудит рішень - Оповіщення про помилки/алерти **НЕ використовується для:** - Високочастотної job-шини - Масових обчислень - Real-time streaming даних ### Data-plane (масові таски, fan-out/fan-in) **NATS JetStream** = черга задач, розподіл обчислень **Призначення:** - Розподіл jobs між воркерами - Fan-out/fan-in обчислень - Retry/ack механізми - Пріоритизація (online/offline) - Idempotency гарантії **Переваги:** - Легкий (менше ресурсів ніж Kafka) - Швидкий (low latency) - Простіший для старту - At-least-once delivery - Підтримка idempotency keys ### State & Index **PostgreSQL** = state (організації, користувачі, threads, events) **Qdrant** = retrieval index (semantic search) **Neo4j** = graph relationships --- ## 📊 Поточна структура БД (що правильно, що треба виправити) ### PostgreSQL **Зараз:** - NODE1 = CloudNativePG (K8s) ✅ - NODE2/3 = Docker (dev/edge) **Правильний фундамент:** - **Один primary Postgres у Tier A** (NODE1/K8s) + репліки - NODE2/3 Postgres у Docker — залишити як dev/edge, але **не як "другий прод-primary"** **Що робити:** - [ ] Якщо NODE3 потрібен як DR/replica: робіть **стрімінг-репліку** з primary (NODE1) на NODE3 - [ ] Для k8s-primary: тримати репліки теж у k8s (CloudNativePG) - [ ] NODE3 використовувати для backup/restore і окремих offline задач ### Qdrant **Зараз:** 3 інсталяції (k8s + 2 docker) **Правильно:** - [ ] Один **prod Qdrant cluster** (Tier A) на NODE1 - [ ] Edge Qdrant (NODE2/3) — лише для локального тесту або кешу, але не "істина" ### Neo4j **Зараз:** Neo4j на всіх нодах (k8s + docker) **Рекомендація:** - [ ] Тримати **один Neo4j** у Tier A (NODE1) як прод - [ ] NODE2/3 — dev - [ ] Кластеризацію Neo4j відкласти, поки не буде реальної потреби в HA саме графа --- ## 🔄 Паралельні обчислення між нодами ### A) Control-plane (людський + командний) = Matrix rooms **Призначення:** - Команди - Статуси - Ручне керування - Аудит рішень **НЕ використовується для:** - Високочастотної job-шини - Масових обчислень ### B) Data-plane (масові таски, fan-out) = NATS JetStream **Вибір:** **NATS JetStream** (легкий, швидкий, простіший за Kafka для старту) **Переваги:** - Легкий (менше ресурсів) - Швидкий (low latency) - Простіший для старту - Підтримує at-least-once delivery - Підтримує idempotency keys ### C) Capability registry (хто що вміє) = Consul або Postgres **Кожен воркер реєструється з capabilities:** ```json { "node_id": "node-123", "tier": "A|B|C", "region": "eu-west", "trust_zone": "internal", "hardware": { "cpu_cores": 32, "ram_gb": 128, "gpu": true, "gpu_model": "RTX 3090", "vram_gb": 24, "cuda_version": "13.0" }, "capabilities": { "max_batch": 1000, "max_tokens": 8192, "models": ["embed-multilingual-v3.0", "llama3:8b"], "embedding_dim": 1024 }, "status": "ready|busy|maintenance", "last_heartbeat": "2026-01-10T19:30:00Z" } ``` --- ## 🔗 Зв'язок Matrix rooms з паралельними воркерами ### Правильний потік: ``` 1. Команда/подія → Matrix room (кімната агента або оркестратора) ↓ 2. Matrix Gateway (сервіс або модуль у Memory Service) - RBAC перевірка в Postgres - Створює Job у NATS (з idempotency key) ↓ 3. Воркер на відповідній ноді - Бере job з NATS - Виконує задачу - Пише результат у Postgres/Qdrant/Neo4j ↓ 4. Gateway публікує статус назад у Matrix - Progress + summary → Matrix - Детальні логи → система логів (Loki) ``` **Результат:** "Безперервний діалог" і керування через Matrix, але обчислення реально паралеляться через чергу. --- ## 📦 NATS JetStream: Streams + Consumers ### Streams (4 основні) #### STREAM `MM_ONLINE` - **Subjects:** `mm.embed.online`, `mm.retrieve.online`, `mm.summarize.online` - **Retention:** limits - **MaxAge:** 30 хв (або 2 год) - **Ack:** explicit - **MaxDeliver:** 3 - **Backoff:** 1s, 5s, 30s - **Replicas:** 3 #### STREAM `MM_OFFLINE` - **Subjects:** `mm.embed.offline`, `mm.index.offline`, `mm.backfill.offline` - **Retention:** limits - **MaxAge:** 7-30 днів - **MaxDeliver:** 10 - **Backoff:** 10s, 1m, 5m, 30m - **Replicas:** 3 #### STREAM `MM_WRITE` - **Subjects:** `mm.qdrant.upsert`, `mm.pg.write`, `mm.neo4j.write` - **Retention:** limits - **MaxAge:** 1-7 днів - **MaxDeliver:** 10 - **Replicas:** 3 #### STREAM `MM_EVENTS` - **Subjects:** `mm.event.audit`, `mm.event.status` - **Retention:** limits - **MaxAge:** 7-30 днів - **Replicas:** 3 ### Consumer Policies #### Online Consumer (Tier A) - **Stream:** `MM_ONLINE` - **Consumer:** `online-worker-tier-a` - **Ack Wait:** 30s - **Max Ack Pending:** 5000 - **Concurrency:** 50-200 (per worker) - **Filter:** `tier=A` AND `needs_gpu=true` (для GPU jobs) #### Offline Consumer (Tier B) - **Stream:** `MM_OFFLINE` - **Consumer:** `offline-worker-tier-b` - **Ack Wait:** 5-30 хв - **Max Ack Pending:** 10000 - **Concurrency:** висока, але з backpressure - **Preemption:** можливе призупинення для online jobs ### Job Payload Schema Детальна схема: `infrastructure/nats/job-payload-schema.json` **Ключові поля:** - `job_id` (ULID) - `idempotency_key` (SHA256 hash) — обов'язково для safe retries - `type` (embed|retrieve|summarize|qdrant_upsert|pg_write|neo4j_write|index) - `priority` (online|offline) - `requirements` (needs_gpu, min_vram_gb, max_latency_ms, tier) - `tenant` (org_id, workspace_id) - `scope` (user_id, agent_id, thread_id) - `input` (text, model, dims) - `refs` (event_id, memory_id, artifact_uri) - `trace` (trace_id, parent_span_id) - `timestamps` (created_at, deadline) --- ## 🎚️ Використання різних нод (Tier A/B/C) ### Поточна класифікація: | Node | Hardware | Tier | Призначення | |------|----------|------|-------------| | **NODE1** | RTX 4000 Ada 20GB | **A** | Online critical, primary DB | | **NODE3** | RTX 3090 24GB | **A/B** | Online + offline, heavy jobs | | **NODE2** | Apple M4 | **C** | Orchestration/dev/offline prep | ### Розподіл задач: - **Online embeddings/LLM** → NODE1 + NODE3 - **Offline backfill** → NODE3 + інші GPU edge ноди - **NODE2** → Orchestration, тестування, підготовка чанків, локальні агентні сесії --- ## 🔐 Embeddings: Cohere vs BGE-M3 ### Правило: **Одна колекція Qdrant = один embedding space** (модель + версія + dims) ### Поточна ситуація: - **Cohere embed-multilingual-v3.0** (1024 dim) — використовується ### Якщо переходите на BGE-M3: - [ ] Створити нову колекцію: `memories_bge_m3_v1` (1024 dim) - [ ] Або додати `model_version` і фізично рознести колекції - [ ] **НЕ змішувати** embedding-простори в одній колекції --- ## 📈 Метрики та Алерти ### NATS JetStream Metrics - `nats_consumer_lag{stream, consumer}` — backlog по streams - `nats_redeliveries_total{stream}` — кількість retries - `nats_ack_pending{stream, consumer}` — jobs в обробці - `nats_stream_storage_bytes{stream}` — використання диску ### Worker Metrics - `worker_jobs_processed_total{type, status}` — загальна кількість jobs - `worker_job_duration_seconds{type, quantile}` — latency (p50, p95, p99) - `worker_gpu_utilization{node_id}` — використання GPU - `worker_vram_usage_bytes{node_id}` — використання VRAM - `worker_errors_total{type, error_type}` — помилки ### Алерти → Matrix Ops Room **Критичні:** - `MM_ONLINE backlog > 1000` — SLO порушення - `redeliveries spike > 100/min` — проблеми з воркерами - `embed p95 > 500ms` (target: 300ms) - `disk > 80%` на JetStream PVC - `worker offline > 2 min` (Tier A) **Попередження:** - `MM_OFFLINE backlog > 10000` - `ack_pending > 80% max_ack_pending` - `worker_gpu_utilization < 20%` (Tier A, протягом 10+ хв) --- ## 📋 Наступні 7 кроків (по порядку) ### 1. ✅ Ротація секретів + прибрати креденшали з документів/репо - [x] Видалено паролі з документів - [ ] Ротація Cohere API key - [ ] Ротація паролів Postgres/Neo4j - [ ] Оновлення в Vault ### 2. ✅ Закрити зовнішній доступ до NodePort - [x] NodePort закрито для Memory Service NODE1 - [ ] Додати JWT/mTLS auth - [ ] NetworkPolicy для internal-only ### 3. ✅ Підняти NATS JetStream (Tier A) - [x] Створено K8s deployment (3 replicas + PVC + auth) - [x] Створено streams definitions (MM_ONLINE, MM_OFFLINE, MM_WRITE, MM_EVENTS) - [x] Створено job payload schema (JSON v1) - [x] Створено worker contract (capabilities + ack/retry) - [ ] Застосувати deployment в K8s - [ ] Налаштувати operator JWT + system account - [ ] Створити streams через NATS CLI або API ### 4. Додати worker-daemon на кожну ноду - [ ] Heartbeat до capability registry - [ ] Capabilities (hardware, models, tier) - [ ] Підписка на черги NATS - [ ] Job execution + результат ### 5. Matrix Gateway - [ ] Команди/статуси ↔ jobs у NATS - [ ] RBAC перевірка - [ ] Публікація результатів назад у Matrix ### 6. Рознести embedding-простори - [ ] Визначити стратегію (Cohere vs BGE-M3) - [ ] Створити окремі колекції або міграцію ### 7. Postgres: визначити один primary + репліки - [ ] NODE1 = primary (CloudNativePG) - [ ] NODE3 = streaming replica (якщо потрібен DR) - [ ] Backup/restore стратегія --- ## 📁 Структура файлів ``` infrastructure/ ├── nats/ │ ├── deployment.yaml # NATS JetStream в K8s │ ├── streams.yaml # Конфігурація streams │ └── accounts.yaml # NATS accounts (якщо потрібно) ├── worker-daemon/ │ ├── deployment.yaml # Worker daemon для кожної ноди │ ├── capability-registry.py # Реєстрація capabilities │ └── job-executor.py # Виконання jobs └── matrix-gateway/ ├── deployment.yaml # Matrix Gateway service ├── gateway.py # Matrix ↔ NATS bridge └── rbac.py # RBAC перевірка ``` --- *Документ створено: 2026-01-10 19:30 CET*