คำแนะนำสำหรับมือใหม่ด้านระบบแบบกระจาย
(somethingsimilar.com)- ความยากหลักของระบบแบบกระจายไม่ใช่เวลาแฝง แต่คือ ความล้มเหลวบางส่วน และแม้แต่การจัดการ exception แบบง่าย ๆ บนเครื่องเดียวก็กลายเป็นส่วนหนึ่งของการออกแบบโปรโตคอลเมื่อมีหลายเครื่อง
- ระบบแบบกระจายที่แข็งแกร่งมีบั๊กที่เผยให้เห็นเฉพาะบนหลายเครื่องจริง เงื่อนไขเครือข่ายของดาต้าเซ็นเตอร์ และชุดข้อมูลขนาดใหญ่ จึง มีต้นทุนสูงกว่าระบบเครื่องเดียว
- หากต้องการสร้างระบบที่ปฏิบัติการได้จริง ควรลดการประสานงาน เพิ่ม ความเป็นอิสระ และใช้ backpressure, partial availability, metrics และ percentile metrics เป็นเครื่องมือพื้นฐาน
- feature flag, การออกแบบพื้นที่ ID, data locality และการแยก service เป็นเทคนิคภาคปฏิบัติที่ช่วยลด ขอบเขตความล้มเหลวและต้นทุนการประสานงาน ระหว่างการ migration และการขยายระบบ
- CAP theorem เป็นเหมือน เครื่องมือวิจารณ์การออกแบบ มากกว่าจุดตั้งต้นของการสร้างระบบ และในระบบแบบกระจาย การประสานงานเชิงสังคมระหว่างทีมกับองค์กรก็ยากพอ ๆ กับปัญหาทางเทคนิค
สิ่งที่มักพลาดเมื่อเริ่มเรียนระบบแบบกระจาย
- บทเรียนจำนวนมากของวิศวกรระบบแบบกระจายมาจากความผิดพลาดที่เจอใน production traffic และ บาดแผลจากการปฏิบัติการ แต่สำหรับวิศวกรมือใหม่ จำเป็นต้องมีบริบทที่ตรงกว่าและนำไปทำได้จริงมากกว่า
- Fallacies of Distributed Computing และ CAP theorem มีประโยชน์ต่อการเรียนรู้ด้วยตนเอง แต่ค่อนข้างเป็นนามธรรมสำหรับวิศวกรที่มีประสบการณ์น้อยจนลงมือได้ทันที
- ระบบแบบกระจายต้องใช้เครื่องและเงินทุนมากกว่า จึงมักมีทีมจำนวนมากและองค์กรขนาดใหญ่เข้ามาเกี่ยวข้อง
- ปัญหาเชิงสังคมโดยทั่วไปเป็นส่วนที่ยากที่สุดในงานของนักพัฒนาซอฟต์แวร์ และอาจเด่นชัดเป็นพิเศษในการพัฒนาระบบแบบกระจาย
- บางครั้งวิธีแก้เชิงสังคมมีประสิทธิภาพและน่าพึงพอใจกว่าวิธีแก้เชิงเทคนิค แต่ภูมิหลัง การศึกษา และประสบการณ์มักทำให้วิศวกรเอนเอียงไปทางวิธีแก้เชิงเทคนิค
ออกแบบโดยตั้งต้นจากความล้มเหลวและต้นทุน
- ระบบแบบกระจายล้มเหลวบ่อย และที่แย่กว่านั้นคือ ล้มเหลวเพียงบางส่วน
- การปลดล็อก mutex ในโปรเซสเดียวล้มเหลวอาจแก้ได้ด้วยการทำให้โปรเซส crash แต่การปลดล็อก mutex แบบกระจายล้มเหลวต้องถูกรวมไว้ในโปรโตคอลของ lock
- แนวทางอย่าง “ส่ง write ไปทั้งสองเครื่องก็พอ” หรือ “retry จนกว่าจะสำเร็จ” จัดการสถานการณ์ที่ write สำเร็จฝั่งหนึ่งแต่อีกฝั่งล้มเหลวได้ไม่เพียงพอ
- อาจเกิดสถานการณ์อย่าง switch เสีย, leader หายไปเพราะ GC pause, socket write ที่ดูเหมือนสำเร็จแต่ฝั่ง remote ล้มเหลว หรือดิสก์ช้าเพียงตัวเดียวทำให้โปรโตคอลสื่อสารทั้งคลัสเตอร์ช้าลง
- โซลูชันแบบกระจายที่แข็งแกร่ง มีต้นทุนสูงกว่าโซลูชันเครื่องเดียว
- มีความล้มเหลวที่เกิดขึ้นเฉพาะบนหลายเครื่อง และแม้ VM กับ cloud จะลดต้นทุนลง แต่ก็ยังไม่ถูกเท่าการออกแบบ พัฒนา และทดสอบบนคอมพิวเตอร์เครื่องเดียวที่มีอยู่แล้ว
- ความล้มเหลวที่เผยให้เห็นเฉพาะเมื่อชุดข้อมูลใหญ่จนใส่ในเครื่อง shared ได้ยาก หรือภายใต้เงื่อนไขเครือข่ายดาต้าเซ็นเตอร์ จำเป็นต้องใช้สภาพแวดล้อมแบบกระจายจริง
- simulation มีประโยชน์ แต่ทดแทนบั๊กของระบบแบบกระจายทั้งหมดไม่ได้
- ระบบแบบกระจายโอเพนซอร์สที่แข็งแกร่งพบได้น้อยกว่าระบบเครื่องเดียวที่แข็งแกร่งมาก
- ต้นทุนในการรันเครื่องจำนวนมากเป็นเวลานานเป็นภาระต่อชุมชนโอเพนซอร์ส
- นักพัฒนาสายงานอดิเรกและ dilettante สร้างโอเพนซอร์สด้วยเครื่องที่มีอยู่และเวลาว่าง จึงหานักพัฒนาที่ยอมเปิดหลายเครื่อง ดูแลรักษา และจ่ายค่าใช้จ่ายได้ยาก
- วิศวกรในบริษัทช่วยเติมช่องว่างบางส่วนได้ แต่ลำดับความสำคัญขององค์กรนั้นอาจไม่ตรงกับลำดับความสำคัญขององค์กรผู้ใช้
ลดการประสานงานและดูดซับความขัดข้อง
- ควรหลีกเลี่ยง การประสานงาน ระหว่างเครื่องให้มากที่สุด
- หัวใจของ horizontal scalability คือการจัดวางข้อมูลให้เครื่องต่าง ๆ เป็นอิสระมากพอที่จะลดการสื่อสารและการตกลงร่วมกันระหว่างกันให้น้อยที่สุด
- ทุกครั้งที่สองเครื่องต้องตกลงกันเรื่องใดเรื่องหนึ่ง การทำ service ก็จะยากขึ้น
- ความเร็วในการส่งต่อข้อมูลมีเพดาน การสื่อสารผ่านเครือข่ายไม่เสถียรกว่าที่คิด และสัญชาตญาณเกี่ยวกับ consensus อาจผิดได้
- การทำความเข้าใจ Two Generals, Byzantine Generals และ ความยากในการ implement Paxos จะช่วยได้
- ถ้าปัญหาใส่ในหน่วยความจำได้ สำหรับวิศวกรระบบแบบกระจายโดยทั่วไปก็ใกล้เคียงกับ ปัญหาเล็กน้อย
- เมื่อข้อมูลไม่ได้อยู่ห่างออกไปแค่ dereference pointer ไม่กี่ครั้ง แต่อยู่ข้าม switch หลายตัว การประมวลผลให้รวดเร็วจะยากขึ้น
- อัลกอริทึมและ implementation สำหรับเครื่องเดียวมีอยู่มากมาย แต่เอกสารและ implementation สำหรับระบบแบบกระจายมีน้อยกว่ามาก
- “ช้า” เป็นปัญหาที่ debug ยากที่สุด
- อาจมีหนึ่งระบบหรือมากกว่านั้นในหลายระบบที่เกี่ยวข้องกับคำขอของผู้ใช้ทำงานช้า หรือบางส่วนของ pipeline แปลงข้อมูลที่ผ่านหลายเครื่องทำงานช้า
- คำอธิบายปัญหาเองแทบไม่ให้เบาะแสว่าข้อบกพร่องอยู่ตรงไหน และอาจมีความล้มเหลวบางส่วนซ่อนอยู่ในจุดที่ไม่ปรากฏบนกราฟที่ดูเป็นประจำ
- ก่อนที่ performance degradation จะชัดเจนมากพอ มักเป็นเรื่องยากที่จะได้รับทรัพยากรอย่างเวลา เงิน และเครื่องมืออย่างเพียงพอ
- Dapper และ Zipkin ถูกสร้างขึ้นด้วยเหตุผลนี้
- ต้อง implement backpressure ทั่วทั้งระบบ
- backpressure คือวิธีที่ระบบที่ให้บริการส่งสัญญาณความล้มเหลวไปยังระบบที่ส่งคำขอ และระบบที่ส่งคำขอจัดการความล้มเหลวนั้นเพื่อป้องกันไม่ให้ทั้งตนเองและอีกระบบ overload
- การจำกัดการใช้ทรัพยากรระหว่าง overload หรือ system failure เป็นองค์ประกอบพื้นฐานของระบบแบบกระจายที่แข็งแกร่ง
- implementation มักเป็นการทิ้ง message ใหม่เมื่อทรัพยากรถูกจำกัดหรือเกิดความล้มเหลว หรือส่ง error กลับไปยังผู้ใช้ และทั้งสองกรณีควรเพิ่ม metrics
- การเชื่อมต่อและคำขอไปยังระบบอื่นจำเป็นต้องมี timeout และ exponential backoff
- หากไม่มี backpressure จะเกิด cascading failure หรือ message loss โดยไม่ตั้งใจได้ง่าย
- ต้องหาวิธีให้บริการแบบ partial availability
- partial availability คือความสามารถในการคืนผลลัพธ์บางส่วนแม้บางส่วนของระบบจะล้มเหลว
- ระบบค้นหาต้องแลกเปลี่ยนระหว่างคุณภาพของผลลัพธ์กับเวลาที่ทำให้ผู้ใช้รอ และเมื่อพ้น time limit ก็อาจคืนเฉพาะผลลัพธ์ที่รวบรวมได้
- ใน private messaging การเกิดขัดข้องกับผู้ใช้บางส่วนอาจดีกว่าการที่ message บางส่วนหายไปสำหรับผู้ใช้ทั้งหมด
- ต้องตัดสินใจว่าจะแยก failure domain มากแค่ไหน เพื่อไม่ให้ความขัดข้องของ private messaging ส่งผลต่อฟีเจอร์ที่ไม่เกี่ยวข้องอย่างการอัปโหลดรูปภาพสาธารณะ
ใช้ observability, capacity และ deployment เป็นเครื่องมือปฏิบัติการ
- metrics เป็น เครื่องมือจำเป็น ในการรู้ว่าระบบทำอะไรจริงใน production
- metrics อย่าง latency percentile, counter ที่เพิ่มขึ้นของพฤติกรรมเฉพาะ และ rate of change ช่วยลดช่องว่างระหว่างพฤติกรรมของระบบที่เชื่อว่ามีอยู่กับพฤติกรรมจริง
- การรู้ว่าพฤติกรรมของระบบในวันที่ 20 แตกต่างจากวันที่ 15 อย่างไร คือสิ่งที่แบ่งแยกวิศวกรรมที่ประสบความสำเร็จกับไสยศาสตร์ที่ล้มเหลว
- metrics จำเป็นต่อการทำความเข้าใจปัญหาและพฤติกรรม แต่ยังไม่เพียงพอที่จะบอกว่าควรทำอะไรต่อไป
- log file มีประโยชน์แต่ โกหกได้ง่าย
- แม้ log ของ error บาง class จะกินพื้นที่ไฟล์จำนวนมาก แต่สัดส่วนต่อ request จริงอาจต่ำ
- log ความสำเร็จส่วนใหญ่ซ้ำซ้อนและอาจทำให้ดิสก์เต็มได้ อีกทั้งวิศวกรมักเดาผิดว่า error log แบบใดมีประโยชน์
- ควรเขียน log โดยสมมติว่าคนที่ไม่เคยเห็นโค้ดจะเป็นผู้อ่าน
- หากเห็นสิ่งแปลกใน log แล้วประเมินเกินจริงโดยไม่ยืนยันด้วย metrics อาจทำให้ outage ยาวนานขึ้น
- ควรใช้ percentile แทนค่าเฉลี่ย
- percentile ที่ 50th, 99th, 99.9th, 99.99th แม่นยำและมีประโยชน์กว่าค่าเฉลี่ยในระบบแบบกระจายส่วนใหญ่
- ค่าเฉลี่ยสมมติว่า metric ที่ประเมินมีการกระจายแบบระฆังคว่ำ แต่ metric ที่วิศวกรให้ความสำคัญมีน้อยมากที่เป็นแบบนั้น
- หาก latency ของระบบแบบกระจายไม่ได้กระจายแบบระฆังคว่ำ ค่าเฉลี่ย latency จะนำไปสู่การตัดสินใจและความเข้าใจที่ผิด
- ต้องฝึกความสามารถในการประเมิน capacity
- การรู้ว่าต้องใช้เครื่องกี่เครื่องสำหรับงานหนึ่ง ๆ เป็นตัวแบ่งระหว่างระบบที่อยู่ได้นานกับระบบที่ต้องเปลี่ยนใหม่ใน 3 เดือน
- ณ ปลายปี 2012 เครื่องทั่วไปมีหน่วยความจำ 24GB, OS ใช้ 4–5GB, การประมวลผล request ต้องการอย่างน้อยอีกไม่กี่ GB และ tweet id ใช้ 8 ไบต์ เป็นต้น ต้องมีการคำนวณคร่าว ๆ แบบนี้
- Numbers Everyone Should Know ของ Jeff Dean ช่วยตั้งความคาดหวังได้
- feature flag คือวิธี rollout infrastructure
- feature flag ไม่ได้ทรงพลังเฉพาะกับ A/B test บน frontend แต่ยังใช้กับการเปลี่ยน infrastructure ได้อย่างมาก
- แนวทาง “big cutover” ทำให้หลายโปรเจกต์ล้มเหลว เพราะ bug ที่พบช้าบังคับให้ต้อง rollback
- เมื่อย้ายจาก database เดี่ยวไปยัง service ที่ซ่อน storage ใหม่ไว้ข้างหลัง อาจค่อย ๆ เพิ่มการส่ง write แบบขนานไปยัง service ใหม่, หลัง backfill เสร็จให้ทดลอง read โดยยังไม่ใช้ตอบผู้ใช้, เปรียบเทียบ read ระหว่างระบบเก่ากับใหม่ แล้วค่อย ๆ เพิ่ม read จริงได้
- หากเกิดปัญหา สามารถลดค่า flag หรือปรับกลับเป็น 0 ได้ทันที และสามารถปรับปริมาณ traffic เพื่อ debug และทดลองได้
- feature flag เป็นการแลก complexity เฉพาะที่ในโค้ดกับความเรียบง่ายและ resilience ในภาพรวม
- ต้องยอมรับว่าการมี infrastructure และข้อมูลหลายเวอร์ชันไม่ใช่ข้อยกเว้น แต่เป็นเรื่องปกติ
การออกแบบ data model และ boundary
- พื้นที่ ID กำหนดรูปทรงของระบบ
- ยิ่งต้องใช้ ID หลายตัวเพื่อเข้าถึงข้อมูลหนึ่งชิ้น ก็ยิ่งมีตัวเลือกในการ partition ข้อมูลมากขึ้น
- ยิ่งต้องใช้ ID น้อยตัวสำหรับข้อมูลหนึ่งชิ้น ก็ยิ่งทำให้ consumer ของ output ระบบใช้งานได้ง่ายขึ้น
- Twitter API v1 lookup, create และ delete tweet ด้วย ID ตัวเลข 64-bit เพียงตัวเดียว และ ID นี้ไม่ได้เชื่อมโยงกับข้อมูลอื่น
- เมื่อจำนวน tweet เพิ่มขึ้น การเก็บ tweet ของผู้ใช้คนเดียวกันไว้บนเครื่องเดียวกันจะช่วยสร้าง user tweet timeline และ subscription timeline ได้อย่างมีประสิทธิภาพ แต่ public API ทำให้ทุก tweet ถูก address ได้ด้วย tweet id เท่านั้น จึงจำเป็นต้องมี lookup service ที่รู้ว่า user ใดเป็นเจ้าของ tweet id ใด
- ทางเลือกคือกำหนดให้การ lookup tweet ต้องใช้ user id หรือฝัง user id ไว้ใน tweet id เอง แต่แบบหลังมีต้นทุนคือ tweet id จะไม่เป็นตัวเลขแบบ k-sortable อีกต่อไป
- โครงสร้าง ID อาจส่งผลต่อการ de-anonymization ของข้อมูลส่วนตัว, การ crawling ที่ไม่คาดคิด, ปัญหา auto-increment ID และการโจมตีอย่าง Insecure Direct Object References
- ต้องใช้ประโยชน์จาก data locality
- ยิ่งวางการประมวลผลข้อมูลและ caching ไว้ใกล้ permanent storage มากเท่าไร ประสิทธิภาพการประมวลผลก็ยิ่งสูงขึ้น และรักษา cache consistency กับความเร็วได้ง่ายขึ้น
- เครือข่ายมีความล้มเหลวและ latency มากกว่า pointer dereference หรือ
fread(3) - locality ใช้ได้ทั้งในเชิงพื้นที่และเวลา
- หากผู้ใช้หลายคนส่งคำขอราคาแพงแบบเดียวกันเกือบพร้อมกัน สามารถรวมเป็นคำขอเดียวได้ และหากคำขอข้อมูลชนิดเดียวกันเกิดใกล้กันในเวลา ก็สามารถรวมเป็นคำขอขนาดใหญ่ครั้งเดียวได้
- วิธีนี้ช่วยลด communication overhead และทำให้การจัดการความขัดข้องง่ายขึ้น
- การเขียนข้อมูลที่ cache กลับไปยัง permanent storage เป็นเรื่องไม่ดี
- ข้อบกพร่องแบบนี้อาจพบได้โดยเฉพาะในระบบที่ออกแบบครั้งแรกโดยคนที่มีประสบการณ์ระบบแบบกระจายน้อย
- implementation ที่พูดถึง “Russian-doll caching” มีโอกาสสูงที่จะเจอบั๊กที่เห็นได้ชัด
- อาการที่พบบ่อยคือข้อมูลผู้ใช้ เช่น screenname, email, hashed password กลับไปเป็นค่าเก่า
- คอมพิวเตอร์ทำงานได้มากกว่าที่คิด
- แม้แต่เว็บเซิร์ฟเวอร์ขนาดเบาในปลายปี 2012 ก็มีโปรเซสเซอร์ 6 ตัวขึ้นไป หน่วยความจำ 24GB และพื้นที่ดิสก์ใหญ่มาก
- แม้แต่แอปพลิเคชัน CRUD ที่ค่อนข้างซับซ้อนบน runtime ภาษาโมเดิร์น ก็สามารถประมวลผล request per second หลายพันรายการบนเครื่องเดียวภายในไม่กี่ร้อยมิลลิวินาที
- ในกรณีส่วนใหญ่ เครื่องละหลายร้อย requests per second ไม่ใช่ตัวเลขที่น่าภูมิใจนักในมุมความสามารถด้านปฏิบัติการ
- หาก profile แอปพลิเคชันและปรับปรุงประสิทธิภาพบนฐานของการวัด ก็ไม่ใช่เรื่องยากที่จะได้ performance สูงขึ้น
- ควรใช้ CAP theorem เป็น เครื่องมือวิจารณ์การออกแบบ ไม่ใช่วิธีสร้างระบบ
- CAP theorem กว้างเกินไป และพื้นที่ของโซลูชันที่เป็นไปได้ก็กว้างเกินไป จึงไม่เหมาะจะใช้เป็น first principle เพื่ออนุมานระบบที่ทำงานได้
- หากนำการออกแบบระบบมาตรวจซ้ำ ๆ ว่า CAP กำหนดข้อจำกัดอะไรให้ subsystem ต่าง ๆ จะช่วยไปสู่การออกแบบที่ดีกว่า
- ในบรรดา C, A, P นั้น เลือก CA ไม่ได้
- การแยก service ออกมาช่วยให้ได้ประโยชน์จาก encapsulation และ deployment
- ในที่นี้ service หมายถึงระบบแบบกระจายที่มี logic ระดับสูงกว่า storage system และโดยทั่วไปมี API แบบ request-response
- ควรพิจารณาอยู่เสมอว่าโค้ดบางส่วนจะเปลี่ยนได้ง่ายกว่าหรือไม่ หากอยู่ใน service แยก แทนที่จะอยู่ภายในระบบ
- service ที่แยกออกมาให้ encapsulation คล้าย library แต่ทำให้การ deploy การเปลี่ยนแปลงรวดเร็วและง่ายกว่าการ upgrade library ของ client system
- service ขนาดเล็กมี dependency ด้านโค้ดและปฏิบัติการน้อย และ boundary ที่เข้มงวดช่วยลดทางลัดที่มักยอมให้เกิดใน library
- เมื่อมี client system หลายตัว shared library ต้องประสาน deployment ของ client แต่ละตัว และจะยากขึ้นถ้า data corruption อาจเกิดตามลำดับการ deploy
- หากผู้ดูแล client system ต่างกัน ต้นทุนการประสานงานเชิงสังคมเพื่อให้ยอม upgrade ก็สูงขึ้นเพราะลำดับความสำคัญไม่ตรงกัน
- use case ที่เป็นตัวแทนคือการซ่อน storage layer ที่กำลังเปลี่ยนแปลงไว้หลัง service ที่มี API สะดวกกว่าและมี surface area เล็กกว่า
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
ในบทความนี้ควรพูดถึง CALM (consistency as logical monotonicity) อย่างยิ่ง เพราะเข้าใจง่ายกว่า CAP มาก และเป็นผลลัพธ์ที่พื้นฐานกว่าด้วย อีกทั้งยังช่วยให้คนที่ประสบการณ์น้อยสามารถสร้างระบบกระจายที่แข็งแกร่งมากได้
Idempotency, CRDT, WAL, และ Raft ล้วนเป็นกรณีพิเศษของหลักการ CALM
https://arxiv.org/pdf/1901.01930
มีอีกข้อที่ไม่ได้พูดไว้ตรงนี้ แต่เป็นหลักการทั่วไปที่ชอบมาก นั่นคือ exactly-once delivery เป็นไปไม่ได้
ทำได้แค่ at-most-once หรือ at-least-once เท่านั้น และต้องเลือกว่าจะยอมรับรูปแบบความล้มเหลวแบบไหนแล้วออกแบบให้สอดคล้องกัน
การย้ายข้อมูลจากแถวหนึ่งไปอีกแถวหนึ่งในฐานข้อมูลแบบคลัสเตอร์นั้นทำได้ และอาจตีความได้ว่าเป็นการส่งข้อความสำเร็จ
ถ้าทั้งระบบเป็น idempotent หรือสามารถมองระบบกระจายทั้งหมดเป็นหน่วยเดียวที่ rollback ร่วมกันได้ ก็จะได้ exactly-once delivery กล่าวคือ ต้องไม่มีผลข้างเคียงต่อระบบอื่นที่อยู่นอกโดเมนนั้น
ทั้งสองอย่างนี้เข้าข่าย logical monotonicity ในแบบหนึ่ง Idempotency ดูออกได้ง่ายกว่า ส่วนความเป็น transactional ก็อาศัย monotonicity ผ่านอัลกอริทึมอย่าง WAL และ Raft
บทความนี้ควรพูดถึง CALM (consistency as logical monotonicity) เพราะเข้าใจง่ายกว่า CAP มาก และเป็นผลลัพธ์ที่พื้นฐานกว่า
https://arxiv.org/pdf/1901.01930
An Overview of End-to-End Exactly-Once Processing in Apache Flink (with Apache Kafka, too!) — https://flink.apache.org/2018/02/28/an-overview-of-end-to-en...
Flink's Fault Tolerance Guarantees — https://nightlies.apache.org/flink/flink-docs-release-1.20/d...
อีกข้อสรุปตามมาจาก “ถ้าเอาปัญหาขึ้นไปไว้ในหน่วยความจำได้ มันก็น่าจะเป็นปัญหาเล็กน้อย” คือ ขอบเขตที่เอาขึ้นหน่วยความจำได้ นั้นใหญ่กว่าที่คิดมาก
ผมเคยคิดว่าตัวเองเข้าใจแล้วว่า RAM ขนาดใหญ่คืออะไร แต่พอคลาวด์รายใหญ่เริ่มมี VM ขนาด 12TB สำหรับ SAP HANA ก็ทำให้เปลี่ยนความคิด
ในบทความก็พูดสั้น ๆ เหมือนกันว่า “คอมพิวเตอร์ทำอะไรได้มากกว่าที่คุณคิด” แต่ตรงนั้นยกตัวอย่างแค่เครื่อง 24GB เท่านั้น แม้จะต้องคำนึงว่านั่นคือมาตรฐานปี 2012 แต่ถึงตอนนั้นก็น่าจะมีเครื่องที่ RAM มากกว่านั้น 10 เท่าอยู่ไม่น้อยแล้ว
ผมแชร์เอกสารนี้ให้คนที่ดูมีแววที่สุดในทีมเสมอ
ตอนทำงานที่ Lookout, Jeff Hodges เคยแชร์บทความนี้ในรูปแบบการบรรยาย และปิดท้ายด้วยข้อสรุปต่อยอดว่า “อย่าแสร้งทำเป็นว่าวิศวกรรมไม่เกี่ยวกับการเมือง”
คนที่คิดว่าโค้ดสื่อสารทุกอย่างได้ด้วยตัวมันเอง กำลังมองข้ามแง่มุมสำคัญที่มีผลต่อวิธีสร้างบางสิ่งขึ้นมาและต่อการทำให้เกิดผลลัพธ์จริง
ผ่านมา 10 ปีแล้ว ก็ยังมีไม่มากนักที่เข้าใจจุดตัดระหว่าง engineering leadership กับสิ่งที่เรามักมองว่าเป็นทักษะพื้นฐานของ SRE/DevOps ได้กระชับเท่านี้
นี่คือกระทู้เก่า ๆ
https://news.ycombinator.com/item?id=5055371
346 points|jcdavis|12 years ago|42 comments
https://news.ycombinator.com/item?id=12245909
386 points|kiyanwang|8 years ago|133 comments
เป็นรายการที่ยอดเยี่ยมมาก และชอบที่อธิบายแบบ ใช้ได้จริงและอยู่บนโลกความเป็นจริง ไม่มีคำฮิต และไม่มีคำว่า “microservices”
ผมคิดว่าคำแนะนำจำนวนมากนี้ใช้ได้กับระบบเครื่องเดียวเช่นกัน เพราะอาจมีองค์ประกอบย่อยที่กระจายกันอยู่ในระดับหนึ่งอยู่มาก เช่น IPC ระหว่างโปรแกรม หรือการประสานงานระหว่างเธรดในโปรเซสเดียว
แนวคิดเรื่องหน่วยความจำแบบรวมศูนย์ของเครื่องเดียวนั้นก็เป็นเรื่องไม่จริงอยู่บ้างเหมือนกัน แต่ถึงอย่างนั้นฮาร์ดแวร์ก็ยังให้หลักประกันที่ดีกว่าสภาพแวดล้อมแบบกระจาย “จริง ๆ”
คำแนะนำในบทความหลายข้อที่เปรียบเทียบ “ระบบกระจาย” กับ “เครื่องเดียว” ก็ใช้กับการเปรียบเทียบ หลายเธรดกับเธรดเดียว ได้ค่อนข้างดีเช่นกัน
อีกแกนหนึ่งคือ ถ้าคุณสร้างโปรแกรมแล้วแจกจ่ายให้คนจำนวนมากนำไปรัน สถานการณ์นั้นก็เป็น “distributed” แบบหนึ่งเช่นกัน เพราะจะมีหลายเวอร์ชันอยู่ในโลกจริง และต้องสนใจปัญหาเรื่องความเข้ากันได้และการอัปเกรด ดังนั้น feature flag ที่บทความพูดถึงก็เกี่ยวข้องด้วย
ความเป็นระบบกระจายนั้นใกล้เคียงกับสเปกตรัมที่ต่อเนื่องจาก CPU เดียว ไปสู่หลาย CPU, หลายคอมพิวเตอร์ที่เชื่อมกันแน่น, จนถึงหลายคอมพิวเตอร์ที่กระจายอยู่ทั่วโลก และยังมีอีกหลายจุดกึ่งกลางกับอีกหลายมิติ
หลายโปรเซสบนเครื่องเดียวกันที่คุยกันผ่าน localhost ก็เป็นตัวอย่างระบบกระจายที่มีชื่อเสียงมาก และจริง ๆ แล้วหลายคนก็เริ่มเรียนรู้ระบบกระจายจากตรงนั้น
ถ้าเป็นแบบนั้น ทุกคนคงเขียนโค้ดทั้งหมดเป็น โปรแกรม MPI แบบ distributed memory ที่สวยงามไปแล้ว ทั้ง false sharing ก็จะหายไป และเราก็คงต้องคิดเรื่องรูปแบบการสื่อสารอย่างลึกซึ้งและชัดเจนขึ้น
เมื่อไม่กี่ปีมานี้ผมมีโอกาสได้ร่วมงานกับผู้เขียนบทความนี้ช่วงสั้น ๆ Jeff เป็นหนึ่งในคนที่ มองได้ลึกและมีทัศนคติเชิงบวกที่สุด เท่าที่ผมเคยเรียนรู้ด้วย
เขาตรงไปตรงมาอย่างน่าทึ่งเกี่ยวกับความยากลำบากที่ตัวเองเผชิญ และเข้าถึงได้ง่ายมากในเรื่องการให้คำปรึกษาและคำแนะนำ
ในประโยคที่ว่า “distributed systems are different because they fail often” ประเด็นสำคัญไม่ใช่อัตราการล้มเหลวแบบเดี่ยว ๆ แต่คือ อัตราการล้มเหลวในระบบที่ประกอบด้วยหลายโหนด
และ “ปัญหาแบบ distributed systems” ก็ไม่ได้เกิดเฉพาะในหลายเซิร์ฟเวอร์ที่เชื่อมกันผ่านเครือข่ายเท่านั้น ถ้าเป็นชุดของโหนดที่มีความสัมพันธ์กัน ไม่ว่าจะเป็นไฟล์บนดิสก์ที่เชื่อมโยงกันเชิงตรรกะ หรือบัฟเฟอร์ของอุปกรณ์ I/O คนละตัว ก็อาจเจอปัญหาคล้ายกันได้
คนรุ่นเก่าบางคนชอบหัวเราะกับความซับซ้อนส่วนเกินที่เกิดจากการพยายามบรรเทาปัญหาเหล่านี้ และบ่นว่าแค่รันซอฟต์แวร์บนเซิร์ฟเวอร์ตัวเดียวก็ง่ายกว่ามาก
ในทางปฏิบัติแม้แต่สมัย AS/400 หรือ VAXft คำพูดนั้นก็แทบไม่จริงอยู่แล้ว และยิ่งใช้ไม่ได้เลยในโลก Unix แบบหลายผู้ใช้ หลายโปรเซส ที่วุ่นวายยิ่งกว่า
“ถ้าเอาปัญหาขึ้นไปไว้ในหน่วยความจำได้ มันก็น่าจะเป็นปัญหาเล็กน้อย” เป็นความเข้าใจผิดที่พบบ่อยในหมู่วิศวกระบบกระจายบางส่วน
มันไม่ได้เล็กน้อยเลย เพียงแต่ปัญหาที่ต้องจัดการจะย้ายไปอยู่ในอีกโดเมนหนึ่งที่เป็นส่วนเติมเต็มกันเท่านั้น
ความเข้าใจผิดนี้ทำให้คนลงเอยกับการใช้ คลัสเตอร์ 100 เครื่อง เพื่อทำงานที่ถ้า optimize ให้ดีแล้ว เครื่องเดียว ก็ทำได้