Продвинутые структуры: HyperLogLog, Bitmaps, Streams Stack

Помимо базовых структур, в Redis есть специализированные: они решают узкие задачи невероятно эффективно, экономя память в разы.

Как посчитать миллионы уникальных посетителей, потратив 12 КБ памяти вместо гигабайтов? HyperLogLog отвечает: пожертвуй точностью ради памяти.

Redis предлагает структуры за пределами строк, списков и множеств. Они «вероятностные» или «битовые» — жертвуют точностью или универсальностью ради колоссальной экономии памяти. Плюс модули Redis Stack расширяют возможности.

HyperLogLog: подсчёт уникальных

Чтобы посчитать число уникальных элементов (уникальные посетители, уникальные IP), обычное множество хранило бы каждый элемент — это много памяти. HyperLogLog оценивает количество уникальных с погрешностью около 0.81%, занимая всего ~12 КБ независимо от объёма.

PFADD visitors "user1" "user2" "user3"
(integer) 1
PFCOUNT visitors        # приблизительная оценка уникальных
(integer) 3
PFMERGE all v1 v2       # объединить несколько HLL

Bitmaps: флаги и присутствие

Bitmap — это строка, рассматриваемая как массив битов. Идеально для компактного хранения «да/нет» по миллионам ID: заходил ли пользователь сегодня, прочитано ли уведомление.

SETBIT online:2025-06-22 42 1   # пользователь 42 был онлайн
GETBIT online:2025-06-22 42     # 1
BITCOUNT online:2025-06-22      # сколько всего было онлайн

Миллион пользователей в bitmap — это всего ~125 КБ.

   Сравнение по памяти на подсчёт уникальных

   Множество (Set):   точно, но ~N * размер_элемента
                      10 млн IP -> сотни МБ

   HyperLogLog:       ~0.81% погрешности, ~12 КБ
                      10 млн IP -> 12 КБ (!)

   Bitmap (флаги):    1 бит на ID
                      10 млн флагов -> ~1.2 МБ

Redis Stack: модули

Redis Stack (а в новых версиях — ядро Redis) добавляет мощные модули:

  • RedisJSON — хранение и частичное изменение JSON-документов нативно, без сериализации всей строки.
  • RediSearch — полнотекстовый поиск и вторичные индексы поверх Redis, включая векторный поиск для AI.
  • RedisBloom — вероятностные структуры: Bloom-фильтр (проверка «точно нет в множестве»), Cuckoo, Count-Min Sketch, Top-K.
  • RedisTimeSeries — эффективное хранение временных рядов с агрегациями.

Демонстрация: приближённый счётчик уникальных на Python

Покажем идею вероятностного подсчёта: точное множество против оценки. Принцип HLL сложнее, но суть «жертвуем точностью ради памяти» видна так:

import hashlib

# Точный подсчёт: храним все элементы (как Set) — много памяти
exact = set()

# Приближённый: храним только "максимальное число ведущих нулей хеша"
# Это упрощённая идея HyperLogLog
max_zeros = [0]

def add(item):
    exact.add(item)
    h = int(hashlib.md5(item.encode()).hexdigest(), 16)
    # считаем ведущие нули в бинарном представлении
    binary = bin(h)[2:].zfill(128)
    zeros = len(binary) - len(binary.lstrip('0'))
    max_zeros[0] = max(max_zeros[0], zeros)

# Добавляем 1000 уникальных и 1000 дубликатов
for i in range(1000):
    add(f"user-{i}")
for i in range(1000):
    add(f"user-{i}")   # дубликаты не меняют оценку уникальных

estimate = 2 ** max_zeros[0]   # грубая оценка по идее HLL
print("Точное число уникальных (Set):", len(exact))
print("Приближённая оценка (идея HLL):", estimate)
print(f"Память Set: хранит {len(exact)} элементов")
print("Память HLL: фиксированная (~12 КБ), не зависит от числа элементов")
print("\nРеальный HyperLogLog даёт ~0.81% погрешности, а не такую грубую.")

Точное множество растёт с числом элементов, а вероятностная оценка занимает фиксированную память. В этом весь смысл HyperLogLog для гигантских объёмов.

Как работает под капотом

HyperLogLog основан на наблюдении: в случайных хешах вероятность встретить хеш с N ведущими нулями связана с числом уникальных элементов. Усредняя такие наблюдения по множеству «регистров», HLL оценивает кардинальность с фиксированной памятью. Bitmap — это просто строка, где команды SETBIT/BITCOUNT работают на уровне отдельных битов. Bloom-фильтр из RedisBloom использует несколько хеш-функций и битовый массив, давая «точно нет» или «возможно есть».

Частые ошибки

  • Ждать точности от HyperLogLog. Это оценка с погрешностью; для точного счёта нужен Set.
  • Bitmap по разреженным ID. Если ID огромные и редкие, bitmap раздуется; тогда лучше Set или HLL.
  • Считать модули доступными везде. RedisJSON/Search есть в Redis Stack/новом ядре, но не в любой старой сборке.

Best practices

  • Для подсчёта уникальных при огромных объёмах — HyperLogLog (память фиксирована).
  • Для миллионов булевых флагов — Bitmaps (1 бит на флаг).
  • Для документов, поиска и вероятностных проверок — модули Redis Stack.

Итог: HyperLogLog считает уникальные за ~12 КБ ценой 0.81% погрешности. Bitmaps хранят миллионы флагов в килобайтах. Модули Redis Stack (JSON, Search, Bloom, TimeSeries) расширяют Redis до документной БД, поисковика и хранилища временных рядов.

Проверьте себя
1. Когда стоит выбрать HyperLogLog вместо обычного множества (Set)?
AКогда нужен точный список всех элементов
BКогда нужно приблизительно посчитать число уникальных элементов при огромных объёмах, экономя память (фиксированные ~12 КБ)
CКогда элементов меньше десяти
DКогда важен порядок элементов
2. Какой модуль Redis Stack добавляет полнотекстовый и векторный поиск?
ARedisJSON
BRediSearch
CRedisBloom
DRedisTimeSeries