Как лучше всего сжать json для хранения в хранилище памяти, таком как Redis или memcache?

Требование: объекты Python с 2-3 уровнями вложенности, содержащие базовые типы данных, такие как целые числа, строки, списки и словари. (без дат и т. д.), необходимо хранить как json в Redis по ключу. Каковы наилучшие методы сжатия json в виде строки для уменьшения объема памяти. Целевые объекты не очень большие, в среднем содержат 1000 мелких элементов или около 15000 символов при преобразовании в JSON.

eg.

>>> my_dict
{'details': {'1': {'age': 13, 'name': 'dhruv'}, '2': {'age': 15, 'name': 'Matt'}}, 'members': ['1', '2']}
>>> json.dumps(my_dict)
'{"details": {"1": {"age": 13, "name": "dhruv"}, "2": {"age": 15, "name": "Matt"}}, "members": ["1", "2"]}'
### SOME BASIC COMPACTION ###
>>> json.dumps(my_dict, separators=(',',':'))
'{"details":{"1":{"age":13,"name":"dhruv"},"2":{"age":15,"name":"Matt"}},"members":["1","2"]}'

1/Есть ли какие-либо другие лучшие способы сжатия json для экономии памяти в Redis (также обеспечивающие последующее легкое декодирование).

2/ Насколько хорошим кандидатом будет msgpack [http://msgpack.org/]?

3/ Должен ли я также рассмотреть такие варианты, как рассол?


person DhruvPathak    schedule 20.03.2013    source источник
comment
каковы требования вашего приложения? вам нужна производительность? надежность, стабильность и т.д.? Вы бы рассмотрели альтернативы Redis?   -  person drekyn    schedule 20.03.2013


Ответы (6)


Мы просто используем gzip в качестве компрессора.

import gzip
import cStringIO

def decompressStringToFile(value, outputFile):
  """
  decompress the given string value (which must be valid compressed gzip
  data) and write the result in the given open file.
  """
  stream = cStringIO.StringIO(value)
  decompressor = gzip.GzipFile(fileobj=stream, mode='r')
  while True:  # until EOF
    chunk = decompressor.read(8192)
    if not chunk:
      decompressor.close()
      outputFile.close()
      return 
    outputFile.write(chunk)

def compressFileToString(inputFile):
  """
  read the given open file, compress the data and return it as string.
  """
  stream = cStringIO.StringIO()
  compressor = gzip.GzipFile(fileobj=stream, mode='w')
  while True:  # until EOF
    chunk = inputFile.read(8192)
    if not chunk:  # EOF?
      compressor.close()
      return stream.getvalue()
    compressor.write(chunk)

В нашем случае мы сохраняем результат в виде файлов, как вы можете себе представить. Чтобы использовать только строки в памяти, вы также можете использовать объект cStringIO.StringIO() в качестве замены файла.

person Alfe    schedule 20.03.2013
comment
Лучше использовать with gzip.GzipFile(fileobj=stream, mode='w') as compressor:? что в обычной функции open python позволит правильно закрыть файл в случае остановки цикла. - person Tian; 29.05.2020

На основе приведенного выше ответа @Alfe приведена версия, которая сохраняет содержимое в памяти (для задач сетевого ввода-вывода). Я также внес несколько изменений для поддержки Python 3.

import gzip
from io import StringIO, BytesIO

def decompressBytesToString(inputBytes):
  """
  decompress the given byte array (which must be valid 
  compressed gzip data) and return the decoded text (utf-8).
  """
  bio = BytesIO()
  stream = BytesIO(inputBytes)
  decompressor = gzip.GzipFile(fileobj=stream, mode='r')
  while True:  # until EOF
    chunk = decompressor.read(8192)
    if not chunk:
      decompressor.close()
      bio.seek(0)
      return bio.read().decode("utf-8")
    bio.write(chunk)
  return None

def compressStringToBytes(inputString):
  """
  read the given string, encode it in utf-8,
  compress the data and return it as a byte array.
  """
  bio = BytesIO()
  bio.write(inputString.encode("utf-8"))
  bio.seek(0)
  stream = BytesIO()
  compressor = gzip.GzipFile(fileobj=stream, mode='w')
  while True:  # until EOF
    chunk = bio.read(8192)
    if not chunk:  # EOF?
      compressor.close()
      return stream.getvalue()
    compressor.write(chunk)

Чтобы проверить сжатие, попробуйте:

inputString="asdf" * 1000
len(inputString)
len(compressStringToBytes(inputString))
decompressBytesToString(compressStringToBytes(inputString))
person AlaskaJoslin    schedule 26.08.2018

Если вы хотите, чтобы это было быстро, попробуйте lz4. Если вы хотите, чтобы он лучше сжимался, используйте lzma.

Существуют ли какие-либо другие лучшие способы сжатия json для экономии памяти в Redis (также обеспечивающие последующее легкое декодирование)?

Насколько хорошим кандидатом будет msgpack [http://msgpack.org/]?

Msgpack относительно быстр и требует меньше памяти. Но ujson обычно работает быстрее для меня. Вы должны сравнить их на своих данных, измерить скорости сжатия и распаковки и степень сжатия.

Должен ли я рассмотреть такие варианты, как рассол?

Рассмотрим как рассол (в частности, cPickle), так и маршал. Они быстрые. Но помните, что они не являются безопасными или масштабируемыми, и вы платите за скорость дополнительной ответственностью.

person gukoff    schedule 06.07.2017

Я провел обширное сравнение между различными бинарными форматами (MessagePack, BSON, Ion, Smile CBOR) и алгоритмами сжатия (Brotli, Gzip, XZ, Zstandard, bzip2).

Для данных JSON, которые я использовал для тестирования, сохранение данных в формате JSON и использование сжатия Brotli было лучшим решением. Brotli имеет разные уровни сжатия, поэтому, если вы сохраняете данные в течение длительного периода времени, то использование высокого уровня сжатия может оказаться оправданным. Если вы не настаиваете очень долго, то более низкий уровень сжатия или использование Zstandard могут быть наиболее эффективными.

Gzip — это просто, но почти наверняка будут альтернативы, которые либо быстрее, либо предлагают лучшее сжатие, либо и то, и другое.

Вы можете прочитать все подробности нашего расследования здесь: Сообщение в блоге

person Richard Shurtz    schedule 09.12.2019

Одним из простых способов «постобработки» является создание карты «короткого имени ключа» и запуск сгенерированного json через нее перед сохранением и снова (в обратном порядке) перед десериализацией в объект. Например:

Before: {"details":{"1":{"age":13,"name":"dhruv"},"2":{"age":15,"name":"Matt"}},"members":["1","2"]}
Map: details:d, age:a, name:n, members:m
Result: {"d":{"1":{"a":13,"n":"dhruv"},"2":{"a":15,"n":"Matt"}},"m":["1","2"]}

Просто пройдите через json и замените ключ->значение на пути к базе данных и значение->ключ на пути к приложению.

Вы также можете использовать gzip для дополнительного удобства (хотя после этого это не будет строкой).

person Jonatan Hedborg    schedule 21.03.2013

Другая возможность — использовать формат хранения MongoDB, BSON.

Вы можете найти две реализации Python на странице реализации на этом сайте.

edit: почему бы просто не сохранить словарь и преобразовать его в json при извлечении?

person Ivo    schedule 20.03.2013
comment
Я не думаю, что BSON можно добавить в качестве значения ключа в Redis. - person DhruvPathak; 20.03.2013
comment
@DhruvPathak уверен, что может, почему бы и нет? У Redis нет мнения о том, что вы храните в ключе. - person Jonatan Hedborg; 21.03.2013
comment
@JonatanHedborg спасибо за исправление. Я не обратил внимание на то, что строки redis безопасны для двоичного кода. - person DhruvPathak; 21.03.2013
comment
Однако BSON на самом деле не более компактен, чем JSON (как указано на их сайте), поэтому на самом деле это не вариант. - person Jonatan Hedborg; 21.03.2013