Wednesday, November 23, 2005

การ implement cache ใน Web Application #1

Post นี้จะว่าด้วย solution สำหรับ java application ก่อน
ครั้งต่อไปถึงจะว่าด้วย caching บน RoR (ruby on rails)
Tool ที่ผมเลือกใช้สำหรับ solution นี้ ก็คือ OSCache

OSCache มาพร้อมกับวิธีการใช้ 3 แบบใหญ่ๆคือ
  • ใช้ API ตรงๆ
    อันนี้เหมาะกับ implement caching ในฝั่ง business logic
  • ใช้ JSP Taglib
    อันนี้เหมาะสำหรับ fine-grain cache
    เพราะเราสามารถกำหนดบริเวณที่เราต้องการ cache ได้ตามใจชอบ
    (เลือกเฉพาะบางส่วนใน jsp ได้)
  • ใช้ CacheFilter
    อันนี้เหมาะสำหรับ cache ระดับ Page
    (เพราะว่าสะดวกในการ config ดี)


ขั้นแรกในการใช้ OSCache ก็คือ
การกำหนด Configuration ที่เราต้องการ
  • เลือกกำหนดขนาดของ cache ที่เราต้องการก่อน
    ค่า default จะอยู่ที่ 1000 objects
    (สามารถกำหนดเป็น unlimit ก็ได้)
  • ประเด็นที่ตามมาก็คือ algorithm ในการควบคุมขนาดของ cache
    OSCache มีให้เลือก 2 แบบ
    • LRU (Least-Recently-Used)
    • FIFO

    ประเด็นที่น่าสนใจอันหนึ่งของ LRU
    ก็คือ OSCache implement LRU โดยใช้
    java.util.LinkedHashMap
    ซึ่งถ้าเราตามไปดู javadoc ของ LinkedHashMap จะเห็นคำอธิบายดังนี้
    A special constructor is provided to create a linked hash map whose order of iteration is the order in which its entries were last accessed, from least-recently accessed to most-recently (access-order). This kind of map is well-suited to building LRU caches.

    และถ้าตามไปดู source code ของ OSCache ในส่วน
    LRU ก็จะพบว่า วิธีการที่มัน implement LRU จะทำเป็น 3 step
    ก็คือ
    • ทดลองดูว่าใน classpath มี LinkedHashMap หรือไม่
      ถ้ามีก็ใช้ตัวนี้
    • ถ้าไม่มี ก็จะมองหา org.apache.commons.collections.SequencedHashMap
      ของ jakarta-commons collection แทน
    • ถ้าไม่พบ ก็จะลงเอยด้วยการใช้ LinkedList
      ซึ่งอันนี้อาจจะมีผลเสียต่อ performance แทนในกรณี load สูงๆ

  • เลือกว่าจะทำ Persistent ของ cache ด้วยหรือไม่
    โดย default แรกสุด จะเก็บ cache ใน Memory เท่านั้น
    แต่เราสามารถเลือก persistent ลง Disk ได้ด้วย
    โดย option ที่น่าสนใจก็คือ
    cache.persistence.overflow.only
    ถ้าเรา set ค่านี้เป็น True เวลาที่ Memory Cache เราเต็ม
    entry ที่ถูก remove ออก จะย้ายมาลง Disk แทน
  • กำหนด cache.blocking
    default คือ false นั่นคือ กรณีที่ entry ของเราหมดอายุ
    แล้วมี thread 2 thread เข้ามาพร้อมๆกัน thread แรก
    จะเป็นผู้ได้รับเลือกให้เป็นคนที่ update ค่านั้น ส่วน thread ที่ 2
    จะได้ค่า ณ ขณะนั้น (stale entry) กลับไปแทน
    (ซึ่งก็ถือว่าใช้ได้นะ เพราะถือว่า entry มีอายุเกินกว่าที่เรากำหนดไปไม่กี่ millisec)
    แต่ถ้ากำหนดเป็น true ก็จะมีผลว่า thread ที่ 2
    จะถูก block เพื่อรอให้ thread 1 ทำการ update ให้เสร็จก่อน
    Note: เท่าที่ลองใล่ debug ดู พบว่า
    กรณีที่เป็น new Entry ใหม่ๆเลย ไม่ว่าเราจะเลือก block เป็น true หรือ
    เป็น false ก็ตาม
    OSCache จะเลือกใช้วิธี blocking สำหรับพวก new Entry เสมอ



ตัวอย่าง source code ให้ลองดูที่ document ของ OScache โดยตรง
เพราะเขาเขียนไว้ดีแล้ว

feature ที่น่าสนใจอื่นๆก็คือ
  • การกำหนด cron expression
    ที่ทำให้เรากำหนด schedule ของการ expire ได้
    เช่นกำหนดให้ cache expire ทุกเช้าวันจันทร์
  • การกำหนด group ของ cache
    อันนี้เหมาะสำหรับกำหนดกลุ่ม cache ที่มี content depend ถึงกัน
    เวลาเรามีการ update content จะได้สั่ง flush cache ทีเดียวทั้ง group ได้เลย
  • ใน jsp taglib หรือ cacheFilter สามารถกำหนด
    scope ของ cache ได้
    เช่นถ้าเป็น session, cache ก็จะถูกแยกเป็นของใครของมันเลย
    ไม่ share กัน
    แต่ถ้าเป็น application ก็จะมี cache แค่ instance เดียว
    ให้ทุก session share ใช้กัน


เพิ่มเติม
ประเด็นที่ต้อง design ดีๆ เวลาใช้ cache
ก็คือ กรณีที่ table ใน database เรามีการเปลี่ยนแปลง
แล้วเราต้องการ flush cache (delete cache) ที่เกี่ยวข้องทิ้งทั้งหมด
เราจะทำได้อย่างไร
ทางออกก็อาจจะเช่น design ให้ key ที่มี content เกี่ยวข้องกัน มี prefix เหมือนๆกัน
(เวลาสั่ง flush สามารถสั่ง flush พวกที่มี prefix ตามที่กำหนดได้)
หรือเลือกใช้ group feature เข้ามาช่วย

Related link from Roti

No comments: