रेडिस स्ट्रिंग्स बनाम रेडिस हैश JSON का प्रतिनिधित्व करने के लिए: दक्षता?


287

मैं एक JSON पेलोड को रेडिस में स्टोर करना चाहता हूं। वहाँ वास्तव में 2 तरीके मैं यह कर सकता है:

  1. एक साधारण स्ट्रिंग कुंजियों और मूल्यों का उपयोग करना।
    कुंजी: उपयोगकर्ता, मूल्य: पेलोड (संपूर्ण JSON बूँद जो 100-200 KB हो सकती है)

    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 यहां भी मदद कर सकता है: redislabs.com/blog/redis-as-a-json-store
बी।

2
किसी ने यहाँ ReJSON का उपयोग किया?
स्वामी

जवाबों:


168

यह इस बात पर निर्भर करता है कि आप डेटा का उपयोग कैसे करते हैं:

विकल्प 1 के लिए जाएं:

  • यदि आप अपने अधिकांश एक्सेस पर अधिकांश फ़ील्ड का उपयोग करते हैं।
  • यदि संभव कुंजियों पर विचरण होता है

विकल्प 2 के लिए जाएं:

  • यदि आप अपनी पहुंच के अधिकांश क्षेत्रों में सिर्फ एकल फ़ील्ड का उपयोग करते हैं।
  • यदि आप हमेशा जानते हैं कि कौन से क्षेत्र उपलब्ध हैं

पुनश्च: अंगूठे के एक नियम के रूप में, उस विकल्प के लिए जाएं जिसमें आपके अधिकांश उपयोग मामलों पर कम प्रश्नों की आवश्यकता होती है।


28
विकल्प 1 अच्छा विचार नहीं है यदि पेलोड के समवर्ती संशोधन की JSONउम्मीद है ( गैर-परमाणु की एक क्लासिक समस्या read-modify-write)।
सामवेन

1
जोसन स्ट्रिंग के रूप में या रेडिस में बाइट ऐरे के रूप में स्टोर करने वाले उपलब्ध विकल्पों में से कौन अधिक कुशल है?
विनीत 89

422

यह लेख यहां बहुत अंतर्दृष्टि प्रदान कर सकता है: http://redis.io/topics/memory-optimization

रेडिस में वस्तुओं की एक सरणी को स्टोर करने के कई तरीके हैं ( स्पॉइलर : मुझे सबसे अधिक उपयोग के मामलों के लिए विकल्प 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. Redis हैश में JSON स्ट्रिंग के रूप में प्रत्येक ऑब्जेक्ट को स्टोर करें।

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

    इससे आप थोड़ा सा मजबूत हो सकते हैं और बहुत सारी चाबियों के बजाय केवल दो चाबियों का उपयोग कर सकते हैं। स्पष्ट नुकसान यह है कि आप प्रत्येक उपयोगकर्ता ऑब्जेक्ट पर TTL (और अन्य सामान) सेट नहीं कर सकते हैं, क्योंकि यह रेडिस हैश में एक क्षेत्र है और पूर्ण-विकसित रेडिस कुंजी नहीं है।

    लाभ : JSON पार्सिंग तेज़ है, खासकर जब आपको एक बार में इस ऑब्जेक्ट के लिए कई फ़ील्ड एक्सेस करने की आवश्यकता होती है। मुख्य मुख्य नामस्थान का कम "प्रदूषणकारी"। नुकसान : # 1 के रूप में एक ही मेमोरी उपयोग के बारे में जब आपके पास बहुत सारी वस्तुएँ होती हैं। # 2 से धीमा जब आपको केवल एक ही क्षेत्र तक पहुंचने की आवश्यकता होती है। शायद "अच्छा अभ्यास" नहीं माना जाता है।

  4. प्रत्येक वस्तु की प्रत्येक संपत्ति को समर्पित कुंजी में संग्रहीत करें।

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

    उपरोक्त लेख के अनुसार, यह विकल्प लगभग कभी भी पसंद नहीं किया जाता है (जब तक कि वस्तु की संपत्ति को विशिष्ट टीटीएल या कुछ और होने की आवश्यकता नहीं है )।

    लाभ : ऑब्जेक्ट गुण पूर्ण-विकसित रेडिस कुंजी हैं, जो आपके ऐप के लिए ओवरकिल नहीं हो सकता है। नुकसान : धीमा, अधिक स्मृति का उपयोग करता है, और "सबसे अच्छा अभ्यास" नहीं माना जाता है। मुख्य कुंजी नाम स्थान के प्रदूषण के बहुत सारे।

समग्र सारांश

विकल्प 4 आमतौर पर पसंद नहीं किया जाता है। विकल्प 1 और 2 बहुत समान हैं, और वे दोनों बहुत आम हैं। मैं विकल्प 1 (आम तौर पर बोलना) पसंद करता हूं क्योंकि यह आपको अधिक जटिल वस्तुओं (घोंसले के शिकार की कई परतों के साथ, आदि) को स्टोर करने की अनुमति देता है। विकल्प 3 का उपयोग तब किया जाता है जब आप वास्तव में मुख्य कुंजी नाम स्थान को प्रदूषित नहीं करने के बारे में परवाह करते हैं (अर्थात आप वहां नहीं चाहते हैं आपके डेटाबेस में बहुत सी चाबियां हों और आप टीटीएल, की-वर्डिंग या कुछ भी) जैसी चीजों की परवाह न करें।

अगर मुझे यहाँ कुछ गड़बड़ लगी है, तो कृपया एक टिप्पणी छोड़ने पर विचार करें और मुझे नीचा दिखाने से पहले उत्तर को संशोधित करने की अनुमति दें। धन्यवाद! :)


