Files
BSN-Chatsystem/bsn-server-split-plan.md
T

9.3 KiB
Raw Blame History

BSN Server-Architektur — Split-Plan

Stand: 18.06.2026 | Autor: Basti (Chef vom Dienst)
Status: Planungsphase — noch nicht umgesetzt


Ziel

Das BSN-Chatsystem von einem Monolithen auf zwei spezialisierte Dienste aufteilen:

Server Rolle Optimiert für
Server A (aktuell, 4C/8GB) Web-Frontend + Datenbank I/O, HTTP-Requests
Server B (lokal, 24C/32GB) Sprachverarbeitung CPU, parallele Jobs

Ist-Analyse

Aktueller Monolith (Port 5002)

app.py (Flask) — EIN Prozess, alles drin:
├── WhatsApp Webhook          (I/O-leicht)
├── Telegram Webhook          (I/O-leicht)
├── Public Frontend           (I/O-leicht)
├── Admin Dashboard           (I/O-leicht)
├── faster-whisper Transkription  (CPU-schwer, 3060s, 1 Kern)
├── Piper TTS                 (CPU-mittel, 13s)
├── ffmpeg Video-Thumbnails   (CPU-mittel, 25s)
├── DeepSeek LLM-Calls        (Netzwerk, kein CPU-Problem)
└── SQLite DB                 (I/O)

Ressourcen-Profil pro Aufgabe

Aufgabe CPU RAM Dauer Parallelfähig
WhatsApp-Request <1% 5 MB 50ms Beliebig
Frontend-Rendering 2% 10 MB 100ms Beliebig
Whisper Transkription 100% (1 Kern) 1,5 GB 3060s ⚠️ 23 gleichzeitig
Piper TTS 50% (1 Kern) 200 MB 13s 45
ffmpeg Thumbnail 80% (1 Kern) 100 MB 25s ⚠️ 23
LLM API-Call 0% 20 MB 25s Beliebig

Kritischer Pfad: SPIEL Essen Messe

Während der Messe (4 Tage, ~10h/Tag) ist mit Spitzen von 2050 parallelen Requests zu rechnen.
Der aktuelle 4-Kern-Server würde bei 34 gleichzeitigen Transkriptionen alle Kerne blockieren — Web-Requests würden timeouten.


Ziel-Architektur

┌─────────────────────────────────────────────────────┐
│              Server A — "Web & Data"                 │
│              4 Kerne, 8 GB RAM                       │
│              Port 5002 (intern)                      │
│                                                      │
│  Flask (app.py), schlank:                            │
│  ├── WhatsApp/Telegram Webhook                       │
│  ├── Public Frontend + Admin                         │
│  ├── SQLite (bsn_intake.db)                          │
│  ├── DeepSeek LLM-Calls                              │
│  └── HTTP-Client → Server B                          │
│                                                      │
│  NUTZT: whisperclient.py (sendet Audio an Server B)  │
└──────────────────────┬──────────────────────────────┘
                       │
          HTTP (lokal, <1ms Latenz)
          POST /transcribe   {audio_file} → {text}
          POST /speak        {text}       → {audio_file}
          POST /thumbnail    {video_file} → {thumbnail}
                       │
┌──────────────────────▼──────────────────────────────┐
│            Server B — "Language Processor"           │
│            24 Kerne, 32 GB RAM (lokal)               │
│            Port 5003 (intern)                        │
│                                                      │
│  Flask/FastAPI (language_server.py):                 │
│  ├── POST /transcribe                                │
│  │   └── faster-whisper 'base' model                 │
│  ├── POST /speak                                     │
│  │   └── Piper TTS (de_DE-thorsten-medium)           │
│  ├── POST /thumbnail                                 │
│  │   └── ffmpeg -ss 1 -vframes 1                     │
│  └── GET /health                                     │
│                                                      │
│  Worker-Pool: 810 parallele Transkriptionen         │
│  RAM: 1,5 GB pro Whisper-Instanz ≈ 15 GB nutzbar     │
└─────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────┐
│          Server C — "Fallback & Test"                │
│          24 Kerne, 32 GB RAM (lokal)                 │
│                                                      │
│  Optional:                                           │
│  ├── Redundanz: Failover wenn Server B ausfällt      │
│  ├── Testumgebung: Staging für neue Features         │
│  └── Proxmox-Host: mehrere isolierte VMs             │
└─────────────────────────────────────────────────────┘

API-Spezifikation (Server A ↔ Server B)

POST /transcribe

Request:  multipart/form-data
  file:    audio.ogg (WhatsApp voice memo)

Response: 200 JSON
{
  "ok": true,
  "text": "Transkribierter deutscher Text...",
  "duration_seconds": 12.5,
  "processing_time_ms": 3420
}

