DevOps The Series | EP.1 Redis eviction policies

รู้จักกับ Eviction policies และตัวอย่าง use-case

Nutchalum Kitpongsai
THiNKNET Engineering
4 min readAug 16, 2018

--

To All Readers

redis นั้นเป็น in-memory data structure store ที่อยู่ในรูปของ key-value
มีความสามารถที่จะเป็นได้ทั้ง cache, database และ message broker

แต่ในบทความนี้จะโฟกัสไปที่การใช้งานเป็น cache กับ database เป็นหลักครับ

What is eviction policies?

ในโลกของ redis นั้นหากเราใช้ทรัพยากรณ์ของพื้นที่ (memory) จนถึงที่สุดแล้ว¹
หากมี key เข้ามาเพิ่มอีกจะทำให้มีพื้นที่ไม่เพียงพอต่อ key ใหม่ที่จะเก็บ

เราสามารถที่จะกำหนดพฤติกรรมของตัว redis ได้ว่าให้แจ้ง error กลับไป หรือพยายามเคลียร์พื้นที่โดยการลบ key ที่มีอยู่ออกไป เพื่อให้มีเพียงพอสำหรับ key ตัวใหม่ที่เข้ามา

และวิธีการตัดสินใจว่าจะลบ key อะไรด้วยเงื่อนไขอะไร ก็มีอยู่หลากหลายรูปแบบ
ซึ่งเราเรียกวิธีต่างๆเหล่านั้นว่า eviction polices นั่นเอง

Eviction policies

ใน redis นั้นมี eviction policies อยู่ด้วยกันทั้งหมด 8 รูปแบบ² ด้วยกันคือ

  • allkeys-lru
  • allkeys-lfu
  • allkeys-random
  • volatile-lru
  • volatile-lfu
  • volatile-random
  • volatile-ttl
  • no eviction

LRU, LFU, Random, TTL, No eviction

  • LRU (Less Recently Used) ลบ key ที่ไม่ถูกใช้งานนานมากที่สุดก่อน
  • LFU³ (Least Frequently Used) ลบ key ที่มีความถี่ในการใช้งานน้อยที่สุดก่อน
  • TTL (Time To Live) ลบ key ที่ใกล้จะหมดอายุที่สุดก่อน
  • Random ลบ key แบบสุ่ม
  • No eviction ไม่มีการลบและเกิด error ให้ client รู้ว่า memory เต็ม

All keys and Volatile

  • All keys คือเลือกลบ key ได้ทุก key โดยไม่มีเงื่อนไข
  • Volatile คือเลือกลบ key เฉพาะ key ที่มีการ set expired time ไว้

Best practices

  1. ถ้าเราต้องการใช้งาน redis เป็น data layer⁴ เราควรกำหนดให้ policy เป็น no eviction เพราะไม่ควรให้มีข้อมูลส่วนใดส่วนหนึ่งหายไป
  2. ถ้าเราต้องการใช้งาน redis เป็น cache layer เราควรคำนึงถึง use-case และ cache และควรมี cache life-cycle⁵ อย่างเช่น expire, evict, หรือ update เป็นต้น
  3. memory size ไม่จำเป็นต้องมีขนาดใหญ่เสมอไป เราอาจใช้ memory ขนาดเล็ก และปรับ eviction policy เพื่อให้เกิด cycle แบบแทนที่กันได้
  4. volatile-* จะลบเฉพาะ key ที่มีการ set expire นั่นแปลว่าทำให้เราสามารถใช้งาน กับ key ที่ไม่มี expire (ไม่ถูกลบ) ร่วมกับ key ที่มี expire (ถูกลบได้)
  5. แต่ทางที่ดี เราควรแยกจากกันไปเลยหากไม่มีจุดประสงค์ที่จะใช้ด้วยกันอยู่แล้ว
  6. ควรกำหนด max memory limit⁶ ทุกครั้ง
  7. การที่เรากำหนด expire time จะใช้พื้นที่โดยรวมของ memory มากขึ้น⁷
  8. การที่เราไม่กำหนด expire time จะทำให้ใช้ memory ได้เกิดประสิทธิภาพกว่า
  9. expire time ไม่ควรที่จะมีเวลานานเกินไป
  10. ใช้ allkeys-lru หรือ allkeys-lfu หากมั่นใจว่าข้อมูลกลุ่มหนึ่งจะถูกเรียกใช้มากกว่าข้อมูลชุดที่เหลือ
  11. ใช้ allkeys-random หากมั่นใจว่าทุก key มีโอกาสที่จะถูกเรียกใช้เกือบเท่าๆกัน
  12. ใช้ allkeys-lru หากไม่มั่นใจ จะเป็นการดีที่สุด⁸

