การ model Object ด้วย Hashes
หนึ่ง hash ต่อหนึ่ง object — รูปแบบมาตรฐาน
หัวข้อที่มีชื่อว่า “หนึ่ง hash ต่อหนึ่ง object — รูปแบบมาตรฐาน”วิธีที่ถูกต้องในการ model domain object ใน Redis คือการเก็บทุกฟิลด์ไว้ภายใต้ hash key เดียว ตัวอย่างเช่น ข้อมูลผู้ใช้จะอยู่ทั้งหมดภายใต้ user:1:
127.0.0.1:6379> HSET user:1 name "Ada" email "[email protected]" age "28" points "0"(integer) 4127.0.0.1:6379> HGETALL user:11) "name"2) "Ada"3) "email"4) "[email protected]"5) "age"6) "28"7) "points"8) "0"เปรียบเทียบกับแนวทางการใช้หลาย key:
| แนวทาง | จำนวน key ที่ต้องใช้ |
|---|---|
| Hash | user:1 — หนึ่ง key, หนึ่ง HSET |
| String แยก | user:1:name, user:1:email, user:1:age, user:1:points — สี่ key |
แนวทาง hash ใช้หน่วยความจำน้อยกว่า และช่วยให้คุณดึงหรืออัปเดตฟิลด์เฉพาะได้ด้วย HGET / HSET โดยไม่ต้องโหลดหรือแตะฟิลด์อื่นในออบเจกต์
HSET user:1 name "Ada" email "[email protected]" age "28" points "0"
HGETALL user:1HINCRBY และ HINCRBYFLOAT
หัวข้อที่มีชื่อว่า “HINCRBY และ HINCRBYFLOAT”เมื่อฟิลด์ใน hash เก็บค่าตัวเลข Redis สามารถเพิ่มค่าได้แบบ atomic — ไม่มีปัญหา race condition จาก read-modify-write:
HINCRBY key field increment— เพิ่มค่าฟิลด์ integer ด้วยจำนวนเต็มHINCRBYFLOAT key field increment— เพิ่มค่าฟิลด์ float ด้วยตัวเลขทศนิยม
127.0.0.1:6379> HINCRBY user:1 points 10(integer) 10127.0.0.1:6379> HINCRBY user:1 age 1(integer) 29127.0.0.1:6379> HINCRBYFLOAT user:1 score 1.5"1.5"127.0.0.1:6379> HINCRBYFLOAT user:1 score 0.5"2"127.0.0.1:6379> HGET user:1 points"10"หากฟิลด์ยังไม่มีอยู่ ทั้งสองคำสั่งจะถือว่าค่าเริ่มต้นคือ 0 ก่อนจะนำค่า increment ไปบวก ดังนั้น HINCRBY user:1 points 5 บนฟิลด์ points ที่ไม่มีอยู่จะสร้างฟิลด์ใหม่ด้วยค่า 5
HSET user:1 name "Ada" email "[email protected]" age "28" points "0"
HINCRBY user:1 points 10
HINCRBY user:1 age 1
HINCRBYFLOAT user:1 score 1.5
HINCRBYFLOAT user:1 score 0.5
HGET user:1 pointsHSETNX — การตั้งค่าฟิลด์แบบมีเงื่อนไข
หัวข้อที่มีชื่อว่า “HSETNX — การตั้งค่าฟิลด์แบบมีเงื่อนไข”HSETNX key field value ตั้งค่าฟิลด์ เฉพาะเมื่อฟิลด์นั้นยังไม่มีอยู่เท่านั้น คำสั่งจะคืนค่า 1 ถ้าสร้างฟิลด์สำเร็จ หรือ 0 ถ้าฟิลด์มีอยู่แล้วและไม่ได้เปลี่ยนแปลง
127.0.0.1:6379> HSETNX user:1 email "[email protected]"(integer) 0127.0.0.1:6379> HGET user:1 email127.0.0.1:6379> HSETNX user:1 referrer "bob"(integer) 1127.0.0.1:6379> HGET user:1 referrer"bob"กรณีใช้งานจริง: บันทึกฟิลด์ created_at เพียงครั้งเดียวเมื่อสร้าง object ครั้งแรก เนื่องจาก HSETNX ทำงานแบบ atomic จึงมั่นใจได้ว่าถ้ามี writer สองตัวแข่งกัน จะมีเพียงตัวแรกเท่านั้นที่ได้รับ 1
HSET user:1 name "Ada" email "[email protected]"
HSETNX user:1 email "[email protected]"
HGET user:1 email
HSETNX user:1 referrer "bob"
HGET user:1 referrerHash vs หลาย string key — เลือกใช้เมื่อไหร่
หัวข้อที่มีชื่อว่า “Hash vs หลาย string key — เลือกใช้เมื่อไหร่”| Hash (หนึ่ง key) | หลาย string key | |
|---|---|---|
| หน่วยความจำ | กะทัดรัด (listpack สำหรับ hash ขนาดเล็ก) | overhead ต่อ key สูงกว่า |
| ดึงหนึ่งฟิลด์ | HGET user:1 name | GET user:1:name |
| ดึงทุกฟิลด์ | HGETALL user:1 | MGET user:1:name user:1:email ... |
| เพิ่มค่าแบบ atomic | HINCRBY | INCR user:1:points |
| TTL ต่อฟิลด์ | ไม่สามารถทำได้ | ได้ (แต่ละ key มี TTL ของตัวเอง) |
กฎทั่วไป: เลือกใช้ hash หนึ่งอันต่อหนึ่ง object เสมอ ใช้ string key แยกเฉพาะเมื่อต้องการกำหนด TTL แยกต่างหากในแต่ละฟิลด์
ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| ตัวเลือก | Benefit | Cost |
|---|---|---|
| Hash เดียวต่อหนึ่ง object | หน่วยความจำน้อยกว่า อัปเดตทีละ field ได้ด้วย HGET/HSET โดยไม่แตะ field อื่น | ตั้ง TTL แยกต่อ field ไม่ได้ ต้องหมดอายุทั้ง hash พร้อมกัน |
HINCRBY/HINCRBYFLOAT สำหรับ field ตัวเลข | เพิ่มค่าแบบ atomic ไม่มี race condition จาก read-modify-write | ใช้ได้เฉพาะ field ที่เป็นตัวเลขเท่านั้น field อื่นต้องใช้ HSET ตามปกติ |
HSETNX สำหรับ field แบบมีเงื่อนไข | รับประกันว่ามีเพียง writer ตัวแรกเท่านั้นที่ set ค่าได้ เหมาะกับ created_at | ไม่สามารถอัปเดตค่าที่มีอยู่แล้วได้ ต้องใช้ HSET ธรรมดาถ้าต้องการ overwrite |
ข้อผิดพลาดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ข้อผิดพลาดที่พบบ่อย”- แยก field ของ object เดียวออกเป็นหลาย string key เช่น
user:1:name,user:1:email— เปลืองหน่วยความจำมากกว่า ควรรวมเป็น hash เดียวเสมอ ยกเว้นต้องการ TTL แยกต่อ field จริง ๆ - ใช้
HSETธรรมดาแทนHINCRBYเมื่ออัปเดต field ตัวเลข — ต้องHGETค่าก่อนแล้วค่อยHSETกลับ เปิดช่องให้เกิด race condition เมื่อมี writer พร้อมกันหลายตัว - ลืมว่า
HSETNXไม่ overwrite ค่าที่มีอยู่แล้ว — เข้าใจผิดว่าเป็น “set หรือ update” ทั้งที่จริงHSETNXทำงานเฉพาะตอนที่ field ยังไม่มีอยู่เท่านั้น
💡 ตัวอย่างจากของจริง
GitHub — เก็บ metadata ของ object อย่าง repository stats เป็น hash เดียวต่อหนึ่ง object ใช้
HINCRBYอัปเดตตัวนับ เช่น star count แบบ atomic โดยไม่ต้องล็อกฝั่ง applicationInstagram — ใช้ hash เก็บ field ของ media object (like count, view count) รวมกันภายใต้ key เดียว ลด round-trip เมื่อต้องอ่านหลาย field พร้อมกันสำหรับหน้า feed