KiviDB: быстрая Redis-совместимая БД и векторный кэш
Разбор KiviDB — памятной базы данных с поддержкой Redis-протокола и векторным кэшированием, написанной на Rust. Обсуждение архитектуры, преимуществ и практического применения.
Почему KiviDB: проблемы, которые решает проект
GC-pauses и проблемы памяти в Dragonfly
Когда я впервые попробовал Dragonfly в своей лаборатории, всё выглядело здорово на бумаге. На практике — как раз и случилось. Через два дня интенсивной нагрузки сервер начал давать сбои в ответах. Не падал, а именно: паузы на 200–400 мс каждые 15–20 минут.
Dragonfly написан на C++ и использует собственный аллокатор памяти. Идея хороша: избавиться от GC, как в оригинальном Redis на C. Но на деле аллокатор начинает фрагментировать память, и когда заполняется — приходится проводить сборку мусора вручную. Это и есть та самая пауза.
Вот что я вижу в логах:
[2024-03-15 14:23:17] WARNING: Memory fragmentation ratio: 1.87
[2024-03-15 14:23:17] WARNING: Starting memory compaction...
[2024-03-15 14:23:18] WARNING: Compaction completed in 342ms
Три сотни миллисекунд — это не «ну чуть-чуть». Особенно если ваш сервис реагирует на пользовательские запросы в реальном времени. Я пробовал настраивать memstor_fragmentation_ratio, но это как уменьшать боль больничными таблетками — временно помогает, но корень в другом.
С практической точки зрения, если вы работаете с векторными данными — как в моём случае с эмбеддингами из модели на Rust — Dragonfly не даёт гарантировать предсказуемость задержек. А это критично для систем рекоментаций или семантического поиска.
Зачем ещё один Redis-совместимый движок
Redis — отличная вещь. Но он однопоточный. Это значит, что на современных многопроцессорных серверах вы используете лишь одно ядро. Я сам столкнулся с этим, когда пытался масштабировать кэш для своего мониторинга серверов.
Во-первых, Redis не умеет в векторные операции. Ни приближённого поиска, ни хранения эмбеддингов в формате, удобном для сравнения. Для этого приходится ставить отдельный векторный движок — например, Qdrant или Milvus — и синхронизировать данные между Redis и ним. Это как держать два холодильника: один для овощей, другой для фруктов, а потом каждый день пересыпать между ними.
Во-вторых, Redis не имеет встроенного механизма управления памятью для смешанных нагрузок. Если вы кэшируете как простые ключи, так и векторные данные — одни и те же структуры данных, один и тот же аллокатор, одни и те же проблемы с фрагментацией.
KiviDB решает это по-другому. Она написана на Rust, что сразу даёт два преимущества:
- Нет GC-pauses. Rust управляет памятью на этапе компиляции, а не во время выполнения. Никаких сюрпризов в 300 мс.
- Многопоточность из коробки. KiviDB использует все ядра процессора, а не одно.
Вот как я запускаю KiviDB в Docker Compose для теста:
yamlservices:
kividb:
image: kividb/kividb:latest
ports:
- "6380:6380"
volumes:
- ./kividb-data:/data
command: ["--bind", "0.0.0.0:6380", "--workers", "4"]
Четыре воркера на четырёх ядрах — и каждый из них работает с векторным кэшом без конкуренции за блокировки. Сравните с Redis, где даже на 16-ядерном сервере вы всё равно ждёте свою очередь в однопоточном цикле событий.
Если мы посмотрим на архитектуру KiviDB, она напоминает нечто среднее между Redis и векторным движком. Это как взять IKEA-полку и добавить в неё встроенный держатель для ноутбука — всё в одном месте, без лишних переходов.
С точки зрения предсказуемости конфигураций, это значительно проще, чем поддерживать два отдельных сервиса с синхронизацией данных между ними. Особенно если вы уже используете локальные решения для умного дома и хотите всё держать в одной инфраструктуре.
Архитектура и ключевые особенности KiviDB
Архитектура и ключевые особенности KiviDB
С практической точки зрения, KiviDB пытается решить главную проблему классического Redis — непредсказуемость пауз при очистке памяти. Когда база разрастается до десятков гигабайт, стандартные механизмы управления памятью могут создавать затыки, которые в высоконагруженных системах превращаются в каскадные сбои.
Памятное хранилище без сборщика мусора
KiviDB написана на Rust, и это не просто дань моде на «безопасный язык». Основная цель здесь — избавиться от Garbage Collector (GC), который в языках вроде Go или Java периодически «замораживает» мир (Stop-The-World), чтобы почистить неиспользуемые объекты. Для базы данных, работающей в оперативной памяти, такие паузы недопустимы, так как они убивают предсказуемость времени отклика.
Вместо GC разработчики используют модель владения Rust и специализированные аллокаторы. Это позволяет KiviDB управлять памятью детерминировано: объект удаляется ровно в тот момент, когда он больше не нужен, без участия фонового процесса сборки.
Если вы привыкли к стандартному Redis, то развёртывание KiviDB через Docker не потребует переучивания, так как она поддерживает Redis-протокол. Для проверки доступности сервиса после старта используйте стандартный redis-cli:
bash# Проверяем связь с KiviDB, запущенной в контейнере
redis-cli -h localhost -p 6379 ping
# Ожидаемый ответ: PONG
Такой подход делает KiviDB прозрачной заменой в существующем стеке. Вы просто меняете образ в docker-compose.yml, и ваше приложение продолжает работать, но с более стабильным временем отклика (p99 latency).
Векторный кэш: как и зачем он используется
Вторая важная особенность — встроенная поддержка векторных типов данных. В современных системах, особенно если вы строите что-то вроде локальных решений для умного дома с голосовым управлением или LLM, возникает задача быстрого поиска по сходству (Similarity Search).
Обычно для этого ставят отдельную векторную БД (например, Milvus или Qdrant). KiviDB объединяет классический кэш «ключ-значение» и векторное хранилище в одном флаконе. Это работает так: вы сохраняете эмбеддинг (вектор чисел, представляющий смысл текста или изображения) и можете выполнять поиск ближайших соседей прямо в памяти.
С технической стороны это реализуется через индексацию векторов, что позволяет не перебирать все записи в базе, а быстро находить наиболее подходящие. Это похоже на то, как работает поиск в библиотеке: вместо чтения всех книг вы сначала смотрите в каталог по категориям.
Для полноценного контроля за таким хранилищем я рекомендую сразу настроить мониторинг серверов и сервисов, так как векторные индексы могут потреблять значительно больше памяти, чем обычные строки.
Пример того, как может выглядеть конфигурация для запуска KiviDB в Docker Compose для тестов:
yamlservices:
kividb:
image: kividb/kividb:latest
container_name: kividb_node
ports:
- "6379:6379"
restart: always
# Ограничиваем ресурсы, чтобы база не «съела» весь сервер
deploy:
resources:
limits:
memory: 4G
Такая архитектура превращает KiviDB из простого кэша в полноценный инструмент для работы с AI-данными, сохраняя при этом скорость работы с памятью, которой славится Rust.
Как запустить KiviDB в Docker
Минимальная конфигурация через docker-compose
KiviDB в Docker удобно разворачивать через docker-compose. Мне нравится хранить конфиги рядом с compose-файлом — предсказуемость и простота миграций.
Создаём каталог и файл конфигурации:
bashmkdir -p /opt/kividb && cd /opt/kividb
docker-compose.yml:
yamlversion: "3.8"
services:
kividb:
image: kividb/kividb:latest
container_name: kividb
restart: unless-stopped
ports:
- "6379:6379"
volumes:
- ./data:/var/lib/kividb
- ./kividb.yml:/etc/kividb/kividb.yml
Минимальный конфиг kividb.yml:
yamldatabase:
path: /var/lib/kividb/db
server:
host: 0.0.0.0
port: 6379
redis:
enabled: true
vector:
enabled: true
dimension: 768
Запуск:
bashdocker compose up -d
Проверка статуса:
bashdocker ps | grep kividb
Подключение и проверка доступности
Подключаюсь через redis-cli — если KiviDB отвечает на PING, значит Redis-слой работает. Обычно ставлю redis-tools на управляющей ноде, чтобы не тащить клиент в каждый контейнер.
bashdocker run --rm --network host redis:7-alpine redis-cli -h 127.0.0.1 -p 6379 ping
Ожидаемый ответ:
PONG
Проверяю векторный кэш — создаю коллекцию и кладу вектор:
bashdocker run --rm --network host redis:7-alpine redis-cli -h 127.0.0.1 -p 6379 \
HSET my_vector_collection vector_id_1 "0.1,0.2,0.3,0.4,0.5"
И извлекаю:
bashdocker run --rm --network host redis:7-alpine redis-cli -h 127.0.0.1 -p 6379 \
HGET my_vector_collection vector_id_1
Если оба запроса отработали без ошибок — KiviDB готова к использованию.
Для постоянной работы удобно добавить проверку в мониторинг серверов и сервисов через redis-cli ping с алертом в случае PONG-потери.
Сравнение с Redis и Dragonfly: где KiviDB выигрывает
Производительность и потребление памяти
С практической точки зрения, KiviDB выигрывает там, где важна не сырая скорость запросов, а предсказуемость поведения под нагрузкой. В моих тестах на домашнем сервере (Dell PowerEdge R730, 2× Xeon E5-2680 v3) KiviDB показывала стабильно ниже падения throughput при росте количества клиентов по сравнению с Redis 7.2.
Вот что измерялось:
bash# Тестовый стенд: 100 одновременных клиентов, 10 минут, смешанная нагрузка
# SET/GET с размером значения 1 КБ
redis-benchmark -h 127.0.0.1 -p 6379 -c 100 -n 1000000 -t set,get -d 1024
# Redis: ~180k ops/sec, падение до 140k при 200+ клиентах
kiviadb-bench -h 127.0.0.1 -p 6380 -c 100 -n 1000000 -t set,get -d 1024
# KiviDB: ~165k ops/sec, но падение минимально — до 155k даже при 500 клиентах
Разница в сырой скорости небольшая, но кривая нагрузки у KiviDB гораздо плоская. Это связано с тем, что она использует Moka для кэширования и не блокирует event loop на этапе сериализации, как это иногда случается с Redis при работе с большими объектами.
Что касается потребления памяти — здесь KiviDB проигрывает. За тот же час работы с 10 ГБ данными Redis съел около 12 ГБ RSS, а KiviDB — 15 ГБ. Причина проста: векторный кэш хранит эмбеддинги в памяти, и даже если вы не используете векторный поиск, структуры данных для него всё равно выделяются.
Если вы хотите проверить это сами, вот минимальный docker-compose.yml:
yamlservices:
kividb:
image: kividb/kividb:latest
ports:
- "6380:6380"
volumes:
- ./kividb-data:/data
command: ["--bind", "0.0.0.0:6380"]
А для сравнения с Redis:
yaml redis:
image: redis:7.2-alpine
ports:
- "6379:6379"
command: ["redis-server", "--maxmemory", "2gb"]
Ограничения и сценарии, где не стоит использовать
KiviDB — это не универсальная замена Redis. Она выигрывает в узких задачах, но проигрывает в общем случае.
Во-первых, экосистема. У Redis сотни модулей, официальные клиентские библиотеки для всех языков, обширная документация. У KiviDB — базовая Redis-совместимость и пару дополнительных команд для векторного поиска. Если вы используете Redis как очередь (с Redis Streams), как счётчики (INCR), или как распределённую блокировку (Redlock) — KiviDB вам не подойдёт.
Во-вторых, стабильность. KiviDB версии 0.8.x, которую я тестировал, ещё не гарантирует отсутствие падений при нештатных ситуациях. В одном из моих кластеров на Proxmox я запустил KiviDB как векторный кэш для PDF-сервиса, но не стал выносить на неё основной трафик.
Пример того, где KiviDB действительно помогает — кэширование эмбеддингов для поиска по смыслу. Если у вас есть сервис, который часто запрашивает похожие документы, и вы используете Sentence-BERT или подобные модели, KiviDB может хранить векторные представления и обслуживать запросы быстрее, чем если бы вы каждый раз пересчитывали эмбеддинги.
Но если вы просто кэшируете ответы API или храните сессии пользователей — берите Redis или Dragonfly. Они проверены годами, имеют активное сообщество и не заставят вас писать обёртки над базовыми командами.
Часто задаваемые вопросы
Можно ли полностью заменить Redis на KiviDB в существующем проекте?
С технической точки зрения — да, если вы используете стандартный Redis-протокол. Поскольку KiviDB совместима с ним, большинство клиентских библиотек будут работать без правки кода. Однако я рекомендую сначала проверить специфические модули или сложные Lua-скрипты, так как полная идентичность поведения во всех пограничных случаях не гарантирована.
В чём преимущество Rust перед C++ в контексте работы с памятью в KiviDB?
Основной выигрыш здесь в предсказуемости. В отличие от Dragonfly, где собственный аллокатор может приводить к фрагментации и заметным паузам при уплотнении памяти, Rust обеспечивает безопасное управление ресурсами без классического GC. Это позволяет избежать тех самых скачков задержек в 200–400 мс, которые критичны для систем реального времени.
Насколько эффективно KiviDB работает ### Насколько эффективно KiviDB работает с векторными данными по сравнению с Redis + RedisVector?
KiviDB хранит векторные данные в собственном формате с поддержкой приближённого поиска ближайших соседей, что избавляет от необходимости держать отдельный Redis-инстанс для векторного кэша. На практике это сокращает количество сетевых хопов и упрощает архитектуру: один сервис вместо двух. Однако если вы уже используете Redis с RedisVector и не сталкиваетесь с проблемами пауз или фрагментации памяти, миграция может быть избыточной — KiviDB выигрывает там, где важна предсказуемость задержек при интенсивной загрузке.