#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 мегабайт.
Итак, мой вывод:
Не беспокойтесь о кодировании в типы данных фиксированного размера. Просто сохраните целое число в виде строки, в шестнадцатеричном формате, если хотите. Это не будет иметь большого значения.
Ссылки:
Комментарии:
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, так что все будет работать просто отлично.