Fehler:   { "ok": false, "error": "..." }

POST /speak

Request:  JSON
{
  "text": "Hallo Daniel, hier ist deine Antwort.",
  "voice": "de_DE-thorsten-medium"     // optional, default
}

Response: 200 audio/wav
  (binary WAV data)

Fehler:   { "ok": false, "error": "..." }

POST /thumbnail

Request:  multipart/form-data
  file:    video.mp4

Response: 200 image/jpeg
  (binary JPEG, 320px wide)

Fehler:   { "ok": false, "error": "..." }

GET /health

Response: 200 JSON
{
  "ok": true,
  "workers_busy": 2,
  "workers_available": 8,
  "uptime_seconds": 86400
}

Dateigrößen & Netzwerk

Medientyp Typische Größe Übertragung (1 Gbit/s lokal)
WhatsApp Sprachnachricht 50500 KB < 5 ms
WhatsApp Audio (2 Min.) 25 MB ~40 ms
WhatsApp Video (1 Min.) 550 MB ~400 ms
WAV-Audio (TTS Output) 100500 KB < 5 ms
JPEG Thumbnail 1050 KB < 1 ms

Fazit: Dateigrößen sind kein Bottleneck. Selbst 50-MB-Videos gehen in <500ms über das lokale Netz.


Migrationsplan (6 Schritte)

Phase 1: Language Server extrahieren (2h)

  1. language_server.py schreiben — Flask-App mit /transcribe, /speak, /thumbnail, /health
  2. Whisper/Piper/ffmpeg-Code aus app.py in language_server.py verschieben
  3. Auf Server B deployen und testen
  4. healthcheck einrichten

Phase 2: Web Server umbauen (1h)

  1. whisperclient.py schreiben — HTTP-Client für Language Server
  2. In app.py: direkte Funktionsaufrufe ersetzen durch whisperclient.transcribe(audio_path)
  3. Fallback: wenn Language Server nicht erreichbar → Submission als "Transkription ausstehend" markieren

Phase 3: Testen (1h)

  1. WhatsApp-Sprachnachricht senden → Transkription via Server B verifizieren
  2. TTS via Server B verifizieren
  3. Video-Thumbnail via Server B verifizieren
  4. Lasttest: 10 parallele Sprachnachrichten

Phase 4: Proxmox (Daniel)

  1. Proxmox auf Server B installieren
  2. VM mit 12 Kernen + 16 GB RAM für Language Server
  3. SSH-Zugang für Basti

Phase 5: Redundanz (optional)

  1. Language Server auch auf Server C deployen
  2. Web Server mit Failover: wenn B nicht antwortet → C anfragen

Phase 6: Monitoring

  1. Health-Checks via cron (alle 60s)
  2. Alert an Telegram wenn Language Server down

Voraussetzungen

Was Daniel bereitstellen muss

  • Server B: Proxmox installiert, VM bereit
  • Server B: SSH-Zugang für Basti
  • Server B: Linux (Debian/Ubuntu), Python 3.11+
  • Lokales Netzwerk: Server A kann Server B erreichen (ping, HTTP)

Was auf Server B installiert wird

# System
apt install python3 python3-pip python3-venv ffmpeg espeak-ng

# Python
pip install faster-whisper flask requests

# Piper TTS (pre-built binary)
# Voices: de_DE-thorsten-medium.onnx (~61 MB)

Risiken & Fallbacks

Risiko Eintritts-WSK Fallback
Server B nicht erreichbar Niedrig (lokales Netz) Submission markiert als "Transkription ausstehend", retry in 30s
Whisper-Modell zu langsam Niedrig (24 Kerne) Kleinere tiny-Variante laden
RAM voll (zu viele parallele Jobs) Mittel (unter Last) Worker-Pool begrenzen, Queue
Piper TTS fehlende Library Niedrig LD_LIBRARY_PATH + ESPEAK_DATA_PATH gesetzt

Kosten-Nutzen

Vor dem Split (aktuell):

  • 4-Kern-Server: Web + Transkription + TTS = alles in einem
  • SPIEL Essen: 3 parallele Transkriptionen = alle Kerne blockiert = Web timeouts

Nach dem Split:

  • 4-Kern-Server: NUR Web = ~100 gleichzeitige Requests möglich
  • 24-Kern-Server: NUR Sprache = 810 parallele Transkriptionen
  • Web-Server bleibt IMMER responsiv, egal wie viele Sprachnachrichten reinkommen

Nächste Schritte

  1. Daniel installiert Proxmox auf Server B
  2. Daniel gibt SSH-Zugang
  3. Basti deployt language_server.py
  4. Basti baut app.py um
  5. Integrationstest
  6. Produktiv setzen