Redis, JSON'u temsil etmek için Redis karmalarını vs: verimlilik?


287

Bir JSON yükünü yeniden kayıtlara kaydetmek istiyorum. Bunu yapmanın 2 yolu var:

  1. Biri basit bir dize anahtarları ve değerleri kullanıyor.
    anahtar: kullanıcı, değer: faydalı yük (100-200 KB olabilecek tüm JSON blob'u)

    SET user:1 payload

  2. Karma kullanma

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

Bir karma kullanırsam değer uzunluğunun tahmin edilemeyeceğini unutmayın. Bunların hepsi yukarıdaki biyo örneği gibi kısa değil.

Hangisi daha verimli bellek? Dize anahtarları ve değerleri mi kullanıyorsunuz yoksa karma mı kullanıyorsunuz?


37
Ayrıca, iç içe geçmiş bir JSON nesnesini karma kümesinde (kolayca) depolayamayacağınızı da unutmayın.
Jonatan Hedborg

3
ReJSON burada da yardımcı olabilir: redislabs.com/blog/redis-as-a-json-store
Cihan B.

2
burada birisi ReJSON'u kullandı mı?
Swamy

Yanıtlar:


168

Verilere nasıl eriştiğinize bağlıdır:

Seçenek 1'e gidin:

  • Erişimlerinizin çoğunda alanların çoğunu kullanırsanız.
  • Olası tuşlarda sapma varsa

Seçenek 2'ye gidin:

  • Erişimlerinizin çoğunda yalnızca tek alanlar kullanırsanız.
  • Hangi alanların mevcut olduğunu her zaman biliyorsanız

Not: Başparmak kuralı olarak, kullanım durumlarınızın çoğunda daha az sorgu gerektiren seçeneği tercih edin.


28
1. Seçenek eğer iyi bir fikir değildir eşzamanlı modifikasyon ait JSONyük bekleniyor (klasik sorunu olmayan atom read-modify-write ).
Samveen

1
Json blobunu bir json dizesi veya Redis'te bir bayt dizisi olarak saklamak için mevcut seçenekler arasında hangisi daha etkilidir?
Vinit89

422

Bu makale burada bir çok fikir verebilir: http://redis.io/topics/memory-optimization

Redis'te Nesneler dizisini saklamanın birçok yolu vardır ( spoiler : Çoğu kullanım durumunda seçenek 1'i seviyorum):

  1. Tüm nesneyi tek bir anahtarda JSON kodlu dize olarak saklayın ve bir küme (veya daha uygunsa liste) kullanarak tüm Nesneleri takip edin. Örneğin:

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

    Genel olarak, bu muhtemelen çoğu durumda en iyi yöntemdir. Nesnede çok fazla alan varsa, Nesneleriniz diğer Nesnelerle iç içe yerleştirilmez ve bir seferde yalnızca küçük bir alan alt kümesine erişme eğilimindeseniz, seçenek 2'ye gitmek daha iyi olabilir.

    Avantajlar : "iyi uygulama" olarak değerlendirilir. Her Nesne tam gelişmiş bir Redis anahtarıdır. JSON ayrıştırma hızlıdır, özellikle de bu Nesne için birçok alana bir kerede erişmeniz gerektiğinde. Dezavantajları : Yalnızca tek bir alana erişmeniz gerektiğinde daha yavaştır.

  2. Her bir Object'in özelliklerini bir Redis karmasına kaydedin.

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

    Avantajlar : "iyi uygulama" olarak değerlendirilir. Her Nesne tam gelişmiş bir Redis anahtarıdır. JSON dizelerini ayrıştırmaya gerek yok. Dezavantajları : Bir Nesnedeki alanların tümüne / çoğuna erişmeniz gerektiğinde muhtemelen daha yavaştır. Ayrıca, yuvalanmış Nesneler (Nesneler içindeki Nesneler) kolayca saklanamaz.

  3. Her Nesneyi bir Redis karmasında JSON dizesi olarak saklayın.

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

    Bu, biraz birleştirmenize ve çok sayıda anahtar yerine yalnızca iki anahtar kullanmanıza olanak tanır. Açık olan dezavantajı, her bir kullanıcı Nesnesi üzerinde TTL'yi (ve diğer şeyleri) ayarlayamamanızdır, çünkü tam bir Redis anahtarı değil, sadece Redis karma alanındaki bir alandır.

    Avantajları : JSON ayrıştırma hızlıdır, özellikle de bu Nesne için birçok alana bir kerede erişmeniz gerektiğinde. Ana anahtar ad alanının daha az "kirletilmesi". Dezavantajları : Çok fazla Nesneniz olduğunda # 1 ile aynı bellek kullanımı hakkında. Yalnızca tek bir alana erişmeniz gerektiğinde # 2'den yavaş. Muhtemelen "iyi bir uygulama" olarak kabul edilmez.

  4. Her bir Nesnenin her bir özelliğini ayrılmış bir anahtarda saklayın.

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

    Yukarıdaki makaleye göre, bu seçenek neredeyse hiç tercih edilmez (Nesnenin özelliğinin belirli TTL'ye veya başka bir şeye sahip olması gerekmedikçe ).

    Avantajları : Nesne özellikleri, uygulamanız için gereğinden fazla atılmayabilecek tam gelişmiş Redis anahtarlarıdır. Dezavantajları : yavaş, daha fazla bellek kullanır ve "en iyi uygulama" olarak kabul edilmez. Ana anahtar ad alanının kirletici bir sürü.

Genel Özet

Seçenek 4 genellikle tercih edilmez. Seçenek 1 ve 2 çok benzer ve her ikisi de oldukça yaygın. Seçenek 1'i (genel olarak konuşursak) tercih ediyorum çünkü daha karmaşık Nesneleri depolamanıza izin verir (birden fazla yuvalama katmanı vb.) Seçenek 3, ana anahtar ad alanını gerçekten kirletmemeniz gerektiğinde kullanılır (yani orada istemezsiniz veritabanında çok fazla anahtar olması ve TTL, anahtar parçalama veya herhangi bir şey gibi şeyleri umursamıyorsun).

Burada yanlış bir şey varsa, lütfen yorum bırakmayı ve indirmeden önce cevabı revize etmeme izin ver. Teşekkürler! :)


