Рядки Redis vs Redis мають хеш для представлення JSON: ефективність?


287

Я хочу зберігати корисну навантаження JSON в redis. Дійсно це зробити два способи:

  1. Один за допомогою простих рядкових клавіш та значень
    ключ: користувач, значення: корисне навантаження (весь блок JSON, який може бути 100-200 Кб)

    SET user:1 payload

  2. Використання хешей

    HSET user:1 username "someone"
    HSET user:1 location "NY"
    HSET user:1 bio "STRING WITH OVER 100 lines"

Майте на увазі, що якщо я використовую хеш, довжина значення не передбачувана. Вони не всі короткі, як, наприклад, біодоклад вище.

Яка більш ефективна пам'ять? Використовуючи рядкові клавіші та значення або використовуєте хеш?


37
Також пам’ятайте, що ви не можете (легко) зберігати вкладений об’єкт JSON у хеш-наборі.
Джонатан Хедборг

3
Тут також може допомогти ReJSON
Cihan B.

2
хтось тут використовував ReJSON?
Swamy

Відповіді:


168

Це залежить від способу доступу до даних:

Перейдіть на варіант 1:

  • Якщо ви використовуєте більшість полів на більшості своїх доступу.
  • Якщо на можливих клавішах є дисперсія

Перейдіть на варіант 2:

  • Якщо ви використовуєте лише поодинокі поля на більшості своїх доступу.
  • Якщо ви завжди знаєте, які поля доступні

PS: Як правило, виберіть варіант, який вимагає меншої кількості запитів у більшості випадків використання.


28
Варіант 1 не є гарною ідеєю , якщо одночасна зміна від JSONкорисного навантаження , як очікується , (класична проблема неатоміческое read-modify-write ).
Самвен

1
Що є більш ефективним серед доступних варіантів зберігання блоку json як рядок json або як байтовий масив у Redis?
Vinit89

422

Ця стаття може запропонувати багато розуміння тут: http://redis.io/topics/memory-optimization

Існує багато способів зберігання масиву об’єктів у Redis ( спойлер : мені подобається варіант 1 для більшості випадків використання):

  1. Зберігайте весь об’єкт як кодований JSON рядок в одній клавіші і відслідковуйте всі об'єкти, використовуючи набір (або список, якщо це більше підходить). Наприклад:

    INCR id:users
    SET user:{id} '{"name":"Fred","age":25}'
    SADD users {id}

    Взагалі, це, мабуть, найкращий метод у більшості випадків. Якщо в Об'єкті багато полів, ваші Об'єкти не вкладені в інші об’єкти, і ви, як правило, отримуєте доступ лише до невеликої підмножини полів одночасно, можливо, краще перейти з варіантом 2.

    Переваги : вважається "хорошою практикою". Кожен об’єкт - це повноцінний ключ Redis. Синтаксичний аналіз JSON проходить швидко, особливо коли вам потрібно отримати доступ до багатьох полів для цього Об'єкта одразу. Недоліки : повільніше, коли вам потрібно отримати доступ лише до одного поля.

  2. Зберігайте властивості кожного об’єкта в хеші Redis.

    INCR id:users
    HMSET user:{id} name "Fred" age 25
    SADD users {id}

    Переваги : вважається "хорошою практикою". Кожен об’єкт - це повноцінний ключ Redis. Не потрібно розбирати рядки JSON. Недоліки : можливо повільніше, коли вам потрібно отримати доступ до всіх / більшості полів об’єкта. Також вкладені Об'єкти (Об'єкти в межах Об'єктів) не можуть бути легко збережені.

  3. Зберігайте кожен об’єкт як рядок JSON у хеші Redis.

    INCR id:users
    HMSET users {id} '{"name":"Fred","age":25}'

    Це дозволяє трохи консолідуватись і використовувати лише дві клавіші замість безлічі клавіш. Очевидним недоліком є ​​те, що ви не можете встановити TTL (та інші речі) для кожного об’єкту користувача, оскільки це просто поле в хеші Redis, а не повноцінний ключ Redis.

    Переваги : синтаксичний аналіз JSON швидкий, особливо коли вам потрібно отримати доступ до багатьох полів для цього Об'єкта одразу. Менше "забруднюють" основний простір імен ключових. Недоліки : приблизно таке ж використання пам’яті, що і №1, коли у вас багато об’єктів. Повільніше, ніж №2, коли вам потрібно лише отримати доступ до одного поля. Напевно, не вважається "хорошою практикою".

  4. Зберігайте кожну властивість кожного Об'єкта у виділеному ключі.

    INCR id:users
    SET user:{id}:name "Fred"
    SET user:{id}:age 25
    SADD users {id}

    Згідно зі статтею вище, ця опція майже ніколи не надається переваги (якщо тільки у властивості Об'єкта не повинно бути конкретного TTL чи чогось іншого).

    Переваги : Властивості об'єкта - це повнорозмірні клавіші Redis, які можуть не бути зайвими для вашої програми. Недоліки : повільно, використовує більше пам’яті і не вважається «найкращою практикою». Багато забруднення основного простору імен ключових.

Загальний підсумок

Варіант 4, як правило, не є кращим. Варіанти 1 і 2 дуже схожі, і обидва вони досить поширені. Я віддаю перевагу варіант 1 (взагалі кажучи), тому що він дозволяє зберігати складніші об'єкти (з декількома шарами вкладення тощо). Варіант 3 використовується, коли вам дуже важливо не забруднювати основний простір імен ключів (тобто ви там не хочете щоб у вашій базі було багато ключів, і вам не байдуже такі речі, як TTL, загострення клавіш тощо.

Якщо я тут щось не так, будь ласка, подумайте про те, щоб залишити коментар і дозволити мені переглянути відповідь перед тим, як відмовитись. Дякую! :)