Example of use cases

  1. ใช้ allkeys-lfu และไม่ต้องกำหนด expire กรณีที่ cache ไม่จำเป็นต้องอัพเดทบ่อย หรือไม่มีอัพเดทเลย เช่น ระยะทางระหว่างจุดสองจุดในพื้นที่จำกัด เช่น แผนที่ประเทศไทย ที่พื้นมีขอบเขตที่แน่นอน และมีโอกาสที่จุดระหว่างเมืองหลวงกับเมืองท่องเที่ยวจะถูกคำนวณเยอะกว่าเมืองอื่นๆ
  2. ใช้ volatile-ttl กรณีที่ cache มีหมดอายุ และเราต้องการให้ cache หมดอายุในลำดับของ FIFO⁹ เช่น session การ login ถ้า login แบบปรกติให้ set expire 30 นาที, ถ้ากด remember me ไม่ต้อง set expire (session ไม่มีหมดอายุ¹⁰) และจะไม่ถูกลบ เพราะ volatile จะลบเฉพาะ key ที่ set expire และหากจำเป็นต้องลบ key เพื่อให้มีพื้นที่เพียงพอ ก็จะลบ key ที่ใกล้จะหมดอายุก่อน¹¹

Common mistakes

  1. บาง library มี default ของ expire ให้ถึงแม้เราจะไม่ได้เซ็ทก็ตาม เราจึงควรระมัดระวังหากต้องไม่ต้องการให้มี expire
  2. การที่ใช้ memory ขนาดเล็กจนเกินไป ทำให้เกิด memory eviction บ่อยจะส่งผลให้ได้ performance ที่แย่ลง
  3. การทำงานของ eviction คือเมื่อ key ที่เข้ามาใหม่ถึงเส้น limit แล้ว จะมีจุดที่ memory เกินอยู่, ลบ key ออก จนกว่าข้อมูลใหม่จะไม่ถึงเส้น limit เพราะฉะนั้นในบางเคสที่ใช้ redis ร่วมกับ process อื่นๆ อาจทำให้เกิด OOM¹² ได้
  4. volatile-lru, volatile-random และ volatile-ttl จะทำงานเหมือน no eviction
    หากไม่สามารถที่จะลบ key ออกไปได้ (เช่น ไม่มี key ที่ถูก set expire อยู่เลย จะเกิด error กลับไปบอก client)
  5. LRU vs LFU หลายคนอาจมองว่าคล้ายๆกัน ก็คือ ถ้าถูกใช้งานบ่อย recently used ก็จะถูกอัพเดทเรื่อยๆ ผลลัพธ์ที่ได้ก็จะเหมือนๆกัน
    ซึ่งความจริงแล้วอาจไม่เสมอไป บางครั้งอาจเป็นเคสที่นานๆทีจะถูกเรียก (มีบ้าง) ทำให้คีย์นี้ถูกดันขึ้นมา และไม่ถูกลบสักที ทั้งๆที่ใช้งานน้อย
  6. ในหนึ่ง instance redis เราสามารถมีได้หลาย database แต่ใช้พื้นที่ memory ร่วมกัน ซึ่งแต่ละ database ก็จะมี eviction policies ที่ไม่เหมือนกัน
    หาก database นึงใช้ noeviction และมีอีก database ใช้ allkeys-lru
    อาจมีเคสที่ทำให้ allkeys-lru ไม่สามารถ evict ได้ เพราะโดนแย่งพื้นที่ไปหมด

Footnote

[1] memory เต็มที่ระดับ host หรือถึง max memory (เฉพาะ redis)
[2] 8 วิธีนี้อ้างอิง ณ วันที่ 15/08/2018 จากการใช้
redislab.io
[3] LFU เป็น algorithm ที่ยังใหม่ เพิ่มเข้าใน redis 4.0
[4] redis เป็นประเภท in-memory แต่สามารถทำ
persistence ได้ในระดับหนึ่ง
[5] cache life-cycle เป็น term ที่ผม make up ขึ้นมา, ไม่มี reference ครับ
[6]
max memory limit มี default อยู่ขึ้นอยู่กับ OS ด้วย (32, 62 bits system)
[7] การที่ set expire จะทำให้ใช้ memory ต่อการเก็บ key เพิ่มขึ้น
[8] แม้ allkeys-lru จะเป็น option ที่ดีที่สุดเวลาไม่มั่นใจ แต่ก็ไม่ควรมีเคสไม่มั่นใจ
[9] FIFO เป็น term ของ queue ชื่อเต็มคือ First In, First Out หรือมาก่อน ออกก่อน
[10] session login ในความเป็นจริงแล้วควรมีหมดอายุทุกรูปแบบ
[11] ในความเป็นจริงไม่ควรที่จะมี user ใดๆ หมดเวลา login ก่อนที่กำหนดไว้
[12] Out-of-memory

หากอ่านแล้วรู้สึกชอบหรือถูกใจ
อย่าลืมกดปรบมือเพื่อเป็นกำลังใจให้กับผู้เขียน

หากมีข้อผิดพลาด, ติชม, แนะนำ, หรือมีข้อสงสัย
สามารถ comment ได้อย่างเต็มที่
ทางผู้เขียนจะนำไปพิจารณา และปรับปรุงให้ดียิ่งขึ้นครับ

ขอบคุณครับ,
Nutchalum Kitpongsai.

--

--