4
Seçenek # 2 için "bir Nesnedeki alanların tümüne / çoğuna erişmeniz gerektiğinde daha yavaş" dersiniz. Bu test edildi mi?
mikegreiling

4
hmget için O (n) 'dir n alanlar olsun hala O (1) olurdu seçeneğiyle 1 ile. Teorik olarak, evet, daha hızlı.
Aruna Herath

4
Seçenek 1 ve 2'yi bir karma ile birleştirmeye ne dersiniz? Nadiren güncellenen veriler için 1. seçeneği ve sık güncellenen veriler için 2. seçeneği kullanın? Diyelim ki makaleleri saklıyoruz ve başlık, yazar ve url gibi alanları JSON dizesinde benzer bir genel anahtarla objve görünümler, oylar ve seçmenler gibi alanları ayrı anahtarlarla mı saklıyoruz? Bu şekilde tek bir OKUYU sorgusu ile nesnenin tamamını alırsınız ve yine de nesnenizin dinamik kısımlarını hızlı bir şekilde güncelleyebilir misiniz? JSON dizesindeki alanlara nispeten nadiren yapılan güncellemeler, nesnenin tamamını bir işleme geri okuyup yazarak yapılabilir.
arun

2
Buna göre: ( instagram-engineering.tumblr.com/post/12202313862/… ) bellek tüketimi açısından birden fazla karma biçiminde saklanması önerilir. Arun'un optimizasyonundan sonra şunları yapabiliriz: 1- json yükünü seyrek güncellenen veriler için dizeler olarak saklayan birden fazla karma yapmak ve 2- sık güncellenen veriler için json alanlarını saklamak için birden fazla karma yapmak
Aboelnour

