คำสรรเสริญ memcached
(jchri.st)- แคชถูกนำมาใช้เพื่อลดภาระของฐานข้อมูล แต่เครื่องมือที่ใช้ง่ายอย่าง Redis มักค่อยๆ ถูก พึ่งพาเหมือนเป็นสตอเรจถาวร เมื่อเวลาผ่านไป
- ปัญหาไม่ได้อยู่ที่ฟีเจอร์ persistence ของ Redis แต่คือกระบวนการปฏิบัติงานที่ทำให้คอมโพเนนต์ซึ่งเริ่มต้นจากการเป็น แคชแบบชั่วคราว เข้าไปผูกกับสถานะหลักของแอปพลิเคชัน
- memcached ตามคำนิยามอย่างเป็นทางการคือ distributed memory object caching system และไม่ได้ตั้งอยู่บนแนวคิดการเก็บข้อมูลลงดิสก์ จึงจัดการได้ง่ายกว่าในฐานะเวิร์กโหลดแคชแบบไร้สถานะ
- memcached หลายอินสแตนซ์ไม่ได้ถูกแบ่งโดยเซิร์ฟเวอร์ แต่ฝั่งไคลเอนต์จะใช้รายการ URL และการแฮชคีย์ในการกระจาย และเมื่อโหนดล่มก็จะถูกเอาออกจาก hasher ก่อน แล้วค่อยพยายามเชื่อมต่อใหม่ภายหลัง
- แทนที่จะเพิ่มแคชก่อนเพียงเพราะ “ฐานข้อมูลช้า” ควรตรวจสอบ slow query และ index ที่ขาดหายไป ก่อน
ช่วงเวลาที่ Redis เปลี่ยนจากแคชเป็นสตอเรจ
- เวลาดูแลอินฟราสตรักเจอร์ มักได้ยินคำขอว่า “ต้องมีแคช” อยู่บ่อยๆ และ Redis ที่คุ้นเคยและมีฟีเจอร์มากก็มักเป็นตัวเลือกแรกที่นึกถึง
- หน้าเว็บของ Redis ชู Redis Iris ซึ่งเป็น real-time context engine สำหรับแอป AI ไว้อย่างเด่นชัด แต่ก็เป็นทิศทางที่เข้าใจได้ เพราะ Redis เป็นบริษัทที่ต้องทำรายได้
- เมื่อดีพลอย Redis และส่งต่อ connection string ให้แล้ว ช่วงแรกมันจะทำงานเหมือน แคชที่เชื่อถือได้
ปัญหาที่เกิดขึ้นหลังจากนั้นไม่กี่เดือน
- เมื่อเวลาผ่านไป
cache.set("key", "value")เรียบง่ายกว่าINSERT INTO table VALUES ('key', 'value')มาก ผู้คนจึงเริ่มปฏิบัติต่อ Redis แบบนี้- เป็นคอมโพเนนต์ที่มีอยู่ตลอดเวลา เป็นที่เก็บข้อมูล แบบพฤตินัยแล้วคือ ฐานข้อมูล
- เริ่มมอง REmote DIctionary Server ว่าเป็นสตอเรจถาวร ไม่ใช่แคชแบบชั่วคราว
- ทั้งคุณและเพื่อนร่วมทีมปฏิบัติการอาจไม่รู้ตัว และเพราะทุกคนคิดว่าแคชจะถูกมองเป็นสิ่งชั่วคราว ระบบแจ้งเตือน (alerting) จึงตรวจจับเรื่องนี้ไม่ได้
- ปัญหาจะโผล่มาก็ต่อเมื่อไปทำอะไรกับ Redis เช่นอัปเกรด ย้ายโหนด หรือเกิดอุบัติเหตุอย่างถาด HDD ของเซิร์ฟเวอร์ RAID0 หลุดออกมา
- ประเด็นสำคัญไม่ใช่ว่า Redis ไม่มีฟีเจอร์ persistence แต่คือความคลาดเคลื่อนของสมมติฐานที่ว่า Redis ที่นำมาใช้เป็นแคชจะถูกใช้งานเหมือนแคช
- ถ้ามาพบการพึ่งพานี้ทีหลัง Redis ก็จะผูกกับแอปพลิเคชันลึกเกินกว่าจะถอดออกได้ง่าย และสุดท้ายต้อง ดูแลรักษาและมอนิเตอร์ มันเหมือน “สัตว์เลี้ยง”
ทำไม memcached จึงตรงไปตรงมากว่าในบทบาทของแคช
- memcached คือ “ระบบแคชอ็อบเจ็กต์ในหน่วยความจำแบบกระจายตัว ฟรี โอเพนซอร์ส และประสิทธิภาพสูง” และเป็นแคชแบบทั่วไปที่มีไว้เพื่อลดภาระของฐานข้อมูลและทำให้เว็บแอปพลิเคชันแบบไดนามิกเร็วขึ้น
- ในเฟรมเวิร์กที่รองรับ plug-in caching อย่าง Django สามารถเปลี่ยน caching backend ได้
- แม้จะมีฟีเจอร์น้อยกว่า Redis แต่เหตุผลที่ควรเลือก memcached คือ คุณลักษณะด้านการปฏิบัติการ ที่เรียบง่ายกว่า
- จัดการ downtime ได้ง่าย: ไลบรารีฝั่งไคลเอนต์มักเพิกเฉยต่อข้อยกเว้นการเชื่อมต่อ และ
getแบบง่ายๆ ก็อาจคืนค่าเริ่มต้นหรือNoneได้แม้เซิร์ฟเวอร์จะล่ม - memcached ไม่มีฟีเจอร์คลัสเตอร์ในตัว จึงกลับทำให้ การทำคลัสเตอร์ สะดวกกว่า
- ถ้าตั้งค่า URL หลายตัวในไลบรารีฝั่งไคลเอนต์ ก็จะเลือกอินสแตนซ์เป้าหมายด้วยการแฮชคีย์
- เมื่อการเรียกจากไคลเอนต์ตรวจพบว่าอินสแตนซ์ล่ม ก็จะ นำโหนดออกจาก hasher และหลังจากเวลาหนึ่งจะพยายามเชื่อมต่อใหม่โดยอัตโนมัติ
- ภาระเรื่อง persistence ลดลงโดยโครงสร้าง: memcached ไม่บันทึกลงดิสก์ จึงเหมาะกับการจัดตารางลงที่ใดก็ได้ในฐานะเวิร์กโหลดแบบไร้สถานะ
- จัดการ downtime ได้ง่าย: ไลบรารีฝั่งไคลเอนต์มักเพิกเฉยต่อข้อยกเว้นการเชื่อมต่อ และ
- แม้จะสร้างแนวทางปฏิบัติการคล้ายกันด้วย Redis ได้ แต่สถาปัตยกรรมของ memcached ใกล้กับแนวทางนี้มากกว่า จึง เข้าใจและใช้งานเป็นแคชได้อย่างเป็นธรรมชาติ
- memcached เป็นแอปพลิเคชันที่ค่อนข้างเรียบง่าย และอีกเหตุผลที่เลือกใช้คือ แทบไม่มีโอเวอร์เฮดแม้จะรันหลายสิบอินสแตนซ์ที่มีขนาดแคชราว 64MB
- ปัญหาจำนวนมากที่ดูเหมือนว่า “ฐานข้อมูลช้า” จริงๆ แล้วเริ่มจาก slow query หรือ index ที่หายไป ดังนั้นนอกจากเพิ่มแคชแล้วก็ควรดูการปรับแต่งคิวรีด้วย
- หากสนใจการตัดสินใจด้านการออกแบบของ memcached ใน memcached blog มีบทความน่าสนใจมากมาย หนึ่งในนั้นคือบทความเดือนพฤษภาคม “กว่าจะได้คำตอบจริงๆ แล้วใช้เวลานานแค่ไหน? (How Long Does That Response Take… For Real?)”
1 ความคิดเห็น
ความเห็นจาก Hacker News
Redis เป็นเทคโนโลยีที่ยอดเยี่ยม แต่ผมมองว่ามันลำบากเพราะพยายามทำสองบทบาทที่ต่างกันไปพร้อมกันให้ดี คือ โครงสร้างข้อมูลแบบคงอยู่ถาวร และ แคชแบบชั่วคราว
ใน Redis เอง ทั้งสองอย่างนี้ก็ผสมกันได้ไม่ดีนัก จึงเป็นลักษณะเปิดหรือปิด persistence แบบทั้งระบบ
สำหรับแคชล้วน ๆ ผมจะใช้ memcached หรือสิ่งที่เทียบเท่ากัน และจะใช้ Redis ที่เปิด persistence ก็ต่อเมื่อจำเป็นต้องมีโครงสร้างข้อมูลอย่างพวกกระดานคะแนน
ที่ $WORK เราไม่ได้ใช้ทั้งคู่ แต่เก็บข้อมูลของชั้นแคชสำหรับงานช้าไว้ทั้งในระบบไฟล์และในตาราง DB ที่ใช้งานเหมือน key-value store
DB ช่วยประสานปัญหา thundering herd ส่วนการอ่านจากเซิร์ฟเวอร์เดียวกันจะอ่านจากระบบไฟล์อย่างเดียว ขณะที่การอ่านจากเซิร์ฟเวอร์อื่นจะดู DB หนึ่งครั้งแล้วคงไว้ในระบบไฟล์
จะเปลี่ยนชั้นระบบไฟล์เป็น memcached ก็ได้ แต่จนถึงตอนนี้มันทำงานได้ดีมาก
Redis มีฟีเจอร์มากกว่าชัดเจน และ antirez ก็เป็นคนที่มีเสน่ห์และถ่อมตัวอย่างน่าทึ่ง จึงเข้าใจได้ว่าทำไม Redis ถึงได้รับความนิยมมากกว่า
ถึงอย่างนั้น สำหรับผม memcached คือจุดสูงสุดของแนวคิด เลือกเทคโนโลยีที่น่าเบื่อไว้ก่อน มาโดยตลอด
ในฐานะวิศวกรแพลตฟอร์ม ผมรองรับได้ทั้งสองแบบ แต่ถ้านักพัฒนาเริ่มใช้ฟีเจอร์ขั้นสูงของ Redis อย่าง persistence, replication, clustering ผมจะพยายามตรวจสอบว่าเขาเข้าใจข้อเสียของการตัดสินใจนั้นดีพอหรือยัง
ทุกครั้งที่ผมเสนอแนวทางแบบนี้ ผมต้องถกเถียงกับคนที่ยังประสบการณ์น้อยและรู้สึกว่าแคชจำเป็นต้องอยู่ใน datastore เฉพาะทางอยู่เสมอในงานวิศวกรรมจริงนับครั้งไม่ถ้วน
ไม่ใช่ว่า memcache จะหลีกเลี่ยงปัญหาเหล่านี้ได้เลย
ผมเคยดูแลระบบที่ขยายตัวด้วย memcache ในช่วงกลางยุค 2000 และนักพัฒนาก็ตกหลุมพรางแบบเดียวกับที่บทความยกตัวอย่างในกรณี Redis
พวกเขาพยายามใช้ memcache เพื่อเลี่ยงกฎของระบบกระจาย และเพราะ การเสพติดแคช จึงกำหนดขนาดฝูงเซิร์ฟเวอร์บนสมมติฐานว่า memcache จะต้องทำงานอยู่เสมอ พอมันล่มก็ระเบิดราวกับ DDoS ทันที
ยังมี write amplification ที่เมื่อโฮสต์ใดโฮสต์หนึ่งลบคีย์ที่มี TPS สูง โฮสต์อื่นทั้งหมดก็จะพยายามเติมคีย์นั้นใหม่ด้วยการกระหน่ำเรียกบริการที่พึ่งพาอยู่, hot key ก็กลายเป็น hot host, และยังรัน memcached ร่วมกับ service daemon จนเกิด CPU spike ปริศนาอีกด้วย
บางครั้งคำเรียก memcache ก็หายเข้า blackhole เพราะความคงค้างของรายการ DNS เก่า
ทั้งหมดนี้หลีกเลี่ยงได้ถ้าใช้ memcache ให้ดีกว่านี้ แต่แรงล่อใจให้ใช้เกินขอบเขตมันสูงเกินไป
ปัญหา Redis/Valkey ที่ผู้เขียนกล่าวถึง ผมคิดว่าแทบจะเคยเจอมาหมดแล้วใน production
เคยมีเหตุขัดข้องที่ Valkey ไม่มีนโยบายหน่วยความจำ ทำให้กินหน่วยความจำจนหมดและเกิดข้อผิดพลาดการเขียน append-only file และก็เคยมีกรณีที่ดิสก์เต็มจริง ๆ จนเขียน AOF ไม่สำเร็จ
เคยมีกรณีที่คาดหวังเต็มที่ว่า Redis จะต้องยังมีชีวิต ทำงานอยู่ และเต็มไปด้วยข้อมูลผู้ใช้ทั้งหมด โดยไม่มีทางถอยกลับสู่เส้นทางช้า จนเกิด 500 error
ยังเคยเห็นการใช้ sorted set และโครงสร้างข้อมูลอื่นอย่างสร้างสรรค์ โดยอาศัยว่าชุดเหล่านั้นจะไม่มีวันถูกไล่ออกอย่างเด็ดขาด
แม้จะเห็นกับตาแบบนี้ ผมก็ยังรู้สึกว่ายากที่จะเริ่มจากแนะนำ memcache ก่อน Redis
การออกแบบแอปให้มีการจัดวางแคชที่เป็นมิตรกับ memcache อาจซับซ้อน และถ้าทีมใหญ่พอใช้ memcache ก็มักมีโอกาสสูงมากที่จะสุดท้ายหาทางไปสู่จุดที่ต้องใช้ Redis อยู่ดี
แล้วคุณก็ต้องดูแลเทคโนโลยีแคช 2 ตัว
Redis instance ที่ตั้งไว้สำหรับแคชจะเอาไปใช้วัตถุประสงค์อื่นไม่ได้, instance สำหรับแคชต้องมี eviction, และ instance ที่ไม่ใช่แคชต้องไม่มี eviction
สุดท้ายก็ต้องมี Redis ตัวที่สองที่ตั้งค่าต่างออกไป
พูดตรง ๆ การออกแบบแอปให้มีการจัดวางแคชที่เป็นมิตรกับ memcache ก็เหมือนกับการออกแบบให้เป็นมิตรกับ Redis
รูปแบบของ application cache พวกนี้เหมือนกันหมด คือดึงมาก่อน ถ้าไม่มีก็คำนวณแล้วค่อย set
var value = cache.lookup( keyname, () => db.query(...), TimeSpan.FromMinutes(5) // or CacheOptions );แบบนี้จะไปเส้นทางสำรองหรือแทรกค่าได้ทันทีเมื่อเกิด cache miss
อีกคุณสมบัติหนึ่งของ memcache ที่ไม่ค่อยถูกพูดถึงคือ ทุก operation ถูกออกแบบให้เป็น O(1)
นี่เป็นการออกแบบที่ผู้สร้างเลือกอย่างตั้งใจ จึงมีข้อจำกัดอยู่บ้าง แต่รับประกันได้ว่าจะไม่เกิดการค้างแบบสุ่มใน operation ง่าย ๆ
ส่วน Redis ใช้แกนการออกแบบแบบ single-thread จึงสามารถรัน operation ที่มีความซับซ้อนตามอำเภอใจได้ และในมุมของนักพัฒนาก็อาจรู้สึกว่าตัวเองฉลาดขึ้นเมื่อใช้งานมัน แต่ทุกอย่างที่เหลือจะต้องรอจนกว่า operation นั้นจะจบ
ในโปรเจ็กต์โอเพนซอร์สหรือโปรแกรมที่ต้องบำรุงรักษาระยะยาว เรื่องแบบนี้เกิดขึ้นบ่อย
พอ codebase ใหญ่ขึ้น ก็สุดท้ายเริ่มรองรับสิ่งที่ไม่ได้อยู่ในแผนเดิม
พอฟีเจอร์มากขึ้น ผู้ใช้ก็เพิ่มขึ้นด้วย บางคนใช้แค่ฟีเจอร์เก่า บางคนรับฟีเจอร์ใหม่ สุดท้ายค่าบางค่ากลายเป็นค่าเริ่มต้นโดยพฤตินัยจนไม่ค่อยดูเหมือนเป็นตัวเลือกอีกต่อไป
ถ้ายก Redis เป็นตัวอย่าง ปิด AOF แล้วมันก็ทำงานเป็นแคช in-memory แบบข้อมูลหายได้ แต่คนส่วนใหญ่ก็ไม่ได้มองมันแบบนั้น
เลยมีตรรกะว่าของที่มีฟีเจอร์น้อยและเรียบง่ายกว่าน่าจะดีกว่า และในบริบทนี้ Memcached เป็นตัวอย่างของแนวทางแบบ worse-is-better
สำหรับทีมใหญ่ เรื่องนี้สมเหตุสมผลมาก แต่โปรเจ็กต์โอเพนซอร์สจำเป็นต้องมีอัปเดตสม่ำเสมอเพื่อให้ได้เงินทุนหรือแรงสนับสนุนต่อเนื่อง จึงมีความตึงเครียดที่แฝงอยู่
บางครั้งก็ลงเอยด้วยการแตก fork หรือโปรเจ็กต์ลูกที่เชี่ยวชาญเฉพาะทางในพื้นที่เฉพาะ
โดยส่วนตัวผมคิดว่าไม่มีคำตอบตายตัวและขึ้นอยู่กับบริบท
เพราะแม้แต่การสื่อสารเองก็ไม่ใช่ของฟรี
ดูเหมือนนักพัฒนาจะไม่ตระหนักเรื่องนี้กันเลย
ผมคิดว่าเป็นเพราะพวกเขาเปลี่ยนจาก Memcached มาเป็น Redis แล้วคาดหวังให้มันเหมือนเดิมทุกอย่าง
ถึงอย่างนั้นมันก็ยังเป็นแคชที่ยอดเยี่ยมอยู่ดี
ช่วงไม่กี่ปีที่ผ่านมา ผมทำงานกับ Flask พอสมควร แม้จะไม่ใช่งานเต็มเวลา แต่ก็ใช้มันเป็นส่วนหนึ่งของ tech stack ของธุรกิจอีคอมเมิร์ซขนาดเล็ก
ในสแตก Python สำหรับ MongoEngine, SQLAlchemy, Celery และ Google/eBay/Shopify ผมเจอกับกับระเบิดและความประหลาดสารพัด แต่ไม่เคยเจอแบบนั้นกับ Redis
อาจเป็นเพราะเราไม่ให้สิทธิ์แอดมินกับใครก็ตามที่คิดว่า Redis เป็น persistent store แต่พูดตามตรง ผมอยากเรียก Redis ว่าเป็น เทคโนโลยีที่แข็งแกร่งและออกแบบมาอย่างดีมาก
API เรียบง่ายสุด ๆ และทุกครั้งที่ต้องทำอะไรแปลก ๆ สักหน่อย มันก็มีวิธีที่สมเหตุสมผลและคิดมาดีรองรับอยู่
จะใช้ระบบทำให้ใช้ไม่ได้อย่างการติดแท็กก็คงได้
ผมสงสัยจริง ๆ ว่ามีอะไรแปลก ๆ ที่ทำกับระบบแคชได้บ้าง และคนนิยมใช้แคชทำอะไรนอกเหนือจากแค่แคชข้อมูล
ผมชอบ memcached แต่ถ้าตั้ง Redis ให้เป็นแคชแบบข้อมูลหายได้แล้วผู้คนกลับปฏิบัติกับมันเหมือนเป็น ที่เก็บข้อมูลถาวร นั่นก็ไม่ใช่ความผิดของ Redis
ยิ่งเมื่อ memcached เองก็ไม่ถาวรเหมือนกัน การเปรียบเทียบแบบนั้นยิ่งแปลกเข้าไปใหญ่
ถ้าไม่มีใครบอกไว้ต่างหาก ก็ไม่แปลกที่นักพัฒนาใหม่จะตั้งสมมติฐานแบบนั้น
Memcached เป็นผู้กอบกู้ของโลกแคชในยุคที่มันออกมา
และก็ดีที่มันถูกสร้างโดย Brad Fitzpatrick สำหรับ LiveJournal ในปี 2003
แต่ละโพสต์ในฟีดผู้ใช้อาจมีข้อจำกัดการเข้าถึงต่างกัน จึงสามารถแคชได้ทั้งระดับโพสต์และทั้งหน้า
ผมใช้มันอยู่หลายปีกับ Ruby on Rails หน้าเว็บเร็วขึ้นและมันก็ทำงานได้ดีเฉย ๆ
ข้อเสียและในแง่ความเร็วก็เป็นข้อดีด้วย คือแคชถูกเก็บไว้ใน หน่วยความจำ ไม่ใช่บนดิสก์
ถ้าข้อมูลที่ต้องแคชมีขนาดใหญ่มากและเป็นเว็บไซต์ขนาดใหญ่ ค่าโฮสต์อาจแพงได้
ในกรณีนั้น Solid Cache เป็นผู้กอบกู้สำหรับผม
โปรเจ็กต์ที่กำลังทำอยู่ตอนนี้มีแคชเกิน 100GB เก็บไว้บนดิสก์ของ PostgreSQL ค้นหาได้เร็วด้วยดัชนี และ Rails ก็จัดการหมดอายุอัตโนมัติด้วยการลบแถวนั้นออก
ถ้าขนาดแคชเล็กกว่านี้และใช้ Redis อยู่แล้ว ผมก็คงใช้ Redis ต่อไปเลย
แต่ถ้าความเร็วสำคัญที่สุด ผมจะลอง benchmark Memcached กับ Redis
ความที่ memcached เป็นของชั่วคราว กับการที่คนจะนำมันไปใช้เหมือนของถาวรหรือไม่ เป็นคนละประเด็นกัน
ถ้าอัตรา cache hit ดูเหมือนจะอยู่ที่ 99.9% และมันมีอยู่ตลอด ไม่นานเดี๋ยวก็ต้องมีคนเขียนโค้ดที่พึ่งพาพฤติกรรมแบบนั้น
ผมคิดว่าในโหมดพัฒนา อาจช่วยได้ถ้าไลบรารีฝั่งไคลเอนต์คืนค่า null สัก 10% ของครั้งทั้งหมด
memcached เร็วกว่า Redis แบบเหลือเชื่อสำหรับงาน แคชคีย์-ค่า ที่เรียบง่าย
มันมีเธรด และถูกปรับแต่งมาอย่างมากให้ทำสิ่งเดียวได้ดีมาก
ขณะที่ Redis ให้ความรู้สึกใกล้เคียงกับ Python heap กลางที่แชร์กันสำหรับงานสารพัดมากกว่า พร้อมโครงสร้างข้อมูลทุกแบบและความเป็น single-threaded
ที่ Notion ใช้ Redis หลายอย่าง แต่ให้ memcached รับหน้าที่แคชจริง
โดยเฉลี่ยอยู่ราว 300 ไมโครวินาที เทียบกับ 350 ไมโครวินาที ต่อการอ่าน
เรื่องที่เป็น single-threaded ก็ไม่ค่อยสำคัญนัก เพราะคอขวดไม่ใช่ CPU แต่เป็น reactive I/O
มันช่วยให้ใช้คอร์ CPU ได้มากขึ้น แต่ถ้าโหลดไม่ได้สูงมาก memcached แบบ single-threaded ก็ใช้ CPU น้อยกว่าแบบ multi-threaded