4
У варіанті 2 ви говорите "можливо повільніше, коли вам потрібно отримати доступ до всіх / більшості полів об'єкта". Це перевірено?
мікегребінг

4
hmget є O (n), для n полів get з варіантом 1 все одно буде O (1). Теоретично, так, це швидше.
Аруна Герат

4
Як щодо поєднання варіантів 1 і 2 з хешем? Використовувати варіант 1 для нечасто оновлюваних даних та варіант 2 для часто оновлюваних даних? Скажімо, ми зберігаємо статті та зберігаємо поля, такі як заголовок, автор та URL у рядку JSON із загальним ключем, як objі зберігаємо поля, як перегляди, голоси та виборці окремими ключами? Таким чином за допомогою одного запиту READ ви отримуєте весь об’єкт і все одно можете швидко оновлювати динамічні частини вашого об’єкта? Відносно рідкісні оновлення полів у рядку JSON можна здійснити, прочитавши та записавши весь об’єкт назад у транзакції.
Аруна

2
Відповідно до цього: ( instagram-engineering.tumblr.com/post/12202313862/… ) рекомендується зберігати в декількох хешах з точки зору споживання пам'яті. Отже після оптимізації arun ми можемо зробити: 1 - зробити кілька хешів, що зберігають json корисне навантаження як рядки для нечасто оновлюваних даних, і 2- зробити кілька хешів, що зберігають поля json для часто оновлюваних даних
Aboelnour

2
У разі option1, чому ми додаємо його до набору? Чому ми не можемо просто скористатися командою Get і перевірити, чи не повертається в нуль.
Прагматичний

8

Деякі доповнення до заданого набору відповідей:

Перш за все, якщо ви збираєтесь ефективно використовувати хеш Redis, ви повинні знати, що ключі підраховують максимальне число та значення max size - інакше, якщо вони вийдуть з хеш-макс-ziplist-значення або хеш-макс-ziplist-записи, Redis перетворить його практично звичайні пари ключів / значень під кришкою. (див. хеш-макс-ziplist-значення, хеш-макс-ziplist-записи) І пробивання під кришкою з параметрів хешу дійсно БАЖЕ, оскільки кожна звичайна пара ключів / значень всередині Redis використовує +90 байт на пару.