2
1. seçenek durumunda, bunu neden bir kümeye ekliyoruz? Neden Get komutunu kullanamıyoruz ve dönüşün sıfır olup olmadığını kontrol edemiyoruz.
Pragmatic

8

Belirli bir cevap kümesine bazı eklemeler:

Her şeyden önce, Redis hash'ı verimli bir şekilde kullanacaksanız, bir anahtar sayısı maksimum sayı ve maksimum boyut değerlerini bilmelisiniz - aksi takdirde hash-max-ziplist-değerini veya hash-max-ziplist-girişlerini çıkarırlarsa Redis bunu pratik olarak dönüştürür normal anahtar / değer çiftleri bir başlık altında. (bkz. hash-max-ziplist-value, hash-max-ziplist-girişleri) Ve karma seçeneklerden bir başlık altında kırmak GERÇEKTEN KÖTÜDİR, çünkü Redis içindeki her normal anahtar / değer çifti çift başına +90 bayt kullanır.

Bu, ikinci seçenekle başlarsanız ve yanlışlıkla max-hash-ziplist değerinden koparsanız, her kullanıcı modelinde sahip olduğunuz her bir ATTRIBUTE başına +90 bayt elde edeceğiniz anlamına gelir! (aslında +90 değil + +70 aşağıdaki konsol çıktısına bakın)

 # 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 cevabı için Birinci seçenek hakkındaki yorumlar yanıltıcıdır:

Tüm alanlara veya birden fazla get / set işlemine ihtiyacınız varsa hgetall / hmset / hmget kurtarmaya.

BMiner cevabı için.

Üçüncü seçenek aslında gerçekten eğlencelidir, max (id) <has-max-ziplist-değeri olan veri kümesi için bu çözümün O (N) karmaşıklığı vardır, çünkü sürpriz, Reddis küçük karmaları dizi / anahtar / değer dizisi kabı olarak depolar nesneleri!

Ancak birçok kez karma sadece birkaç alan içerir. Karmalar küçük olduğunda, bunun yerine uzunluk (ön) anahtar değeri çiftlerine sahip doğrusal bir dizi gibi O (N) veri yapısında kodlayabiliriz. Bunu sadece N küçükken yaptığımız için, HGET ve HSET komutları için amortisman süresi hala O (1): içerdiği öğe sayısı çok fazla artar artmaz, gerçek bir karma tablosuna dönüştürülecektir.

Ancak endişelenmemelisiniz, hash-max-ziplist girişlerini çok hızlı kıracaksınız ve işte şimdi 1 numaralı çözümde bulunuyorsunuz.

İkinci seçenek büyük olasılıkla bir başlık altında dördüncü çözüme gidecektir, çünkü soru belirttiği gibi:

Bir karma kullanırsam değer uzunluğunun tahmin edilemeyeceğini unutmayın. Bunların hepsi yukarıdaki biyo örneği gibi kısa değil.

Ve daha önce de söylediğin gibi: dördüncü çözüm, her özellik için en pahalı +70 bayttır.

Benim önerim böyle bir veri kümesini nasıl optimize edeceğim:

İki seçeneğiniz var:

  1. İlk çözüm için kullandığınızdan daha fazla kullanıcı özniteliğinin maksimum boyutunu garanti edemiyorsanız ve bellek sorunu yeniden depolanmadan önce json'u sıkıştırmaktan daha önemliyse.

  2. Tüm niteliklerin maksimum boyutunu zorlayabiliyorsanız. Daha sonra hash-max-ziplist-girdilerini / değerini ayarlayabilir ve karmaları kullanıcı temsili başına bir karma olarak VEYA bir Redis kılavuzunun bu konusundan karma bellek optimizasyonu olarak kullanabilirsiniz: https://redis.io/topics/memory-optimization ve kullanıcıyı json dizesi olarak saklar. Her iki şekilde de uzun kullanıcı özelliklerini sıkıştırabilirsiniz.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.