งูต้นฉบับกำลังดูแลชั้นเก็บของที่ช่องหนึ่งค่อย ๆ ว่างเมื่อเจ้าของยกกล่องออกไป

Python WeakValueDictionary: ทำ cache ที่ไม่ยื้ออายุ object

พี่โชว์Engineering

cache ปกติเป็นเจ้าของค่าที่เก็บอยู่ จึงอาจทำให้ object ใหญ่มีชีวิตตลอดอายุ process แม้ส่วนอื่นเลิกใช้แล้วครับ weakref.WeakValueDictionary เก็บค่าแบบ weak reference: เมื่อไม่มี strong reference จากเจ้าของจริง entry จะหายตาม object เหมือนสมุดรายชื่อที่ลบบัตรเมื่อเจ้าของนำของออกจากคลัง

import gc
import unittest
from weakref import WeakValueDictionary


class Preview:
    def __init__(self, body: bytes):
        self.body = body


class WeakCacheTests(unittest.TestCase):
    def test_entry_follows_real_owner(self):
        cache = WeakValueDictionary()
        preview = Preview(b"forest")
        cache["hero"] = preview
        self.assertIs(cache["hero"], preview)

        del preview
        gc.collect()
        self.assertNotIn("hero", cache)


if __name__ == "__main__":
    unittest.main()

Cache นี้รับประกันอะไร

เหมาะกับ interning, metadata หรือ wrapper ที่สร้างใหม่ได้และมี owner ภายนอกชัดเจน ถ้า cache คือเจ้าของสำเนาเพียงคนเดียว ค่าอาจหายทันทีหลังคืนจากฟังก์ชัน ทำให้ hit rate แทบเป็นศูนย์ งานที่ต้องคุมจำนวนรายการหรือต้องเก็บอย่างน้อยช่วงเวลาหนึ่งควรใช้ LRU/TTL cache ซึ่งมี policy ชัดกว่า

การเช็ก key in cache แล้วค่อยอ่านเป็นสองจังหวะมี race ได้ เพราะ object อาจถูกเก็บกวาดระหว่างกลาง ให้อ่านด้วย cache.get(key) ครั้งเดียวแล้วเก็บผลในตัวแปร local ซึ่งกลายเป็น strong reference ระหว่างใช้งาน นอกจากนี้ built-in บางชนิด เช่น list และ dict ปกติรองรับ weak reference ไม่ได้ ต้องห่อด้วย class ที่เหมาะสม และ __slots__ ต้องมี __weakref__ หากต้องการใช้กลไกนี้

อย่าใส่ callback ที่จับ object เป้าหมายกลับแบบ strong reference เพราะจะสร้างวงจรที่ขัดกับเป้าหมาย และอย่าใช้เวลาที่ entry หายเป็น business event ที่แน่นอน เนื่องจาก garbage collection ไม่ใช่ scheduler

Recap

  • ใช้ weak cache เฉพาะค่าที่สร้างซ้ำได้และมี owner ภายนอก
  • อ่านด้วย get() ครั้งเดียวแล้วถือ local reference ระหว่างใช้
  • ตรวจว่าชนิดค่ารองรับ weak reference
  • ใช้ LRU/TTL เมื่อ retention เป็นส่วนหนึ่งของสัญญา