Спустя некоторое время экспериментов с HurriCache я пришёл к важному выводу: чтобы существующие проекты могли попробовать решение с минимумом изменений, нужен способ быстрого входа без переписывания кода.
Строгая типизация в HurriCache — осознанный выбор. Мы явно создаём скаляры (целые числа, числа с плавающей точкой, байтовые массивы) и контейнеры (List, Vector, Queue, OrderedSet, HashSet, SortedMap, HashMap) без ленивого приведения типов. Это даёт предсказуемость поведения и контроль над памятью, но повышает порог входа: нельзя просто взять и «подставить» HurriCache вместо Redis, не меняя семантику работы с данными.
Чтобы решить эту проблему, мы добавили RESP‑коннектор, совместимый с Redis‑клиентами. И сразу столкнулись с фундаментальными различиями моделей данных: то, что в Redis «просто строка», в HurriCache требует явного типа и стратегии создания.
Первые сложности: две модели данных — два мира
Главная проблема при внедрении RESP‑коннектора оказалась фундаментальной: модели данных Redis и HurriCache устроены принципиально по‑разному. Чтобы коннектор работал с моделью данных HurriCache нам пришлось потрудится в преобразовании моделей данных и разработать достаточно неплохой подход.
Redis | HurriCache | |
Тип данных | Всё — строка. Даже если строка похожа на число, это всё равно строка. | Строгая типизация: скаляры (int, float, byte array) и контейнеры создаются явно. |
Создание объекта | Ленивое: если ключа нет, он создаётся при первой операции. | Явное: тип должен быть известен заранее; ленивого создания нет. |
Преобразования | Неявные: можно работать с «числовой строкой» как с числом. | Запрещены: нельзя трактовать число как строку и наоборот. Но можно заменить. Было строкой, стало числом |
Работа с контейнером | Универсальные структуры, поведение часто зависит от команды. | Разные контейнеры (List, Vector, Queue, Ordered/Hashed Sets Ordered/Hashed Maps) с чёткими API и известными ограничениями. |
Основная задача RESP коннектора — не пытаться сделать из HurriCache очередной форк Redis, а дать разработчикам попробовать HurriCache через привычное RESP API при этом не потерять плюсы строгой типизации HurriCache, которые продолжают работать нативно через GRPC
Решение оказалось компромисом между ограниченым ленивым созданием объектов и строгим созданием. «Ленивость» была добавлена только в отдельные места, что позволило сохранить преимущества строгой типизации, но позволило RESP коннектору комфортно работать с HurriCache ядром. Это позволило:
Сохранить преимущества строгой типизации в ядре
Дать RESP‑коннектору возможность работать с ядром: для совместимых команд тип можно однозначно вывести, и ленивое создание здесь не порождает неопределённости.
Не размывать гарантии системы: если команда требует поведения, которое противоречит строгой типизации (например, неявное приведение типов), она отклоняется с понятным кодом ошибки, причем система ведет себя одинаково как с точки зрения GRPC так и со стороны RESP API
Что получилось в итоге: принципы работы RESP‑коннектора
После анализа самых частых команд Redis мы реализовали RESP‑коннектор, руководствуясь чёткими принципами, которые сохраняют гарантии HurriCache:
Принцип строгой интерпретации типов.
Если создаваемое значение похоже на число (целое или с плавающей точкой), оно трактуется как скаляр соответствующего типа. Если это не число — значение считается байтовым массивом (byte array), и любые попытки трактовать его иначе отклоняются с ошибкой.
Единый контейнер для совместимости.
В HurriCache есть несколько типов контейнеров (List, Vector, Queue), но для совместимости мы используем Vector как универсальный контейнер. Он поддерживает как random access, так и операции, похожие на очередь.
Ограниченный функционал по дизайну.
Задача RESP‑коннектора — дать возможность интеграции, а потом принять решение о миграцмм, а вовсе не дублировать весь Redis API в HurriCache. Поэтому расширенные возможности (например, распределённые блокировки) оставлены в gRPC‑интерфейсе. RESP остаётся «чистым» коннектором с базовым функционалом и не более того
Осознанные границы несовместимости.
Некоторые различия в интерфейсе все же присутствуют, например:
OrderedSet: HurriCache допускает одинаковые ключи с разным весом, а Redis — нет.
Вес элемента: в HurriCache вес —
uint64_t, в Redis —double.
В таких случаях коннектор преобразует double в uint64_t и не пытается быть настоящим Redis.
Через неделю работы был реализован полноценный RESP‑коннектор который позволил HurriCache быть немного Redis Friendly
Тестирование: совместимость и производительность
Для тестирования RESP‑коннектора мы выбрали два независимых вектора проверки:
Функциональная совместимость — насколько поведение HurriCache соответствует ожиданиям Redis‑клиентов.
Производительность — как накладные расходы на трансляцию типов и ограниченный ленивый режим влияют на QPS и задержки.
Проверка совместимости
Для тестирования совместимости мы использовали репозиторий resp-compatibility — стандартный набор тестов, который проверяет поведение сервера по спецификации RESP и сравнивает его с Redisами от 4.0 до современных версий.
Результаты: коннектор прошёл 50% всех тестов на совместимость с Redis 4.0 — 7.0. В плане реализованых команд совместимость больше 80%, здесь важно понимать, что эта цифра отражает не «качество реализации», а границы совместимости: мы не эмулируем семантику, которая противоречит строгой типизации HurriCache, а реализуем именно коннектор.
При этом все реализованные команды вели себя в точности как Redis 7.0 — в рамках заявленных ограничений (запрет неявных преобразований, отличия в OrderedSet и так далее). Это значит, что типовые сценарии (SET/GET, базовые операции над списками и множествами) работают предсказуемо, а «несовместимые» кейсы явно отклоняются с понятными кодами ошибок — так, чтобы разработчик мог быстро понять причину.
Производительность
Чтобы оценить влияние RESP‑коннектора на производительность, мы использовали redis‑benchmark в условиях, приближённых к реальным нагрузкам:
Объём данных: 1 млн ключей,
размер значения — 512 Б.
Нагрузка: 100 клиентов.
Без пайплайнинга: в реальных сценариях пайплайнинг встречается не всегда, и важно было проверить поведение при обычных последовательных запросах.
Redis 7.0 | HurriCache | Разница | |
SET QPS | 140 528 | 176 025 | +25% |
SET p50 | 0.615 мс | 0.279 мс | вдвое ниже задержка |
GET QPS | 148 942 | 190 730 | +28% |
GET p50 | 0.487 мс | 0.271 мс | вдвое ниже задержка |
GET max | 5.51 мс | 1.35 мс | в 4 раза ниже хвост |
Интерпретация результатов:
Скорость сопоставима или выше даже с трансляцией типов. Это говорит о том что архитектура работает так как ожидалось
Стабильность хвоста. Максимальная задержка GET у HurriCache — всего 1.35 мс против 5.5 мс у Redis.
Изоляция накладных расходов. Так как HurriCache многопоточный то это позволяет распределять нагрузки на нужные ядра процессора. RESP коннектор не влияет на основной путь обработки и не конкурирует с ядром HurriCache
Где можно попробовать
Если хочется быстро поднять HurriCache и протестировать RESP‑коннектор есть готовые Docker‑образы:
docker.io/alexaborisov/fastcache-standalone-noavx512:latest— версия без AVX‑512. Подойдёт для большинства сред, включая те, где расширенные инструкции недоступны.docker.io/alexaborisov/fastcache-standalone:latest— полная версия с поддержкой AVX‑512 (если инфраструктура позволяет).
Обе версии регулярно обновляются, так что latest — это всегда актуальная сборка.
Клиенты (C++, Java, Go, Python) доступны в моём профиле(habr) — можно сразу подключить привычный стек и проверить типовые сценарии.
Если планируете пилот на кластере из нескольких нод — напишите в комментариях или в личку. Обсудим конфигурацию (3–4 ноды, мониторинг, лимиты по воркерам), чтобы подобрать оптимальный вариант под ваши сценарии и SLA.
Обратите внимание: исходники сервера пока закрыты.