Це означає, що якщо ви почнете з другого варіанту і випадково вирветеся з max-hash-ziplist-значення, ви отримаєте +90 байт за ВСЕ АТРИБУТ, у вас є модель користувача! (насправді не +90, але +70 див. консольний вихід нижче)

 # you need me-redis and awesome-print gems to run exact code
 redis = Redis.include(MeRedis).configure( hash_max_ziplist_value: 64, hash_max_ziplist_entries: 512 ).new 
  => #<Redis client v4.0.1 for redis://127.0.0.1:6379/0> 
 > redis.flushdb
  => "OK" 
 > ap redis.info(:memory)
    {
                "used_memory" => "529512",
          **"used_memory_human" => "517.10K"**,
            ....
    }
  => nil 
 # me_set( 't:i' ... ) same as hset( 't:i/512', i % 512 ... )    
 # txt is some english fictionary book around 56K length, 
 # so we just take some random 63-symbols string from it 
 > redis.pipelined{ 10000.times{ |i| redis.me_set( "t:#{i}", txt[rand(50000), 63] ) } }; :done
 => :done 
 > ap redis.info(:memory)
  {
               "used_memory" => "1251944",
         **"used_memory_human" => "1.19M"**, # ~ 72b per key/value
            .....
  }
  > redis.flushdb
  => "OK" 
  # setting **only one value** +1 byte per hash of 512 values equal to set them all +1 byte 
  > redis.pipelined{ 10000.times{ |i| redis.me_set( "t:#{i}", txt[rand(50000), i % 512 == 0 ? 65 : 63] ) } }; :done 
  > ap redis.info(:memory)
   {
               "used_memory" => "1876064",
         "used_memory_human" => "1.79M",   # ~ 134 bytes per pair  
          ....
   }
    redis.pipelined{ 10000.times{ |i| redis.set( "t:#{i}", txt[rand(50000), 65] ) } };
    ap redis.info(:memory)
    {
             "used_memory" => "2262312",
          "used_memory_human" => "2.16M", #~155 byte per pair i.e. +90 bytes    
           ....
    }

Для відповіді TheHippo, коментарі до Варіанту перший вводять в оману:

hgetall / hmset / hmget на допомогу, якщо вам потрібні всі поля або кілька операцій get / set.

Для відповіді BMiner.

Третій варіант насправді цікавий, оскільки для набору даних з max (id) <has-max-ziplist-value це рішення має складність O (N), тому що, дивно, Reddis зберігає невеликі хеші як контейнер, що нагадує масив довжиною / ключем / значенням об’єкти!

Але багато разів хеші містять лише кілька полів. Коли хеші невеликі, ми можемо замість цього просто зашифрувати їх у структурі даних O (N), як лінійний масив із встановленими по довжині парами ключових значень. Оскільки ми робимо це лише тоді, коли N мале, амортизований час для команд HGET і HSET все ще O (1): хеш буде перетворений у справжню хеш-таблицю, як тільки кількість елементів, які він містить, зросте занадто багато

Але ви не повинні турбуватися, ви швидко розібнете записи хеш-макс-ziplist і там ви вже перебуваєте на рівні рішення №1.

Другий варіант, швидше за все, піде на четверте рішення під кришкою, оскільки, як йдеться в питанні:

Майте на увазі, що якщо я використовую хеш, довжина значення не передбачувана. Вони не всі короткі, як, наприклад, біодоклад вище.

І як ви вже говорили: четверте рішення - це найдорожчий +70 байт на кожен атрибут точно.

Моя пропозиція, як оптимізувати такий набір даних:

У вас є два варіанти:

  1. Якщо ви не можете гарантувати максимальний розмір деяких атрибутів користувача, тоді ви перейдете до першого рішення, і якщо значення пам'яті є вирішальним, ніж стиснути json користувача перед зберіганням у redis.

  2. Якщо ви можете застосувати максимальний розмір усіх атрибутів. Тоді ви можете встановити хеш-max-ziplist-записи / значення та використовувати хеші як один хеш на представлення користувача АБО як оптимізацію хеш-пам'яті з цієї теми керівництва Redis: https://redis.io/topics/memory-optimization та зберігати користувача як рядок json У будь-якому випадку ви також можете стискати довгі атрибути користувача.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.