JSONを表すためのRedis文字列vs Redisハッシュ:効率?


287

JSONペイロードをredisに保存したい。これを行うには、本当に2つの方法があります。

  1. 単純な文字列のキーと値を使用するもの。
    key:user、value:payload(100-200 KBになる可能性があるJSONブロブ全体)

    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オブジェクトをハッシュセットに(簡単に)格納できないことに注意してください。
ジョナタンヘドボルグ2013

3
ReJSONもここで役立ちます:redislabs.com/blog/redis-as-a-json-store
Cihan B.

2
誰かがここでReJSONを使用しましたか?
スワミー2017年

回答:


168

データへのアクセス方法によって異なります。

オプション1に進みます。

  • ほとんどのアクセスでほとんどのフィールドを使用する場合。
  • 可能なキーに差異がある場合

オプション2に進みます。

  • ほとんどのアクセスで単一のフィールドのみを使用する場合。
  • 利用可能なフィールドが常にわかっている場合

PS:経験則として、ほとんどのユースケースで必要なクエリが少ないオプションを選択してください。


28
場合はオプション1は良いアイデアではありません同時変更JSONペイロードが期待されている(の古典的な問題非アトミック read-modify-write)。
Samveen

1
json blobをjson文字列として、またはRedisのバイト配列として保存するための利用可能なオプションの中で、どちらがより効率的ですか?
Vinit89 2018

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}'
    

    これにより、ビットを統合して、多数のキーではなく2つのキーのみを使用することができます。明らかな欠点は、TTL(およびその他のもの)をRedisハッシュ内の単なるフィールドであり、本格的なRedisキーではないため、各ユーザーオブジェクトにTTL(およびその他のもの)を設定できないことです。

    利点:JSON解析は高速です。特に、このオブジェクトの多くのフィールドに一度にアクセスする必要がある場合は特にそうです。メインのキー名前空間の「汚染」が少ない。 短所:多数のオブジェクトがある場合の#1とほぼ同じメモリ使用量。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の場合、「オブジェクト内のすべてのフィールドまたはほとんどのフィールドにアクセスする必要がある場合は、速度が遅くなる可能性があります」と言います。これはテストされましたか?
mikegreiling 2014年

4
オプション1 でn個のフィールドを取得する場合、hmgetはO(n)ですが、O(1)のままです。理論的には、はい、より高速です。
Aruna Herath、2015

4
オプション1と2をハッシュと組み合わせてみませんか?頻繁に更新されないデータにはオプション1を使用し、頻繁に更新されるデータにはオプション2を使用しますか?たとえば、記事を保存し、タイトル、作成者、URLなどのフィールドを、JSON文字列のような一般的なキーobjで保存し、ビュー、投票、有権者などのフィールドを別々のキーで保存しますか?このように、単一のREADクエリでオブジェクト全体を取得し、オブジェクトの動的な部分をすばやく更新できますか?JSON文字列のフィールドに対する比較的まれな更新は、オブジェクト全体をトランザクションで読み書きすることによって実行できます。
2015年

2
これによると:(instagram-engineering.tumblr.com/post/12202313862/…)メモリ消費の観点から、複数のハッシュに格納することをお勧めします。したがって、arunの最適化後に、次のことを実行できます
。1-

2
option1の場合、なぜそれをセットに追加するのですか?単にGetコマンドを使用して、nil以外に戻るかどうかを確認できないのはなぜですか。
実用的な

8

与えられた答えのセットへのいくつかの追加:

まず第一に、Redisハッシュを効率的に使用する場合、キーカウントの最大数と値の最大サイズを知っておく必要があります。それ以外の場合は、hash-max-ziplist-valueまたはhash-max-ziplist-entriesが発生した場合、Redisは実際に変換します。内部での通常のキーと値のペア。(hash-max-ziplist-value、hash-max-ziplist-entriesを参照)そして、Redis内の通常の各キー/値のペアはペアあたり+90バイトを使用するため、ハッシュオプションから内部を壊すことは本当に悪いです。

つまり、オプション2から始めて、誤ってmax-hash-ziplist-valueから抜け出した場合、ユーザーモデル内にある各属性ごとに+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の回答の場合、オプション1に関するコメントは誤解を招くものです。

すべてのフィールドまたは複数のget / set操作が必要な場合は、hgetall / hmset / hmgetを使用してください。

BMinerの回答について。

3番目のオプションは実際には本当に楽しいです。max(id)<has-max-ziplist-valueのデータセットの場合、このソリューションはO(N)の複雑さを持っています。これは、Reddisが長さ/キー/値の配列のようなコンテナーとして小さなハッシュを格納するためですオブジェクト!

しかし、多くの場合、ハッシュにはいくつかのフィールドしか含まれていません。ハッシュが小さい場合は、代わりに、長さのプレフィックスが付いたキーと値のペアを持つ線形配列のように、O(N)データ構造にエンコードするだけです。Nが小さい場合にのみこれを行うため、HGETコマンドとHSETコマンドの償却時間はO(1)のままです。ハッシュは、含まれる要素の数が増えすぎるとすぐに実際のハッシュテーブルに変換されます。

しかし、心配する必要はありません。hash-max-ziplist-entriesを非常に速く壊し、実際にはソリューション番号1になります。

2番目のオプションは、フードとして4番目のソリューションに進む可能性が高いです。

ハッシュを使用する場合、値の長さは予測できないことに注意してください。上記のバイオの例のように、すべてが短いわけではありません。

そして、すでに述べたように、4番目のソリューションは、確かに各属性ごとに最も高価な+70バイトです。

そのようなデータセットを最適化する方法についての私の提案:

次の2つのオプションがあります。

  1. 一部のユーザー属性の最大サイズを最初の解決策よりも保証できない場合、およびメモリの問題が重要な場合は、redisに格納する前にユーザーjsonを圧縮します。

  2. すべての属性の最大サイズを強制できる場合。hash-max-ziplist-entries / valueを設定して、ユーザー表現ごとに1つのハッシュとして、またはRedisガイドのこのトピックのハッシュメモリ最適化としてハッシュを使用できます:https : //redis.io/topics/memory-optimization andユーザーをjson文字列として保存します。どちらの方法でも、長いユーザー属性を圧縮できます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.