Qdrant Collections — Tenant-isolierte Vektorsuche¶
Überblick¶
Das kora-platform-Retrieval nutzt pro Tenant zwei Arten von Qdrant- Collections:
- Chatbot-eigene Collection:
kora_<tenant_id>_<chatbot_id>— enthält die Dokumente, die für genau diesen Chatbot hochgeladen wurden. - Shared-Collection pro Tenant:
kora_<tenant_id>_shared— enthält Dokumente, die für alle Chatbots dieses Tenants verfügbar sind (z.B. allgemeine Geschäftsbedingungen, tenant-weite Richtlinien).
Bei einer User-Query an einen Chatbot sucht das Retrieval parallel in beiden Collections und gibt die Top-K-Ergebnisse gemerged nach Score zurück (Fan-Out-Retrieval).
Tenant-Isolation: Der Collection-Name enthält direkt die Tenant-UUID. Kombiniert mit dem Service-Level-Cross-Tenant-Guard und RLS auf DB-Ebene ergibt sich eine dreischichtige Defense-in-Depth-Isolation, die in Integration-Tests mit 200 parallelen Queries (0 Cross-Hits) verifiziert wurde.
Komponenten im Stack¶
| Service | Rolle |
|---|---|
kora-platform-qdrant |
Vektor-Store (Collections + Points) |
kora-platform-embedder |
Embedding-Service (multilingual-e5-large) |
kora-platform-api |
Orchestrierung (Upload, Retrieval, Lifecycle) |
kora-platform-scheduler |
ofelia, triggert Cleanup täglich 03:00 |
Dokumente hochladen¶
Uploads gehen durch den DocumentUploader-Service. Aktuell via Python-
API (Routen kommen mit Block 5). Manuelles Beispiel für Smoke-Tests
siehe tests/integration/test_qdrant_tenant_isolation.py — die
two_tenants_with_data-Fixture zeigt den End-to-End-Flow (Tenant +
Chatbot anlegen, DocumentUploader.upload() in Chatbot- und Shared-
Collection).
Retrieval abrufen¶
Analog via Python-API (PlatformRetrieval.search()). HTTP-Routen kommen
mit Block 5 (Chatbot-Templates & CRUD) bzw. Block 7 (Operator-UI).
Chatbot-Lifecycle¶
Soft-Delete¶
Markiert den Chatbot als gelöscht (chatbots.deleted_at = now()), ohne
Qdrant-Daten zu entfernen. Grace-Period: 30 Tage.
Effekte:
PlatformRetrieval.search()wirftChatbotNotFoundErrorDocumentUploader.upload()ist technisch weiter möglich (keindeleted_at-Check im Upload-Pfad), sollte aber operational vermieden werden
Soft-Delete wird aktuell nicht über ein Make-Target exposed (kommt mit den API-Routen). Manuell via DB:
docker exec kora-platform-postgres psql -U kora_platform kora_platform \
-c "UPDATE chatbots SET deleted_at = now() WHERE id = '<chatbot-uuid>';"
Hard-Delete (Cleanup)¶
Entfernt Chatbots, deren Soft-Delete älter als die Grace-Period ist. Löscht sowohl DB-Row als auch Qdrant-Collection.
Automatisch täglich um 03:00 via Scheduler:
Manuell jederzeit:
make platform-exec cmd="kora-platform cleanup-expired-chatbots"
# oder mit custom grace:
make platform-exec cmd="kora-platform cleanup-expired-chatbots --grace-days 0"
--grace-days 0 löscht alle soft-deleted Chatbots sofort. Nützlich für
Tests oder wenn ein Chatbot bewusst endgültig weg soll.
Collection-Management¶
Übersicht aller Collections¶
docker exec kora-platform-qdrant \
curl -s http://localhost:6333/collections | jq '.result.collections[].name'
Collections eines bestimmten Tenants¶
TENANT_ID=<uuid>
docker exec kora-platform-qdrant \
curl -s http://localhost:6333/collections \
| jq ".result.collections[] | select(.name | startswith(\"kora_${TENANT_ID}_\"))"
Manuelles Löschen einer Collection (Notfall)¶
Sollte nicht routinemäßig nötig sein. Nur bei korrupten Collections oder Rollback-Szenarien. Entfernt nur Qdrant-Daten, nicht den DB- Eintrag:
COLLECTION_NAME=kora_<tenant>_<chatbot>
docker exec kora-platform-qdrant \
curl -X DELETE http://localhost:6333/collections/$COLLECTION_NAME
Wichtig: Wenn du eine Collection manuell löschst, zeigt der DB-Row
weiterhin auf sie. Die nächste Upload-Operation würde sie automatisch
neu anlegen (via QdrantManager.ensure_collection idempotent).
Embedder-Service¶
Health¶
docker exec kora-platform-embedder \
curl -s http://localhost:8090/health
# erwartet: {"status":"ok","model":"intfloat/multilingual-e5-large"}
Restart (bei Problemen)¶
docker compose -p kora-platform -f docker-compose.platform.yml \
--env-file .env.platform restart embedder
Cold-Start-Zeit¶
Erste Start-Sekunde nach Container-Create: ~30–40s (Modell-Load ins RAM). Nach erstem Start: wenige Sekunden pro Restart (Modell bleibt im Image- Layer gecacht).
Scheduler-Jobs hinzufügen¶
Siehe infra/scheduler/README.md für das generische Pattern. Kurz-
fassung: neuen [job-exec "<name>"]-Eintrag in
infra/scheduler/config.ini, dann
docker restart kora-platform-scheduler.
Troubleshooting¶
Retrieval liefert keine Hits¶
- Collection existiert?
curl localhost:6333/collections/<name> - Collection hat Points?
curl localhost:6333/collections/<name>→points_countprüfen - Chatbot nicht soft-deleted?
SELECT id, deleted_at FROM chatbots WHERE id = '<uuid>'; - Embedder erreichbar?
docker exec kora-platform-api curl -s http://embedder:8090/health
ChatbotNotFoundError trotz existierendem Chatbot¶
Wahrscheinlich deleted_at IS NOT NULL. Siehe Soft-Delete-Abschnitt.
Rollback per UPDATE:
CrossTenantAccessError¶
Der aufrufende Code hat einen tenant_id/chatbot_id-Mismatch. Das ist
ein Bug im Caller, nicht in der Retrieval-Logik. Die Fehlermeldung wird
mit Warning-Level geloggt (retrieval.cross_tenant_attempt), also
Logs nach dem Muster durchsuchen.
Scheduler triggert Job nicht¶
- Scheduler läuft?
docker ps --filter name=kora-platform-scheduler - Config geladen?
docker logs kora-platform-scheduler --tail 20 - Docker-Socket zugänglich? Der Scheduler braucht
/var/run/docker.sockmounted (read-only reicht)
Embedder-Container restarted¶
Logs checken:
Häufige Ursache: Memory-Limit (4GB) zu knapp bei großen Batches. Batch-
Size in der api-Config reduzieren (KORA_EMBEDDER_BATCH_SIZE).
Backup (noch nicht implementiert)¶
Backup-Strategie ist als TODO-Block-4b geplant, nicht Teil dieses Blocks. Siehe offene TODOs.
Bis dahin: keine automatischen Qdrant-Snapshots. Im Ernstfall (Disaster Recovery) müssten die Collections aus den Source-Dokumenten neu aufgebaut werden, was bei produktiven Tenants nicht-trivial ist. Das ist ein anerkanntes Gap.
Referenzen¶
- Konzept: Fundament §6 — Design-Diskussion, Naming-Varianten, Fan-Out
- Code:
src/kora_platform/services/qdrant_manager.py,src/kora_platform/services/document_uploader.py,src/kora_platform/services/retrieval.py,src/kora_platform/services/chatbot_lifecycle.py - Integration-Test:
tests/integration/test_qdrant_tenant_isolation.py(4/4 PASS, 200 parallele Queries, 0 Cross-Hits) - Verwandt: Compose-Invocations, Bind-Mount-Discipline