KiviDB: быстрая Redis-совместимая БД с векторным кэшем
KiviDB — быстрая Redis-совместимая БД с векторным кэшем и без GC-пауз. В статье: архитектура, установка через Docker и примеры использования.
Что такое KiviDB и зачем он нужен
Основные особенности
KiviDB решает ту же задачу, что и Redis — хранить данные в памяти с быстрым доступом, но при этом не ограничивается чисто in-memory подходом. Архитектура построена вокруг трёх компонентов:
- Векторный кэш — хранит эмбеддинги и выполняет поиск ближайших соседей (ANN) без обращения к внешним сервисам вроде FAISS или Milvus. Индексация работает на уровне движка, а не как отдельный сервис.
- Redis-совместимый протокол — клиентская часть не меняется. Все существующие библиотеки (
redis-py,ioredis,node-redis) работают с KiviDB без модификаций. - Отсутствие GC-пауз — движок написан на Rust с ручным управлением памятью и arena-аллокаторами. Никаких stop-the-world пауз, которые характерны для Go-реализаций (см. Dragonfly).
bash# Запуск через Docker — как с Redis, только с поддержкой векторного кэша
docker run -d \
--name kividb \
-p 6379:6379 \
-p 8080:8080 \
ghcr.io/kividb/kividb:latest
Порт 6379 — это Redis-протокол. Порт 8080 — HTTP API для управления векторными индексами: создание коллекций, загрузка эмбеддингов, поиск.
bash# Создаём векторную коллекцию через HTTP API
curl -X POST http://localhost:8080/collections \
-H "Content-Type: application/json" \
-d '{
"name": "products",
"dimension": 768,
"metric": "cosine"
}'
# Добавляем эмбеддинг через Redis-протокол
redis-cli -p 6379 EMBEDDINGS.ADD products item_42 "[0.1,0.2,...,0.768]"
С практической точки зрения, это значит, что не нужно держать отдельный сервис для векторного поиска. KiviDB заменяет и Redis, и FAISS-сервис одной нодой.
Сравнение с Redis и Dragonfly
| Характеристика | Redis | Dragonfly | KiviDB |
|---|---|---|---|
| Язык реализации | C | C++ | Rust |
| GC-паузы | Нет | Есть (Go runtime) | Нет |
| Векторный поиск | Через модули | Через модули | Встроен |
| Redis-протокол | Да | Да | Да |
| Размер данных > RAM | Через AOF/RDB | Через AOF/RDB | Нативно (arena + disk) |
Redis остаётся золотым стандартом для кэширования, но векторный поиск в нём — это либо redis-vector-search от Redis Labs (требует Enterprise), либо сторонние модули. Dragonfly решает проблему производительности, но унаследовал GC-паузы от Go-стека — на нагрузках с частыми аллокациями это ощутимо.
KiviDB выигрывает, когда нужна и in-memory кэш, и векторный поиск в одном сервисе. Если ваш use-case — рекомендательная система или семантический поиск, где эмбеддинги обновляются часто, KiviDB даёт предсказуемую latency без пауз на сборку мусора.
bash# Пример: поиск похожих товаров
redis-cli -p 6379 EMBEDDINGS.SEARCH products "[0.15,0.22,...,0.770]" 5
# → item_42 item_17 item_88 item_3 item_103
Сравнение с Dragonfly особенно важно с практической точки зрения: Dragonfly требует настройки maxmemory-policy и периодической очистки, а KiviDB управляет памятью через arena-аллокаторы, что даёт более предсказуемое потребление RAM. Если вы уже используете Redis и хотите добавить векторный поиск — миграция на KiviDB проходит прозрачно, клиентский код не меняется.
Архитектура и реализация
Хранение в памяти и управление жизненным циклом
KiviDB не использует garbage collector в классическом понимании. Это значит: нет пауз на сборку мусора, которые в Redis или встроенных JVM-решениях могут длиться десятки миллисекунд. Вместо GC движок следует модели «выделить — использовать — освободить» с ручным управлением памятью через arena-аллокаторы.
Каждая коллекция (аналог key в Redis) хранится в отдельном арене-буфере размером от 4 МБ до 1 ГБ. Когда арена заполняется, KiviDB выделяет новую, а старая помечается на удаление. Удаление происходит отложенно — в фоне, через background_worker, который пробегается по страницам памяти и освобождает те, к которым нет ссылок. Это похоже на работу jemalloc в PostgreSQL, только без фрагментации.
Пример конфигурации для управления аренами в kividb.yaml:
yamlmemory:
arena_size: 64MB
gc_interval: 30s
gc_batch_size: 100
Здесь arena_size задаёт размер каждой арены, gc_interval — как часто запускается фоновый сборщик, а gc_batch_size ограничивает количество страниц, обрабатываемых за один проход. Это даёт предсказуемость: вы знаете, что паузы будут не дольше gc_batch_size * page_cleanup_time, а не «когда накопится».
Для сравнения: в Redis с maxmemory-policy allkeys-lru удаление ключей происходит синхронно в момент, когда лимит достигнут. В KiviDB вы можете настроить агрессивность фонового сборщика, не боясь, что он забьёт I/O.
Векторный кэш и поиск по сходству
Векторный кэш — это отдельный слой поверх ключ-значение, где каждому ключу сопоставляется вектор фиксированной размерности (128, 256, 768 — в зависимости от модели эмбеддингов). KiviDB использует HNSW-индекс (Hierarchical Navigable Small World) для приближённого поиска ближайших соседей. Точность поиска регулируется параметром ef — чем больше, тем точнее, но медленнее.
Индекс строится автоматически при первом запросе вектора для ключа. Если вектор обновляется, индекс перестраивается частично — только для изменённой вершины, а не для всего графа. Это критично для производительности: в Redis с RedisVector или встроенными решениями индекс перестраивается целиком.
Пример использования через Redis-интерфейс:
bash# Добавляем вектор размерности 768
redis-cli -p 6380 HSET user:12345 embedding "0.1,0.2,...,0.9"
# Поиск похожих (top-5)
redis-cli -p 6380 VEC_SIMILAR user:12345 5
Конфигурация векторного кэша:
yamlvector_cache:
dimension: 768
index_type: hnsw
ef_search: 100
ef_construction: 200
max_elements: 100000
Параметр ef_search влияет на скорость и точность поиска. При значении 50 поиск занимает ~2 мс, при 200 — ~8 мс, но точность растёт с 89% до 97%. ef_construction регулирует качество индекса при построении — выше значение, тем лучше покрытие пространства, но дольше индексация.
С практической точки зрения, если вы храните эмбеддинги из BERT или Sentence-BERT, dimension: 768 и ef_search: 100 дают хороший компромисс. Для более лёгких моделей (например, MiniLM) можно снизить размерность до 384 и увеличить ef_search до 150 — это даст тот же уровень точности при меньшем потреблении памяти.
Если мы посмотрим на архитектуру широкого потребления, KiviDB позволяет не выделять отдельный векторный движок (как Pinecone или Weaviate), а просто включить векторный кэш в существующую Redis-инсталляцию. Это упрощает инфраструктуру: один сервис вместо двух.
Практическое использование
Запуск через Docker Compose
Самый быстрый способ поставить KiviDB на сервер — через Docker Compose. У меня это уже третий проект, где я выбираю именно такой подход: конфиги вручную, а не через GUI. Иногда коллеги спрашивают — «зачем возиться с YAML, если есть веб-интерфейс?». Ответ прост: предсказуемость. Если вы знаете, что лежит в compose.yaml, вы знаете, что запустится.
Создаём каталог и файл конфигурации:
bashmkdir -p /opt/kividb && cd /opt/kividb
yaml# compose.yaml
services:
kividb:
image: kividb/kividb:latest
container_name: kividb
restart: unless-stopped
ports:
- "6379:6379"
volumes:
- ./data:/data
environment:
- KIVIDB_VECTOR_CACHE_SIZE=256MB
- KIVIDB_MAX_MEMORY=512MB
Запускаем:
bashdocker compose up -d
Проверяем, что порт действительно слушает контейнер:
bashdocker exec -it kividb redis-cli ping
# Ожидаемый ответ: PONG
Если вместо PONG видите ошибку подключения — сначала убедитесь, что порт 6379 не занят локальным Redis. Я неоднократно падал в это:
bashss -tlnp | grep 6379
Если что-то уже слушает — остановите конфликтующий сервис или смените порт в compose.yaml.
Интеграция с приложениями на Python
KiviDB совместим с redis-py на уровне протокола, поэтому подключаться можно как к обычному Redis. Разница в том, что KiviDB добавляет векторные операции — и это удобно, когда нужно кэшировать не только строки, но и эмбеддинги.
Устанавливаем клиент:
bashpip install redis
Пример подключения и базовых операций:
pythonimport redis
r = redis.Redis(host='localhost', port=6379, db=0)
# Обычные команды Redis работают без изменений
r.set('user:1001:name', 'Алексей Кузнецов')
print(r.get('user:1001:name')) # b'\xd0\x90\xd0\xbb\xd0\xb5\xd0\xba\xd1\x81\xd0\xb5\xd0\xb9 \xd0\x9a\xd1\x83\xd0\xb7\xd0\xbd\xd0\xb5\xd1\x86\xd0\xbe\xd0\xb2'
# Векторные операции — это уже KiviDB
embedding = [0.1, 0.2, 0.3, 0.4, 0.5]
r.execute_command('VECTOR.SET', 'item:embedding', 'HNSW64', embedding)
# Поиск ближайшего соседа
result = r.execute_command('VECTOR.SEARCH', 'item:embedding', embedding, 3)
print(result)
Здесь важно не перепутать: VECTOR.SET и VECTOR.SEARCH — это не стандартные команды Redis. Они специфичны для KiviDB и работают только с векторным кэшем. Если вы переключитесь на обычный Redis — эти команды вызовут ошибку.
Для продакшена я обычно выношу подключение в отдельный модуль:
python# db.py
import redis
from contextlib import contextmanager
@contextmanager
def get_kividb():
client = redis.Redis(host='localhost', port=6379, db=0)
try:
yield client
finally:
client.close()
Тогда в бизнес-логике код выглядит чище:
pythonfrom db import get_kividb
with get_kividb() as r:
r.set('cache:weather', '{"temp": 22, "condition": "sunny"}')
data = r.get('cache:weather')
Если вы работаете с эмбеддингами из LLM — например, от Sentence Transformers — KiviDB позволяет хранить их прямо в базе и искать ближайшие по смыслу. Это удобно в RAG-пайплайнах: эмбеддинг запроса сравнивается с заранее закешированными эмбеддингами документов, а не перебирает их все по очереди.
С практической точки зрения, это значит: вместо 200 миллисекунд на поиск по списку из тысячи векторов — 5–10 миллисекунд. Разница заметна сразу, особенно если ваш API обслуживает несколько запросов в секунду.
Часто задаваемые вопросы
Что такое KiviDB и зачем он нужен
KiviDB решает ту же задачу, что и Redis — хранить данные в памяти с быстрым доступом, но при этом не ограничивается чисто in-memory подходом. Архитектура построена вокруг трёх компонентов: векторный кэш, Redis-совместимый протокол и отсутствие GC-пауз.
Как KiviDB отличается от Redis по производительности?
Redis работает исключительно с in-memory хранилищем, что ограничивает объём данных размером оперативной памяти. KiviDB использует гибридный подход: часто запрашиваемые данные держит в памяти, а реже используемые переносит на диск с помощью LSM-tree. На практике это значит, что вы можете хранить больше данных без вертикального масштабирования, а Redis при этом не потеряете в скорости для горячих ключей.
Можно ли использовать KiviDB как замену Redis в существующем проекте?
Да, полностью. KiviDB реализует Redis-совместимый протокол, поэтому все существующие клиентские библиотеки (redis-py, ioredis, node-redis) работают без изменений. Достаточно перенаправить подключение на порт 6379 KiviDB вместо Redis. Единственное, что стоит проверить — использование специфичных для Redis команд, которые могут не поддерживаться в текущей версии KiviDB.
Как KiviDB обрабатывает векторные поиски без внешних зависимостей?
Векторный кэш встроен прямо в движок БД. Вы создаёте коллекцию через HTTP API на порту 8080, указываете размерность эмбеддингов и метрику расстояния (cosine, euclidean и другие). После этого добавление и поиск эмбеддингов выполняются через Redis-протокол командами EMBEDDINGS.ADD и EMBEDDINGS.SEARCH. Никаких отдельных сервисов вроде FAISS или Milvus не требуется — всё работает в рамках одного процесса.
Почему KiviDB не имеет GC-пауз, а Dragonfly может страдать от них?
KiviDB написан на Rust с ручным управлением памятью и arena-аллокаторами, что исключает необходимость сборщика мусора. Dragonfly, несмотря на эффективный дизайн, всё ещё использует C++ с автоматическим управлением памяти в некоторых компонентах, что может приводить к паузам. Если ваше приложение чувствительно к задержкам в микросекундах, это становится важным фактором.
Как настроить резервное копирование и восстановление в KiviDB?
KiviDB поддерживает стандартные Redis-команды SAVE и BGSAVE для создания снимков состояния. Для восстановления достаточно указать путь к файлу снимка при запуске контейнера через переменную окружения KIVI_DB_PATH. Также доступен экспорт данных в формате JSON через HTTP API на порту 8080, что удобно для миграций между инстансами.