Хранение целых чисел в упорядоченном наборе redis?

#redis

#редис

Вопрос:

У меня есть система, которая имеет дело с ключами, которые были превращены в длинные целые числа без знака (путем упаковки коротких последовательностей в байтовые строки). Я хочу попробовать сохранить их в Redis, и я хочу сделать это наилучшим из возможных способов. Меня беспокоит в основном эффективность работы памяти.

Играя с онлайн-репломбом, я замечаю, что два следующих варианта идентичны

 zadd myset 1.0 "123"

zadd myset 1.0 123
 

Это означает, что даже если я знаю, что хочу сохранить целое число, оно должно быть задано как строка. Из документации я заметил, что ключи просто хранятся как char* s и что такие команды, как SETBIT, указывают на то, что Redis не прочь обрабатывать строки как байтовые строки в клиенте. Это намекает на несколько более эффективный способ хранения unsigned long s, чем в виде их строкового представления.

Каков наилучший способ хранения unsigned long s в отсортированных наборах?

Ответ №1:

Спасибо Андре за его ответ. Вот мои выводы.

Хранение целых чисел напрямую

Ключи Redis должны быть строками. Если вы хотите передать целое число, это должна быть какая-то строка. Для небольших, четко определенных наборов значений Redis преобразует строку в целое число, если оно равно единице. Я предполагаю, что он будет использовать этот int для настройки своей хэш-функции (или даже статического измерения хэш-таблицы на основе значения). Это работает для небольших значений (примерами могут служить значения по умолчанию для 64 записей со значением до 512). Я проверю большие значения во время моего расследования.

http://redis.io/topics/memory-optimization

Сохранение в виде строк

Альтернативой является сжатие целого числа, чтобы оно выглядело как строка.

Похоже, что в качестве ключа можно использовать любую байтовую строку.

Для случая моего приложения на самом деле это не имело большого значения для хранения строк или целых чисел. Я полагаю, что структура в Redis в любом случае подвергается какому-то выравниванию, так что в любом случае может быть несколько предварительно потраченных байтов. Значение хэшируется в любом случае.

Используя Python для моего тестирования, я смог создать значения, используя struct.pack . long long s весят 8 байт, что довольно много. Учитывая распределение целых значений, я обнаружил, что на самом деле может быть выгодно хранить строки, особенно если они закодированы в шестнадцатеричном формате.

Поскольку строки redis выполнены в стиле Pascal:

 struct sdshdr {
    long len;
    long free;
    char buf[];
};
 

и учитывая, что мы можем хранить там все, что угодно, я немного дополнил Python, чтобы преобразовать тип в максимально короткий тип:

 def do_pack(prefix, number):
    """
    Pack the number into the best possible string. With a prefix char.
    """ 

    # char
    if number < (1 << 8*1):
        return pack("!cB", prefix, number)

    # ushort
    elif number < (1 << 8*2):
        return pack("!cH", prefix, number)

    # uint
    elif number < (1 << 8*4):
        return pack("!cI", prefix, number)

    # ulonglong
    elif number < (1 << 8*8):
        return pack("!cQ", prefix, number)
 

Похоже, что это приводит к незначительной экономии (или вообще никакой). Вероятно, из-за заполнения структуры в Redis. Это также перегружает процессор Python, делая его несколько непривлекательным.

Данные, с которыми я работал, составляли 200000 zsets consecutive integer => (weight, random integer) × 100 , плюс некоторый инвертированный индекс (на основе случайных данных). dbsize выдает 1 200 001 ключ.

Конечное использование памяти сервером: 1,28 ГБ ОЗУ, 1,32 виртуальной. Различные настройки в любом случае приводили к разнице не более чем в 10 мегабайт.

Итак, мой вывод:

Не беспокойтесь о кодировании в типы данных фиксированного размера. Просто сохраните целое число в виде строки, в шестнадцатеричном формате, если хотите. Это не будет иметь большого значения.

Ссылки:

http://docs.python.org/library/struct.html

http://redis.io/topics/internals-sds

Комментарии:

1. Я пробовал zadd 1000000 раз в строке, 1000000 раз в целых числах. Использование памяти было почти одинаковым, даже иногда при хранении в integer использовалось больше памяти.

Ответ №2:

Я не уверен в этом ответе, это скорее предложение, чем что-либо еще. Я должен был бы попробовать и посмотреть, работает ли это.

Насколько я могу судить, Redis поддерживает только строки UTF-8.

Я бы предложил взять битовое представление вашего длинного целого числа и заполнить его соответствующим образом, чтобы заполнить ближайший байт. Закодируйте каждый набор из 8 байтов в строку UTF-8 (заканчивающуюся строкой 8x * utf8_char *) и сохраните ее в Redis. Тот факт, что они без знака, означает, что вам не важен этот первый бит, но если бы вы это сделали, вы могли бы добавить флаг в строку.

После извлечения данных вы должны не забыть снова заполнить каждый символ 8 байтами, поскольку UTF-8 будет использовать меньше байтов для представления, если символ может быть сохранен с меньшим количеством байтов.

Конечным результатом является то, что вы сохраняете максимум 8 символов размером 8 байт вместо (возможно) максимум 64 символов размером 8 байт.

Комментарии:

1. Это хорошее предложение. Но не приведет ли это к появлению непечатаемых символов, возможно, включая кавычки или пробелы? Это может затруднить отправку. У меня будет эксперимент, но я вижу, что это, возможно, тупик.

2. Я полагаю, единственное, что вам действительно нужно было бы избежать, это пробелы, поскольку Redis будет думать (начиная с версии 2.4), что это другой член. Все остальное будет допустимым UTF-8, так что все будет работать просто отлично.