4
विकल्प # 2 के लिए आप कहते हैं "संभवतः धीमा जब आपको किसी ऑब्जेक्ट में सभी / अधिकांश क्षेत्रों तक पहुंचने की आवश्यकता होती है"। क्या यह परीक्षण किया गया है?
माइकग्रैइलिंग

4
hmget O है (n) n क्षेत्रों के लिए विकल्प 1 के साथ मिलता है फिर भी O (1) होगा। सैद्धांतिक रूप से, हाँ, इसके तेज।
अरुणा हेराथ

4
एक हैश के साथ विकल्प 1 और 2 के संयोजन के बारे में कैसे? अक्सर अद्यतन किए गए डेटा के लिए विकल्प 1 का उपयोग करें और अक्सर अद्यतन किए गए डेटा के लिए विकल्प 2? कहते हैं, हम लेखों को संग्रहीत कर रहे हैं और हम जेन्सन स्ट्रिंग में शीर्षक, लेखक और यूआरएल जैसे फ़ील्ड को एक सामान्य कुंजी के साथ objस्टोर करते हैं और अलग-अलग कुंजी के साथ दृश्य, वोट और मतदाता जैसे फ़ील्ड स्टोर करते हैं? इस तरह एक एकल READ क्वेरी के साथ आपको पूरी वस्तु मिल जाती है और फिर भी अपनी वस्तु के गतिशील भागों को जल्दी से अपडेट कर सकती है? JSON स्ट्रिंग में फ़ील्ड्स के लिए अपेक्षाकृत निराला अद्यतन पूरी वस्तु को लेनदेन में पढ़कर और लिखकर किया जा सकता है।
अरुण

2
इसके अनुसार: ( instagram-engineering.tumblr.com/post/12202313862/… ) इसे मेमोरी खपत के मामले में कई हैश में स्टोर करने की सलाह दी जाती है। इसलिए अरुण के अनुकूलन के बाद, हम कर सकते हैं: 1- कई हैश को जोंस पेलोड को संचयित अद्यतन डेटा के लिए स्ट्रिंग्स के रूप में बनाते हैं, और 2- अक्सर अद्यतन किए गए डेटा के लिए कई फ़ील्ड को
संचयित करते हुए जोंस फ़ील्ड बनाते हैं

2
विकल्प 1 के मामले में, हम इसे एक सेट में क्यों जोड़ रहे हैं? हम केवल कमांड प्राप्त नहीं कर सकते हैं और चेक करें कि क्या शून्य में नहीं लौटा है।
व्यावहारिक

8

उत्तर दिए गए सेट में कुछ जोड़:

सबसे पहले अगर आप रेडिस हैश का कुशलतापूर्वक उपयोग करने जा रहे हैं, तो आपको अधिकतम संख्या और मानों की गणना के लिए अधिकतम आकार जानना आवश्यक है - अन्यथा यदि वे हैश-मैक्स-ज़िपलिस्ट-मूल्य या हैश-मैक्स-ज़िपलिस्ट-एंट्रीज़ को तोड़ते हैं, तो रेडिस इसे व्यावहारिक रूप से बदल देगा। एक हुड के तहत सामान्य कुंजी / मूल्य जोड़े। (हैश-मैक्स-ज़िपलिस्ट-वैल्यू, हैश-मैक्स-ज़िप्लिस्ट-एंट्रीज़ देखें) और हैश विकल्पों में से एक हुड के नीचे टूटना IS READY BAD है, क्योंकि रेडिस के अंदर प्रत्येक सामान्य कुंजी / मान जोड़ी प्रति जोड़ी +90 बाइट्स का उपयोग करती है।

इसका मतलब है कि यदि आप विकल्प दो से शुरू करते हैं और गलती से अधिकतम-हैश-ज़िपलिस्ट-मूल्य से बाहर हो जाते हैं, तो आपको प्रति उपयोगकर्ता मॉडल के अंदर +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 बचाव के लिए यदि आप सभी क्षेत्रों या कई मिल / सेट ऑपरेशन की जरूरत है।

BMiner जवाब के लिए।

तीसरा विकल्प वास्तव में मज़ेदार है, अधिकतम (आईडी) के साथ डेटासेट के लिए <है-मैक्स-ज़िप्लिस्ट-वैल्यू इस समाधान में ओ (एन) जटिलता है, क्योंकि, आश्चर्य की बात है, रेडीस छोटे हैश को लंबाई / कुंजी / मूल्य के सरणी-जैसे कंटेनर के रूप में संग्रहीत करता है वस्तुओं!

लेकिन कई बार हैश में सिर्फ कुछ फ़ील्ड होते हैं। जब हैश छोटे होते हैं, तो हम उन्हें केवल O (N) डेटा संरचना में एन्कोड कर सकते हैं, जैसे कि लंबाई-पूर्वसंशोधित कुंजी मान युग्मों के साथ एक रैखिक सरणी। चूँकि हम ऐसा केवल तभी करते हैं जब N छोटा होता है, HGET और HSET आदेशों के लिए परिशोधन समय अभी भी O (1) है: हैश को वास्तविक हैश तालिका में बदल दिया जाएगा जैसे ही इसमें शामिल तत्वों की संख्या बहुत अधिक बढ़ जाएगी

लेकिन आपको चिंता नहीं करनी चाहिए, आप बहुत तेजी से हैश-मैक्स-ज़िप्लिस्ट-प्रविष्टियाँ तोड़ देंगे और वहाँ आप अब आप वास्तव में समाधान नंबर 1 पर हैं।

दूसरा विकल्प एक हुड के तहत चौथे समाधान के लिए जाने की संभावना है, क्योंकि प्रश्न कहते हैं:

ध्यान रखें कि यदि मैं एक हैश का उपयोग करता हूं, तो मूल्य लंबाई अनुमानित नहीं है। वे सभी छोटे नहीं हैं जैसे कि ऊपर दिए गए जैव उदाहरण।

और जैसा कि आपने पहले ही कहा है: चौथा समाधान प्रत्येक विशेषता के प्रति सुनिश्चित करने के लिए सबसे महंगा +70 बाइट है।

मेरा सुझाव है कि ऐसे डेटासेट का अनुकूलन कैसे करें:

आपको दो विकल्प मिले हैं:

  1. यदि आप पहले समाधान के लिए जाने की तुलना में कुछ उपयोगकर्ता विशेषताओं के अधिकतम आकार की गारंटी नहीं दे सकते हैं और यदि स्मृति मामला रेडिस में स्टोर करने से पहले उपयोगकर्ता के संक्षेपण से महत्वपूर्ण है।

  2. यदि आप सभी विशेषताओं के अधिकतम आकार को बाध्य कर सकते हैं। थान से आप हैश-मैक्स-जिप्लिस्ट-प्रविष्टियाँ / मान सेट कर सकते हैं और हैश का उपयोग प्रति उपयोगकर्ता प्रतिनिधित्व के रूप में या एक रेडिस गाइड के इस विषय से हैश मेमोरी ऑप्टिमाइज़ेशन के रूप में कर सकते हैं: https://redis.io/topics/memory.acimization और उपयोगकर्ता को json string के रूप में स्टोर करें। किसी भी तरह से आप लंबी उपयोगकर्ता विशेषताओं को संपीड़ित कर सकते हैं।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.