สตริง Redis เทียบกับ Redis hash เพื่อเป็นตัวแทนของ JSON: มีประสิทธิภาพหรือไม่


287

ฉันต้องการเก็บ JSON ส่วนของข้อมูลลงใน Redis มีสองวิธีที่ฉันสามารถทำได้:

  1. หนึ่งใช้คีย์สตริงและค่าง่าย ๆ
    สำคัญ: ผู้ใช้ค่า: น้ำหนักบรรทุก (ทั้ง JSON blob ซึ่งสามารถเป็น 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 ที่ซ้อนกันในชุดแฮชได้อย่างง่ายดาย
Jonatan Hedborg

3
ReJSON สามารถช่วยได้ที่นี่เช่นกัน: redislabs.com/blog/redis-as-a-json-store
Cihan B.

2
มีคนใช้ ReJSON ที่นี่บ้างไหม
Swamy

คำตอบ:


168

ขึ้นอยู่กับว่าคุณเข้าถึงข้อมูลอย่างไร:

ไปสำหรับตัวเลือกที่ 1:

  • หากคุณใช้ฟิลด์ส่วนใหญ่ในการเข้าถึงส่วนใหญ่ของคุณ
  • หากมีความแปรปรวนของปุ่มที่เป็นไปได้

ไปสำหรับตัวเลือก 2:

  • หากคุณใช้เพียงฟิลด์เดียวในการเข้าถึงส่วนใหญ่ของคุณ
  • หากคุณรู้อยู่เสมอว่ามีฟิลด์ใดบ้าง

ป.ล. : ตามกฎง่ายๆให้ไปที่ตัวเลือกที่ต้องใช้แบบสอบถามน้อยลงในกรณีการใช้งานส่วนใหญ่ของคุณ


28
ตัวเลือกที่ 1 ไม่ได้เป็นความคิดที่ดีถ้าปรับเปลี่ยนพร้อมกันของJSONน้ำหนักบรรทุกที่คาดว่า (เป็นปัญหาคลาสสิกของที่ไม่ใช่อะตอม read-modify-write )
Samveen

1
ตัวเลือกใดที่มีประสิทธิภาพมากกว่าสำหรับการจัดเก็บ json blob เป็นสตริง 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 คุณพูดว่า "อาจช้าลงเมื่อคุณต้องการเข้าถึงฟิลด์ทั้งหมด / เกือบทั้งหมดในวัตถุ" สิ่งนี้ได้รับการทดสอบแล้วหรือยัง?
mikegreiling

4
hmgetคือ O (n) สำหรับฟิลด์n รับด้วยตัวเลือก 1 จะยังคงเป็น O (1) ในทางทฤษฎีใช่มันเร็วขึ้น
Aruna Herath

4
วิธีการเกี่ยวกับการรวมตัวเลือกที่ 1 และ 2 กับแฮช? ใช้ตัวเลือกที่ 1 สำหรับข้อมูลที่อัปเดตไม่บ่อยและตัวเลือก 2 สำหรับข้อมูลที่อัปเดตบ่อยครั้งหรือไม่ สมมติว่าเรากำลังจัดเก็บบทความและเราจัดเก็บฟิลด์เช่นชื่อเรื่องผู้แต่งและ URL ในสตริง JSON ที่มีคีย์ทั่วไปเช่นobjและจัดเก็บฟิลด์เช่นมุมมองคะแนนและผู้ลงคะแนนด้วยปุ่มแยกกันหรือไม่ วิธีนี้ด้วยแบบสอบถาม READ เดียวที่คุณได้รับทั้งวัตถุและยังคงสามารถปรับปรุงส่วนไดนามิกของวัตถุของคุณได้อย่างรวดเร็ว? การอัพเดตที่ไม่บ่อยครั้งในฟิลด์ในสตริง JSON สามารถทำได้โดยการอ่านและเขียนวัตถุทั้งหมดกลับไปในการทำธุรกรรม
อรุณ

2
ตามนี้: ( instagram-engineering.tumblr.com/post/12202313862/ ...... ) ขอแนะนำให้จัดเก็บในหลายแฮชในแง่ของการใช้หน่วยความจำ ดังนั้นหลังจากการเพิ่มประสิทธิภาพอรุณเราสามารถทำได้: 1- ทำให้หลาย hashes เก็บน้ำหนักบรรทุก JSON เป็นสตริงสำหรับปรับปรุงข้อมูลไม่บ่อยและ 2- ทำให้ hashes หลายสาขาจัดเก็บ JSON สำหรับปรับปรุงข้อมูลบ่อย
Aboelnour

2
ในกรณีของตัวเลือก 1 ทำไมเราเพิ่มเข้าไปในชุด? ทำไมเราไม่สามารถใช้คำสั่ง Get และตรวจสอบว่าการส่งคืนไม่ใช่ไม่มีหรือไม่
ทางปฏิบัติ

8

เพิ่มเติมบางส่วนของชุดคำตอบที่ให้มา:

ก่อนอื่นถ้าคุณจะใช้ Redis hash อย่างมีประสิทธิภาพคุณต้องรู้ว่ามีปุ่มนับจำนวนสูงสุดและขนาดสูงสุด - มิฉะนั้นถ้ามันแบ่งออก hash-max-ziplist-value หรือ hash-max-ziplist-Redis จะแปลงเป็นจริง คู่ของคีย์ / ค่าปกติภายใต้ประทุน (ดู hash-max-ziplist-value, hash-max-ziplist-entry) และการแตกภายใต้ฮู้ดจากตัวเลือกแฮชคือ BAD จริงๆเพราะแต่ละคู่คีย์ / ค่าปกติภายใน Redis ใช้ +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 เพื่อช่วยเหลือหากคุณต้องการฟิลด์ทั้งหมดหรือการดำเนินการ get / set หลายรายการ

สำหรับคำตอบ BMiner

ตัวเลือกที่สามคือความสนุกจริงๆสำหรับชุดข้อมูลที่มี max (id) <has-max-ziplist-value โซลูชันนี้มีความซับซ้อน O (N) เนื่องจากแปลกใจ Reddis เก็บแฮชขนาดเล็กเป็นคอนเทนเนอร์เหมือนอาร์เรย์ที่มีความยาว / คีย์ / ค่า วัตถุ!

แต่แฮชหลายครั้งมีเพียงไม่กี่ฟิลด์ เมื่อแฮชมีขนาดเล็กเราสามารถเข้ารหัสได้ในโครงสร้างข้อมูล O (N) แทนอาเรย์เชิงเส้นที่มีคู่ของคีย์ค่าที่มีคำนำหน้ายาว เนื่องจากเราทำสิ่งนี้เฉพาะเมื่อ N มีขนาดเล็กเวลาที่ค่าตัดจำหน่ายสำหรับคำสั่ง HGET และ HSET ยังคงเป็น O (1): แฮชจะถูกแปลงเป็นตารางแฮชจริงทันทีที่จำนวนองค์ประกอบที่มีจะเติบโตมากเกินไป

แต่คุณไม่ควรกังวลคุณจะแฮ็ก - แม็ก - ซิสทิสต์ - รายการอย่างรวดเร็วและไปที่นั่นตอนนี้คุณอยู่ที่หมายเลข 1

ตัวเลือกที่สองน่าจะเป็นทางออกที่สี่ภายใต้ประทุนเนื่องจากเป็นคำถามที่ระบุไว้:

โปรดทราบว่าหากฉันใช้แฮชความยาวค่าจะไม่สามารถคาดเดาได้ มันไม่ได้สั้นทั้งหมดเช่นตัวอย่างชีวภาพข้างต้น

และอย่างที่คุณพูดไปแล้ว: วิธีที่สี่คือ +70 ไบต์ที่แพงที่สุดสำหรับแต่ละคุณสมบัติ

คำแนะนำของฉันวิธีเพิ่มประสิทธิภาพชุดข้อมูลดังกล่าว:

คุณมีสองตัวเลือก:

  1. หากคุณไม่สามารถรับประกันขนาดสูงสุดของแอตทริบิวต์ผู้ใช้บางอย่างได้มากกว่าที่จะใช้วิธีแก้ไขปัญหาแรกและหากหน่วยความจำมีความสำคัญกว่าการบีบอัดผู้ใช้ json ก่อนเก็บใน redis

  2. หากคุณสามารถบังคับขนาดสูงสุดของคุณลักษณะทั้งหมด กว่าที่คุณสามารถตั้งค่า hash-max-ziplist-entry / value และใช้ hash เป็นหนึ่ง hash ต่อการแสดงของผู้ใช้หรือเป็นการเพิ่มประสิทธิภาพหน่วยความจำ hash จากหัวข้อของคู่มือ Redis นี้: https://redis.io/topics/memory-optimizationและ เก็บผู้ใช้เป็นสตริง json ด้วยวิธีใดวิธีหนึ่งคุณอาจบีบอัดแอตทริบิวต์ผู้ใช้ที่ยาว

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.