Дайджесты новостей
Инфографический чертёж архитектуры шардирования KiviDB с распределением потоков по ядрам процессора и кольцевыми буферами io_uring.

KiviDB: многопоточная альтернатива Redis и Valkey на Rust

Представь классическую картину: вечер, на сервисе начинается пик трафика, время отклика API внезапно ползет вверх, а графики задержек p99 окрашиваются в тревожный красный цвет. Ты заходишь на сервер по SSH, открываешь утилиту htop и видишь до боли знакомый парадокс: одно процессорное ядро намертво забито в 100%, в то время как остальные тридцать одно ядро мощного 32-ядерного процессора безмятежно простаивают.

Виновник этой драмы хорошо известен любому бэкендеру: легендарный Redis. Сальваторе Санфилиппо в далеком 2009 году заложил в Redis однопоточный цикл обработки событий (event loop). Никаких мьютексов, предельно простой Си-код и строгая атомарность. Но серверное железо давно масштабируется числом ядер, а наш швейцарский нож так и остался заперт в границах одного ядра.

Эту архитектурную боль берется решить KiviDB — новое открытое in-memory хранилище со встроенным поиском, написанное на Rust. Давай разберем, как устроена эта база под капотом и где скрываются компромиссы.

Потолок одного ядра: цена кластеризации

Когда одно ядро Redis упирается в 100%, у тебя остается два пути: мириться с очередями или запускать Redis Cluster. Но кластеризация на одном физическом сервере — это ощутимый инженерный налог:

  • Приходится настраивать сетевую маршрутизацию между десятками процессов и поддерживать порты от 6379 до 6410;
  • Любая составная операция вроде MGET или транзакции требует, чтобы ключи лежали в одном хэш-слоте, вынуждая прописывать хэш-теги вида user:{1001}:profile;
  • Клиентские библиотеки в коде вынуждены отслеживать топологию узлов и перехватывать редиректы MOVED.

Аналогия простая: представь супермаркет с 32 кассами, где работает только один кассир. Кластеризация Redis — это попытка построить рядом еще тридцать ларьков со своими заборами вместо того, чтобы посадить кассиров за соседние кассы.

Форки вроде Valkey сохранили верность классической архитектуре ради совместимости, а Dragonfly выбрал C++. KiviDB решает проблему с помощью модели памяти на Rust.

Шардинг по ядрам и магия io_uring

В основе KiviDB лежит шардированная по ядрам архитектура (sharded-by-core in-memory design). Пространство ключей делится на независимые партиции, а каждый рабочий поток жестко привязывается к выделенному ядру CPU (CPU affinity).

Когда в базу поступает пакет команд, KiviDB направляет ключ строго в тот поток, которому принадлежит этот сектор памяти. Каждый поток безраздельно владеет своей партицией, поэтому внутри KiviDB нет глобальных блокировок или мьютексов.

Сетевой ввод-вывод в Linux построен на асинхронном системном интерфейсе io_uring через крейт tokio_uring. Вместо переключений контекста между пространством пользователя и ядром ОС, KiviDB обменивается с ядром кольцевыми буферами. Если io_uring заблокирован политиками безопасности, рантайм автоматически переключается на Tokio.

KiviDB прозрачно реализует бинарные протоколы RESP2 и RESP3. Стандартные библиотеки (redis-py, go-redis, очереди BullMQ или Celery) подключаются к серверу без изменения кода. По умолчанию KiviDB слушает порт 6380, что позволяет развернуть его рядом с боевым Redis для тестов.

Встроенный векторный поиск HNSW

С развитием RAG разработчикам часто требуется искать семантически близкие векторы документов. Обычно для этого поднимают отдельную СУБД (Qdrant, Milvus) либо тяжелые расширения.

В KiviDB встроен векторный и полнотекстовый движок, совместимый с синтаксисом Redis Stack (FT.*). Поддерживаются индексы FLAT (точный перебор) и HNSW (граф для приближенного поиска ближайших соседей). Векторы хранятся в формате FLOAT32 внутри стандартных хэшей (HASH).

Главная инженерная находка: в отличие от систем, где при рестарте векторный граф часами строится заново, KiviDB при вызове SAVE сериализует в дамп dump.kdb сам рассчитанный граф HNSW. При перезапуске готовый граф считывается за секунды. Также поддерживается упреждающий журнал дозаписи (--aof).

Проверка боем: подключение и бенчмарки

Запустим тестовый инстанс через Docker и проверим подключение:

# Запуск контейнера KiviDB на порту 6380
docker run -d --name kividb-test -p 6380:6380 kividb/kividb:latest

# Проверяем соединение и пишем данные по протоколу RESP
redis-cli -p 6380 SET user:1001:session '{"role":"developer"}' EX 3600
redis-cli -p 6380 GET user:1001:session

Теперь создадим векторный индекс HNSW и выполним поиск пяти ближайших соседей (KNN 5):

# Создание индекса HNSW для четырехмерных векторов
redis-cli -p 6380 FT.CREATE idx:articles ON HASH PREFIX 1 article: SCHEMA \
  title TEXT embedding VECTOR HNSW 10 TYPE FLOAT32 DIM 4 DISTANCE_METRIC COSINE M 16 EF_CONSTRUCTION 128

# Запись статьи и векторный поиск
redis-cli -p 6380 HSET article:42 title "Шардинг" embedding "\xcd\xcc\xcc=\xcd\xccL>\x9a\x99\x99>\xcd\xcc\xcc>"
redis-cli -p 6380 FT.SEARCH idx:articles "*=>[KNN 5 @embedding $vec]" PARAMS 2 vec "\xcd\xcc\xcc=\xcd\xccL>\x9a\x99\x99>\xcd\xcc\xcc>" DIALECT 2

На 32-ядерном сервере AWS Graviton3 c7gn.metal при конвейеризации KiviDB показал 21,2 млн GET/s и 19,2 млн SET/s, опередив Dragonfly 2.0.2 (9,1 млн GET/s) в 2,3 раза, а Redis 8.10 (2,4 млн GET/s) почти в 9 раз. Задержка p99 под нагрузкой 1600 клиентов составила 13,9 мс против 33,9 мс у Dragonfly и 72,1 мс у Redis.

Ранее мы разбирали смежную задачу управления памятью в материале Отказоустойчивый запуск ArgoCD в режиме Stateless на bare-metal Kubernetes, где надежность кэша напрямую влияла на стабильность контроллера.

Подводные камни и границы применимости

Прежде чем внедрять KiviDB, важно учесть скрытые нюансы:

  1. Лицензия AGPL-3.0: Сетевые клиенты не подпадают под требования копилефта, но службы безопасности многих корпораций запрещают AGPL в инфраструктуре.
  2. Профиль seccomp: Для полной скорости io_uring контейнеру нужен флаг --security-opt seccomp=unconfined, что в закрытых кластерах требует согласования.
  3. Кросс-шардовые операции: Команды над ключами из разных партиций требуют межпоточной синхронизации, снижая преимущество шардинга.
  4. Зрелость: Проект находится на версии 1.1 (около 800 звезд на GitHub), поэтому боевой опыт еще нарабатывается.
Практика безопасного внедрения в стек

Запусти KiviDB на резервном порту 6380 и настройте зеркалирование части трафика чтения с продакшена через прокси. Это покажет реальное поведение ваших нагрузок без риска для пользователей.