Rails Framework ใหญ่ Built-in Tools เยอะสำหรับ Specific Situations ซีรีส์นี้สำรวจ Lesser-known Tools ใน Codebase ใหญ่ของ Rails
บทความนี้อธิบาย Methods increment และ decrement ใน Rails.cache
The Rails.cache Helper
Rails.cache = Entry Point Interact กับ Cache ใน App เป็น Abstraction ให้ Common API เรียกได้ไม่ว่า Cache "Store" จริงจะเป็นอะไร Out of the Box Rails Support:
- FileStore
- MemoryStore
- MemCacheStore
- NullStore
- RedisCacheStore
Inspect Rails.cache ดูว่าใช้ Store ไหน:
> Rails.cache
=> #<ActiveSupport::Cache::RedisCacheStore options={:namespace=>nil, ...
Summary สั้นๆ:
- NullStore: ไม่เก็บอะไร Read Back =
nil Default New Rails App - FileStore: เก็บ Cache เป็น Files บน Hard Drive Persist แม้ Restart Server
- MemoryStore: เก็บใน RAM Stop Server = Wipe Cache
- MemCacheStore/RedisCacheStore: ใช้ External Programs (MemCache/Redis) Maintain Cache
3 ตัวแรก Dev/Testing Production -> Redis/MemCache Rails.cache Abstract Differences -> Easy รัน Stores ต่างกันใน Environments ต่างๆ (NullStore Dev, MemoryStore Test, RedisCacheStore Production)
Cached Data (ข้อมูลที่แคช)
ผ่าน Rails.cache.read, write, fetch Store/Retrieve Arbitrary Data ได้ง่าย บทความก่อนหน้าครอบคลุมละเอียด สำคัญ: No Built-in Thread-safety บน Methods เหล่านี้ Update Cached Data จาก Multiple Threads (Running Count) ต้อง Wrap Read/Write ใน Lock (Mutex) กัน Race Conditions สมมติ Redis Cache Store:
threads = []
Rails.cache.write(:test_counter, 0)
4.times do
threads << Thread.new do
100.times do
current_count = Rails.cache.read(:test_counter)
current_count += 1
Rails.cache.write(:test_counter, current_count)
end
end
end
threads.map(&:join)
puts Rails.cache.read(:test_counter)
4 Threads แต่ละตัว Increment 100 ครั้ง ผลควร 400 แต่มักน้อยกว่า (Test ได้ 269) Race Condition: Threads Operate Shared Data Out of Sync Thread หนึ่ง Read แล้ว อีก Thread Read แล้ว Increment Store Thread แรก Resume ใช้ค่าเก่า
Common Fix: Mutex Lock ระหว่าง Code แต่ Rails.cache มี Methods จัดการ Scenario นี้
Rails Cache Increment และ Decrement
Rails.cache มี increment และ decrement สำหรับ Act Directly บน Cached Data เหมือน Counter:
threads = []
Rails.cache.write(:test_counter, 0, raw: true)
4.times do
threads << Thread.new do
100.times do
Rails.cache.increment(:test_counter)
Rails.cache.decrement(:test_counter)
Rails.cache.increment(:test_counter)
end
end
end
threads.map(&:join)
puts Rails.cache.read(:test_counter, raw: true)
ใช้ increment/decrement ต้องบอก Cache Store ว่า raw: true (Bare Integer) อ่านกลับต้อง raw: true ไม่งั้น Error ยังใช้ expires_in Flags อื่นได้
Key: increment/decrement ใช้ Atomic Operations (Redis/MemCache) = Thread Safe ไม่มีทาง Thread Pause ระหว่าง Atomic Operation
Note: Methods นี้ Return New Value ด้วย ถ้าต้องใช้ค่าใหม่ทันที ไม่ต้อง Read อีกครั้ง
Real-World Applications (การใช้งานจริง)
ดูเหมือน Low-level Helpers สำหรับ Background Job Gems แต่รู้จักแล้วอาจ Surprised ที่ Useful
Use Case 1: ป้องกัน Duplicate Scheduled Background Jobs รันพร้อมกัน Production App: Scheduled Jobs Update Search Indices, Mark Abandoned Carts Search Indices ใช้ Memory เยอะ 2 Jobs รันพร้อม -> Exceed Heroku Dyno Limit -> Worker Killed Jobs Unique แต่วันละ Job ต่างกันก็อาจรันพร้อมกัน Base Class สำห Scheduled Jobs เก็บ Counter Running Jobs Count สูง -> Re-enqueue Wait
Use Case 2: Side Project Background Job(s) Process User Wait Progress Communication Common Issue ใช้ Rails.cache.increment Update Global Counter Structure:
- Class ใน
/app/modelsAbstract Cache Counter Generate Unique Cache Key Per Job - Job สร้าง Instance Model Update ระหว่าง Process Items
- JSON Endpoint สร้าง Instance Grab Current Value
- Front-end Poll Endpoint ทุกๆ วินาที Update UI (ActionCable Push Updates ก็ได้)
Conclusion (สรุป)
ซื่อสัตย์ Rails.cache.increment ไม่ใช่ Tool ที่หาบ่อย เพราะไม่อften Update Data ใน Cache (Temporary Nature) แต่ใช่เมื่อ Related to Background Jobs (Jobs Store Data ใน Redis แล้ว Temporary) Natural เก็บ Related Data (Percentage Complete) ในที่เดียวกับ Persistence ระยะสั้น
สิ่ง "Off the Beaten Path" ควรระวัง Introduce เข้า Codebase แนะนำ: เพิ่ม Comments อธิบาย Future Developers ทำไมใช้ Method นี้ (พวกเขาน่าจะไม่คุ้น)