Спустя некоторое время экспериментов с 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‑коннектора мы выбрали два независимых вектора проверки:

  1. Функциональная совместимость — насколько поведение HurriCache соответствует ожиданиям Redis‑клиентов.

  2. Производительность — как накладные расходы на трансляцию типов и ограниченный ленивый режим влияют на 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‑образы:

Обе версии регулярно обновляются, так что latest — это всегда актуальная сборка.

Клиенты (C++, Java, Go, Python) доступны в моём профиле(habr) — можно сразу подключить привычный стек и проверить типовые сценарии.

Если планируете пилот на кластере из нескольких нод — напишите в комментариях или в личку. Обсудим конфигурацию (3–4 ноды, мониторинг, лимиты по воркерам), чтобы подобрать оптимальный вариант под ваши сценарии и SLA.

Обратите внимание: исходники сервера пока закрыты.