Qdrant - Vector Database
Мультитенантность в Qdrant
Ваш RAG-сервис растёт: 10 клиентов, 100, 1000. Каждый хочет свои данные - и не хочет видеть чужие. Как организовать хранение, чтобы масштабироваться до тысяч тенантов без деградации производительности и без утечек между клиентами? Разберём три паттерна.
- **SaaS платформа:** 2000 компаний, у каждой своя база знаний → custom sharding, deleteShardKey при offboarding
- **Enterprise с отделами:** 50 отделов, нужна изоляция + cross-department search → shared collection с фильтрами + role-based access в API
- **Marketplace:** 10 крупных клиентов с миллионами документов → отдельная коллекция + dedicated Qdrant node per client
Предварительные знания
Три паттерна мультитенантности
**Мультитенантность** - когда один Qdrant инстанс обслуживает множество клиентов (tenants). Три основных паттерна: отдельная коллекция на тенанта, общая коллекция с payload-фильтром, кастомный шардинг по tenant_id. У каждого - свои trade-offs.
| Паттерн | Изоляция | Стоимость управления | Производительность | Когда использовать |
|---|---|---|---|---|
| Отдельная коллекция | Полная | Высокая (N коллекций) | Хорошая | < 100 tenants, требуется hard isolation |
| Общая коллекция + filter | Логическая | Низкая | Деградирует при большом N | 1000+ tenants, небольшие объёмы |
| Custom sharding | Физическая по шарду | Средняя | Отличная при любом N | Production: 10-10000 tenants |
**Критическое предупреждение по паттерну 2:** payload-фильтр обеспечивает логическую изоляцию, но не физическую. Если в коде забыть передать `filter: { must: [{ key: 'tenant_id', match: ... }] }` - один тенант получит данные другого. Это реальная угроза безопасности в multi-tenant SaaS. Всегда инкапсулируйте поиск в сервис, который автоматически добавляет фильтр.
Ваш SaaS: 5000 клиентов, у каждого по 1000-5000 документов. Требования: изоляция данных, управляемость, хорошая производительность. Какой паттерн?
Custom Sharding: физическая изоляция по tenant_id
**Custom Sharding** - лучший подход для production мультитенантности. Одна коллекция, но данные физически разделены по шардам на основе `shard_key`. Qdrant автоматически роутит запросы к правильным шардам. Масштабируется на тысячи tenants.
**Defence in depth:** даже при использовании custom sharding - дублируйте `tenant_id` в payload и добавляйте payload-фильтр в поисковый запрос. Если в коде ошибочно передан неверный shard_key, payload-фильтр будет последней линией защиты. Двойная защита = безопаснее.
Тенант удаляет аккаунт. Как эффективно удалить все его данные при custom sharding?
Безопасность: риски и митигации при мультитенантности
**Qdrant не имеет встроенной аутентификации на уровне тенанта.** API ключ даёт доступ ко всем коллекциям. Изоляция данных - ответственность приложения. Разберём реальные векторы атак и как их закрыть.
| Риск | Вектор атаки | Митигация |
|---|---|---|
| Data leakage | Забытый/неверный filter | TenantScopedRepository + defence in depth |
| Cross-tenant access | Прямой доступ к Qdrant API | Qdrant за firewall, только через backend API |
| Tenant enumeration | Угадать shard_key другого тенанта | UUID v4 как tenant_id (непредсказуемы) |
| GDPR violation | Данные не удаляются при деактивации | deleteShardKey() при удалении аккаунта + аудит-лог |
«Payload-фильтр по tenant_id обеспечивает такую же безопасность как отдельные коллекции»
Payload-фильтр - логическая изоляция. Qdrant не гарантирует на уровне API что запросы без tenant_id фильтра не вернут чужие данные. Custom sharding + TenantScopedRepository - production-ready подход.
В отличие от PostgreSQL Row Level Security (где изоляция встроена в движок), Qdrant применяет фильтры в runtime запроса. Ошибка в приложении = потенциальная утечка. Архитектурная инкапсуляция в TenantScopedRepository + custom sharding устраняет этот риск.
Клиент требует: «Докажите что наши данные физически изолированы от других клиентов». Какой паттерн и аргументы?
Ключевые идеи
- **Паттерн 1** (отдельная коллекция): полная изоляция, но < 100 тенантов — иначе управленческий кошмар
- **Паттерн 2** (общая коллекция + filter): простой, масштабируется до тысяч, но логическая изоляция — риск при ошибке в коде
- **Паттерн 3** (custom sharding): физическая изоляция по шарду, deleteShardKey для быстрого offboarding, лучший выбор для production
- **Defence in depth:** shard_key + payload filter в одном запросе — двойная защита
- **TenantScopedRepository** — инкапсулируй логику изоляции, не дай коду напрямую вызывать Qdrant без tenant контекста
Что дальше
Мультитенантность настроена. Финальный шаг - интегрировать всё в NestJS сервис с proper DI, BullMQ async indexing и тестами.
- NestJS интеграция — Обернуть TenantScopedRepository в NestJS module/service паттерн
- Production RAG Pipeline — RAG pipeline с учётом мультитенантности - фильтры в поиске, изоляция при upsert
- Фильтрация с payload — Полный синтаксис фильтров используемых в мультитенантных запросах
Вопросы для размышления
- При custom sharding: если у тенанта 100 документов и у тенанта 1,000,000 документов они на разных шардах. Как это влияет на балансировку нагрузки? Как Qdrant распределяет запросы?
- GDPR требует право на удаление данных ('right to be forgotten'). Как deleteShardKey() соответствует этому требованию лучше чем payload-фильтр soft-delete?
- Представьте что разработчик по ошибке не передал shard_key при поиске в multitenant коллекции. Что произойдёт? Какой payload-фильтр нужен чтобы предотвратить утечку?