อินเทอร์เน็ตถูกสร้างขึ้นมาได้ดีจนคนส่วนใหญ่มองว่ามันเป็นทรัพยากรธรรมชาติอย่างมหาสมุทรแปซิฟิก มากกว่าที่จะเป็นสิ่งที่มนุษย์สร้างขึ้น เมื่อไหร่ที่คุณเคยเห็นเทคโนโลยีที่มีขนาดใหญ่ขนาดนี้ แล้วไร้ข้อผิดพลาดขนาดนี้มาก่อน?

Alan Kay ในการให้สัมภาษณ์กับ Dr. Dobb’s Journal (2012)

ถ้าคุณกำลังสร้างแอปพลิเคชัน คุณจะต้องขับเคลื่อนด้วยลิสต์ของ requirements สิ่งที่อยู่บนสุดของลิสต์ ส่วนใหญ่คือฟังก์ชันการทำงานที่แอปพลิเคชันต้องนำเสนอ: ต้องมีหน้าจออะไร ปุ่มอะไรบ้าง และแต่ละการดำเนินการควรทำอะไรเพื่อให้บรรลุวัตถุประสงค์ของซอฟต์แวร์ของคุณ สิ่งเหล่านี้คือ functional requirements (ความต้องการเชิงฟังก์ชัน)

นอกจากนี้ คุณอาจมี nonfunctional requirements (ความต้องการที่ไม่ใช่เชิงฟังก์ชัน) ด้วย เช่น แอปควรเร็ว เชื่อถือได้ ปลอดภัย เป็นไปตามกฎหมาย และบำรุงรักษาง่าย requirements เหล่านี้อาจไม่ได้ถูกเขียนไว้อย่างชัดเจน เพราะมันอาจดูเหมือนชัดเจนอยู่แล้ว แต่มันก็สำคัญไม่แพ้ฟังก์ชันการทำงานของแอปเลย แอปที่ช้าอย่างทนไม่ได้หรือไม่น่าเชื่อถือ ก็เหมือนกับว่าไม่มีอยู่จริง

ข้อกำหนดที่ไม่ใช่เชิงฟังก์ชันหลายอย่าง เช่น ความปลอดภัย อยู่นอกขอบเขตของหนังสือเล่มนี้ แต่เราจะพิจารณาบางข้อ และบทนี้จะช่วยให้คุณสามารถกำหนดข้อกำหนดเหล่านี้สำหรับระบบของคุณเองได้ โดยเฉพาะเราจะดูในเรื่องต่อไปนี้:

  • การกำหนดและวัด performance (ประสิทธิภาพ) ของระบบ

  • ความหมายของการที่บริการจะ reliable (เชื่อถือได้) — นั่นคือการทำงานอย่างถูกต้องต่อไป แม้ว่าเมื่อมีสิ่งผิดปกติเกิดขึ้น

  • การทำให้ระบบ scalable (ปรับขนาดได้) โดยการมีวิธีที่มีประสิทธิภาพในการเพิ่มขีดความสามารถในการคำนวณเมื่อโหลดของระบบเพิ่มขึ้น

  • การทำให้ระบบบำรุงรักษาง่ายขึ้นในระยะยาว

คำศัพท์ที่นำเสนอในบทนี้จะมีประโยชน์ในบทต่อ ๆ ไปเช่นกัน เมื่อเราลงลึกในรายละเอียดของวิธีการที่ระบบที่ต้องจัดการข้อมูลจำนวนมากถูกนำไปใช้งาน อย่างไรก็ตาม นิยามเชิงนามธรรมอาจจะค่อนข้างแห้งแล้ง เพื่อให้แนวคิดเป็นรูปธรรมมากขึ้น เราจะเริ่มบทนี้ด้วยกรณีศึกษาของบริการ social network ซึ่งจะให้ตัวอย่างเชิงปฏิบัติของ performance และ scalability

Case Study: Social Network Home Timelines (กรณีศึกษา: Home Timelines ของ Social Network)

ลองนึกภาพว่าเราได้รับมอบหมายให้สร้าง social network ในรูปแบบของ X (เดิมคือ Twitter) ที่ผู้ใช้สามารถโพสต์ข้อความและติดตามผู้ใช้คนอื่น ๆ ได้ นี่จะเป็นการลดทอนความซับซ้อนลงอย่างมาก เมื่อเทียบกับวิธีการที่บริการดังกล่าวทำงานจริง [ 1 , 2 , 3 ] แต่มันจะช่วยให้เห็นภาพปัญหาบางอย่างที่เกิดขึ้นในระบบขนาดใหญ่

สมมติว่าผู้ใช้ทั้งหมดโพสต์ 500 ล้านโพสต์ต่อวัน หรือเฉลี่ย 5,800 โพสต์ต่อวินาที ในบางครั้ง อัตรานี้อาจพุ่งสูงถึง 150,000 โพสต์ต่อวินาที [ 4 ] สมมติเพิ่มเติมว่าผู้ใช้ทั่วไปติดตาม 200 คนและมีผู้ติดตาม 200 คน (แม้ว่าจะมีช่วงที่กว้างมาก: คนส่วนใหญ่มีผู้ติดตามเพียงไม่กี่คน และคนดังบางคน เช่น Barack Obama มีผู้ติดตามมากกว่า 100 ล้านคน)

Representing Users, Posts, and Follows (การแทนข้อมูลผู้ใช้ โพสต์ และการติดตาม)

เราเก็บข้อมูลทั้งหมดไว้ใน relational database ดังที่แสดงใน Figure 2-1 เรามีตารางสำหรับผู้ใช้หนึ่งตาราง ตารางสำหรับโพสต์หนึ่งตาราง และตารางสำหรับความสัมพันธ์ของการติดตามหนึ่งตาราง

Diagram illustrating a simple relational schema for a social network, showing tables for users, posts, and follow relationships with sample data.

Figure 2-1. A simple relational schema for a social network in which users can follow one another (โครงสร้าง relational อย่างง่ายสำหรับโซเชียลเน็ตเวิร์กที่ผู้ใช้สามารถติดตามกันได้)

สมมติว่าการอ่านข้อมูลหลักที่ social network ของเราต้องรองรับคือ home timeline ซึ่งแสดงโพสต์ล่าสุดจากคนที่ผู้ใช้กำลังติดตาม (เพื่อความง่ายเราจะไม่นับโฆษณา โพสต์แนะนำจากคนที่ไม่ได้ติดตาม และส่วนขยายอื่น ๆ) เราสามารถเขียน SQL query ต่อไปนี้เพื่อดึง home timeline สำหรับผู้ใช้คนหนึ่ง ๆ:

SELECT posts.*, users.* FROM posts
  JOIN follows ON posts.sender_id = follows.followee_id
  JOIN users   ON posts.sender_id = users.id
  WHERE follows.follower_id = current_user
  ORDER BY posts.timestamp DESC
  LIMIT 1000

ในการ execute query นี้ database จะใช้ตาราง follows เพื่อหาทุกคนที่ current_user กำลังติดตามอยู่ จากนั้นค้นหาโพสต์ล่าสุดจากผู้ใช้เหล่านั้น และเรียงลำดับตาม timestamp เพื่อให้ได้ 1,000 โพสต์ล่าสุดจากผู้ใช้ที่ติดตามทั้งหมด

โพสต์ควรจะมีความทันสมัย ดังนั้นสมมติว่าหลังจากมีคนโพสต์ข้อความ เราต้องการให้ผู้ติดตามของพวกเขา สามารถเห็นโพสต์นั้นภายในห้าวินาที วิธีหนึ่งคือให้ client ของผู้ใช้ทำ query ซ้ำทุก ๆ ห้าวินาที ในขณะที่ผู้ใช้ออนไลน์อยู่ (วิธีนี้เรียกว่า polling ) ถ้าเราสมมติว่ามีผู้ใช้ 10 ล้านคนที่ออนไลน์และล็อกอินพร้อมกัน นั่นหมายความว่าเราจะต้องรัน query 2 ล้านครั้งต่อวินาที ถึงแม้เราจะ poll ถี่น้อยลง มันก็ยังเยอะมากอยู่ดี

query นี้ยังมีต้นทุนค่อนข้างสูง: ถ้าผู้ใช้กำลังติดตาม 200 คน query จะต้องดึงลิสต์ของโพสต์ล่าสุด จากแต่ละคนใน 200 คนนั้นและ merge ลิสต์เหล่านั้น สองล้าน timeline queries ต่อวินาที คูณด้วย 200 บัญชีที่ติดตาม ทำให้ต้องมีการ lookup 400 ล้านครั้งต่อวินาที ซึ่งเป็นตัวเลขที่มหาศาล และนั่นคือกรณีทั่วไป ผู้ใช้บางคนติดตามเป็นหมื่นเป็นพันบัญชี; สำหรับพวกเขาแล้ว query นี้มีค่าใช้จ่ายสูงมาก และยากที่จะทำให้เร็ว

Materializing and Updating Timelines (การ Materialize และอัปเดต Timelines)

เราจะทำได้ดีกว่านี้ได้อย่างไร? ประการแรก แทนที่จะใช้ polling ควรจะดีกว่าถ้า server push โพสต์ใหม่ไปยังผู้ติดตามที่กำลังออนไลน์อยู่โดยอัตโนมัติ ประการที่สอง เราควร precompute ผลลัพธ์ของ query เพื่อให้คำขอ home timeline ของผู้ใช้สามารถให้บริการจาก cache ได้

ลองนึกภาพว่าสำหรับผู้ใช้แต่ละคน เราจัดเก็บ data structure ที่มี home timeline ของพวกเขา (นั่นคือ โพสต์ล่าสุดจากคนที่พวกเขากำลังติดตาม) ทุกครั้งที่มีผู้ใช้โพสต์ข้อความ เราจะค้นหาผู้ติดตามทั้งหมดของพวกเขาและแทรกโพสต์นั้นลงใน home timeline ของผู้ติดตามแต่ละคน เหมือนกับการส่งข้อความไปยังกล่องจดหมาย เมื่อผู้ใช้ล็อกอิน เราก็สามารถให้ home timeline ที่ precompute ไว้แล้วนี้แก่พวกเขาได้ ยิ่งไปกว่านั้น เพื่อรับการแจ้งเตือนเกี่ยวกับโพสต์ใหม่บน timeline client ของผู้ใช้เพียงแค่ต้อง subscribe ไปยังสตรีมของโพสต์ที่กำลังถูกเพิ่มลงใน home timeline ของพวกเขา

ข้อเสียของแนวทางนี้คือตอนนี้เราต้องทำงานมากขึ้นทุกครั้งที่มีผู้ใช้โพสต์ข้อความ เพราะ home timelines เป็น derived data ที่ต้องถูกอัปเดต กระบวนการนี้แสดงใน Figure 2-2 เมื่อ request เริ่มต้นหนึ่งครั้งส่งผลให้เกิด request ปลายทางหลายรายการ เราจะใช้คำว่า fan-out เพื่ออธิบายปัจจัยที่จำนวน request เพิ่มขึ้น

Diagram showing the fan-out process, illustrating how a user's post is distributed to each follower's home timeline, demonstrating derived data updating in a messaging system.

Figure 2-2. Fan-out: delivering new posts to every follower of the user who made the post (Fan-out: การส่งโพสต์ใหม่ไปยังผู้ติดตามทุกคนของผู้ใช้ที่โพสต์)

ที่อัตรา 5,800 โพสต์ต่อวินาที ถ้าโพสต์ทั่วไปเข้าถึงผู้ติดตาม 200 คน (นั่นคือ fan-out factor เท่ากับ 200) เราจะต้องเขียน home timeline มากกว่า 1 ล้านครั้งต่อวินาที นี่เป็นจำนวนที่มาก แต่ก็ยังเป็นการประหยัดอย่างมีนัยสำคัญเมื่อเทียบกับการ lookup โพสต์ 400 ล้านครั้ง ต่อวินาทีที่เราจะต้องทำหากไม่ใช้วิธีนี้

ถ้าอัตราการโพสต์พุ่งสูงขึ้นเนื่องจากเหตุการณ์พิเศษ เราไม่จำเป็นต้องทำการส่ง timeline ทันที เราสามารถใส่ไว้ใน queue และยอมรับว่ามันอาจใช้เวลานานขึ้นเล็กน้อยกว่าโพสต์จะปรากฏบน timeline ของผู้ติดตาม แม้ในช่วงที่มี load spike เช่นนี้ timelines ก็ยังคงโหลดได้เร็ว เพราะเราเพียงแค่ให้บริการจาก cache

กระบวนการ precompute และอัปเดตผลลัพธ์ของ query นี้เรียกว่า materialization และ timeline cache เป็นตัวอย่างของ materialized view (แนวคิดที่เราจะพูดถึงเพิ่มเติมในบทต่อ ๆ ไป) Materialized view ทำให้การอ่านเร็วขึ้น แต่แลกมาด้วยการที่เราต้องทำงานมากขึ้นในการเขียน ต้นทุนของการเขียนสำหรับผู้ใช้ส่วนใหญ่นั้นไม่มากนัก แต่ social network ก็ยังต้องพิจารณากรณีที่รุนแรงบางกรณี:

  • ถ้าผู้ใช้ติดตามบัญชีจำนวนมาก และบัญชีเหล่านั้นโพสต์เยอะ ผู้ใช้คนนั้นจะมีการเขียน ไปยัง materialized timeline ในอัตราที่สูง อย่างไรก็ตาม ผู้ใช้นั้นมักจะไม่อ่านโพสต์ทั้งหมดใน timeline ดังนั้นการปล่อยให้การเขียน timeline บางส่วนหายไปและแสดงเฉพาะตัวอย่างโพสต์จากบัญชีที่พวกเขาติดตามก็ถือว่าไม่เป็นไร [ 5 ]

  • เมื่อบัญชีคนดังที่มีผู้ติดตามจำนวนมากโพสต์ข้อความ เราจะต้องทำงานจำนวนมาก เพื่อแทรกโพสต์นั้นลงใน home timeline ของผู้ติดตามแต่ละคนนับล้าน ในกรณีนี้ การปล่อยให้การเขียนบางส่วนหายไปนั้นไม่ใช่เรื่องที่ทำได้ วิธีหนึ่งในการแก้ปัญหานี้คือจัดการโพสต์ของคนดัง แยกจากโพสต์ของคนอื่น ๆ: เราสามารถประหยัดความพยายามในการเพิ่มโพสต์ของคนดังลงใน timeline นับล้าน ๆ โดยการเก็บมันแยกต่างหากและ merge กับ materialized timeline เมื่อมีการอ่าน แม้จะมีการปรับแต่งเช่นนี้ การจัดการกับคนดังบน social network ก็ยังต้องการ โครงสร้างพื้นฐานจำนวนมาก [ 6 ]

Describing Performance (การอธิบายประสิทธิภาพ)

การพูดถึง performance ของซอฟต์แวร์ส่วนใหญ่มักพิจารณา metric หลักสองประเภท:

Response time (เวลาตอบสนอง)

เวลาที่ผ่านไปตั้งแต่ผู้ใช้ส่ง request จนกระทั่งได้รับคำตอบกลับ หน่วยวัดเป็นวินาที (หรือมิลลิวินาที หรือไมโครวินาที)

Throughput (ปริมาณงาน)

จำนวน request ต่อวินาที หรือปริมาณข้อมูลต่อวินาที ที่ระบบกำลังประมวลผล สำหรับการจัดสรรทรัพยากรฮาร์ดแวร์ที่กำหนด จะมี maximum throughput (ปริมาณงานสูงสุด) ที่สามารถจัดการได้ หน่วยวัดคือ "สิ่งของต่อวินาที"

ในกรณีศึกษา social network "posts ต่อวินาที" และ "timeline writes ต่อวินาที" เป็น throughput metrics ในขณะที่ "เวลาที่ใช้ในการโหลด home timeline" และ "เวลากว่าโพสต์จะถึงมือผู้ติดตาม" เป็น response time metrics

Throughput และ response time มักมีความสัมพันธ์กัน ตัวอย่างของความสัมพันธ์ดังกล่าวสำหรับ online service แสดงใน Figure 2-3 บริการจะมี response time ต่ำเมื่อ throughput ของ request ต่ำ แต่ response time จะเพิ่มขึ้น เมื่อ load เพิ่มขึ้น ทั้งนี้เป็นเพราะ queueing (การเข้าคิว): เมื่อ request มาถึงระบบที่มี load สูง CPU มักจะกำลังประมวลผล request ก่อนหน้าอยู่ ดังนั้น request ที่เข้ามาจึงต้องรอจนกว่า request ก่อนหน้าจะเสร็จสมบูรณ์ เมื่อ throughput เข้าใกล้ขีดสูงสุดที่ฮาร์ดแวร์สามารถจัดการได้ ความล่าช้าจาก queueing จะเพิ่มขึ้นอย่างรวดเร็ว

A graph illustrates the relationship between throughput and response time, showing that response time increases sharply as throughput nears the hardware's capacity due to queueing.

Figure 2-3. As the throughput of a service approaches its capacity, the response time increases dramatically because of queueing. (เมื่อ throughput ของบริการเข้าใกล้ขีดความสามารถของมัน response time จะเพิ่มขึ้นอย่างมากเนื่องจาก queueing)

When an Overloaded System Won’t Recover (เมื่อระบบที่โอเวอร์โหลดไม่สามารถฟื้นตัว)

ถ้าระบบใกล้จะโอเวอร์โหลด โดยที่ throughput ถูกดันเข้าใกล้ขีดจำกัด บางครั้งมันอาจเข้าสู่วัฏจักร vicious cycle ที่ทำให้มันมีประสิทธิภาพน้อยลงและยิ่งโอเวอร์โหลดมากขึ้น ตัวอย่างเช่น ถ้ามี request รออยู่ในคิวยาว response times อาจเพิ่มขึ้นมากจน client timeout และส่ง request ซ้ำอีก ทำให้อัตรา request เพิ่มขึ้นยิ่งไปอีก ทำให้ปัญหาแย่ลง ซึ่งเรียกว่า retry storm (พายุการส่งซ้ำ) แม้เมื่อ load ลดลงแล้ว ระบบเช่นนี้อาจยังคงอยู่ในสถานะโอเวอร์โหลด จนกว่าจะถูกรีบูตหรือรีเซ็ตด้วยวิธีอื่น ปรากฏการณ์นี้เรียกว่า metastable failure (ความล้มเหลวแบบ metastable) และมันสามารถทำให้เกิดการหยุดให้บริการที่ร้ายแรงในระบบ production [ 7 , 8 , 9 ]

เพื่อหลีกเลี่ยงการที่ retries ทำให้บริการโอเวอร์โหลด คุณสามารถเพิ่มและสุ่มระยะเวลาระหว่างการ retry ในฝั่ง client ( exponential backoff [ 10 , 11 ]) และหยุดส่ง request ไปยังบริการที่เพิ่งส่ง error กลับมา หรือ timeout ชั่วคราว (โดยใช้ circuit breaker [ 12 , 13 ] หรือ token bucket algorithm [ 14 ]) server ยังสามารถตรวจจับเมื่อมันเข้าใกล้การโอเวอร์โหลดและเริ่มปฏิเสธ request ล่วงหน้า ( load shedding [ 15 ]) หรือส่งคำตอบกลับไปขอให้ client ชะลอความเร็วลง ( backpressure [ 1 , 16 ]) การเลือก queueing และ load balancing algorithms ก็สามารถสร้างความแตกต่างได้เช่นกัน [ 17 ]

ในแง่ของ performance metrics response time มักเป็นสิ่งที่ผู้ใช้ใส่ใจมากที่สุด ในขณะที่ throughput เป็นตัวกำหนดทรัพยากรการคำนวณที่ต้องการ (เช่น ต้องใช้ server กี่เครื่อง) และดังนั้นจึงเป็นต้นทุนในการให้บริการ workload หนึ่ง ๆ ถ้า throughput มีแนวโน้มจะเพิ่มขึ้นเกินความสามารถของฮาร์ดแวร์ปัจจุบัน ความจุก็จำเป็นต้องขยาย; ระบบจะถูกเรียกว่า scalable (ปรับขนาดได้) ถ้า throughput สูงสุดของมันสามารถเพิ่มขึ้นอย่างมีนัยสำคัญโดยการเพิ่มทรัพยากรการคำนวณ

ในส่วนนี้เราจะมุ่งเน้นไปที่ response times เป็นหลัก และเราจะกลับมาที่ throughput และ scalability ใน "Scalability" อีกครั้ง

Latency and Response Time (Latency และ Response Time)

"Latency" และ "response time" บางครั้งถูกใช้แทนกันได้ แต่ในหนังสือเล่มนี้เราจะใช้คำเหล่านี้ และคำที่เกี่ยวข้องอีกสองสามคำในลักษณะที่เฉพาะเจาะจง (แสดงใน Figure 2-4 ):

  • Response time (เวลาตอบสนอง) คือสิ่งที่ client เห็น; มันรวมความล่าช้าทั้งหมดที่เกิดขึ้นในระบบ

  • Service time (เวลาบริการ) คือระยะเวลาที่บริการกำลังประมวลผล request ของ client อย่างแข็งขัน

  • Queueing delays (ความล่าช้าจากการเข้าคิว) สามารถเกิดขึ้นได้หลายจุดใน flow — ตัวอย่างเช่น หลังจากได้รับ request แล้ว มันอาจต้องรอจนกว่า CPU จะว่างก่อนที่จะประมวลผลได้ หรือแพ็กเก็ตตอบรับอาจต้องถูก buffer ก่อนที่จะส่งผ่านเครือข่าย ถ้างานอื่นบนเครื่องเดียวกันกำลังส่งข้อมูลจำนวนมากผ่าน network interface ขาออก

  • Latency คือคำที่ใช้ครอบคลุมเวลาที่ request ไม่ได้ถูกประมวลผลอย่างแข็งขัน — นั่นคือช่วงเวลาที่มัน latent (แฝงอยู่) โดยเฉพาะอย่างยิ่ง network latency หรือ network delay (ความหน่วงของเครือข่าย) หมายถึงเวลาที่ request และ response ใช้ในการเดินทางผ่านเครือข่าย

Diagram illustrating the components of response time, showing user and service interaction with network latency, queueing delay, and service time.

Figure 2-4. Response time, service time, network latency, and queueing delay (Response time, service time, network latency, และ queueing delay)

ใน Figure 2-4 เวลาจะไหลจากซ้ายไปขวา; แต่ละ node ที่สื่อสารถูกแสดงเป็นเส้นแนวนอน และ request หรือ response message ถูกแสดงเป็นลูกศรทแยงหนาจาก node หนึ่งไปยังอีก node หนึ่ง คุณจะพบกับไดอะแกรมสไตล์นี้บ่อยครั้งตลอดทั้งเล่มนี้

Response time สามารถแปรผันอย่างมีนัยสำคัญจาก request หนึ่งไปยังอีก request หนึ่ง แม้ว่าคุณจะส่ง request เดิมซ้ำแล้วซ้ำเล่าก็ตาม มีหลายปัจจัยที่สามารถเพิ่มความล่าช้าแบบสุ่ม — ตัวอย่างเช่น context switch ไปยัง background process, การสูญเสีย network packet และ TCP retransmission, การหยุดเพื่อ garbage collection, page fault ที่บังคับให้อ่านจากดิสก์ หรือการสั่นสะเทือนทางกลใน server rack [ 18 ] เราจะพูดถึงหัวข้อนี้ในรายละเอียดเพิ่มเติมใน "Timeouts and Unbounded Delays"

Queueing delays มักเป็นสาเหตุส่วนใหญ่ของความแปรปรวนใน response times เนื่องจาก server สามารถประมวลผลสิ่งต่าง ๆ แบบขนานได้ในจำนวนจำกัด (เช่น จำกัดด้วยจำนวน CPU cores) จึงต้องใช้ request ที่ช้าเพียงไม่กี่รายการ เพื่อชะลอการประมวลผลของ request ที่ตามมา — ผลกระทบนี้เรียกว่า head-of-line blocking (การปิดกั้นหัวแถว) แม้ว่า request ที่ตามมาจะมี service time ที่รวดเร็ว client จะยังคงเห็น response time โดยรวมที่ช้าเนื่องจากเวลาที่รอให้ request ก่อนหน้าเสร็จสมบูรณ์ Queueing delay ไม่ใช่ส่วนหนึ่งของ service time และด้วยเหตุนี้ การวัด response times ในฝั่ง client จึงเป็นสิ่งสำคัญ

Average, Median, and Percentiles (ค่าเฉลี่ย มัธยฐาน และ Percentiles)

เนื่องจาก response time แปรผันจาก request หนึ่งไปยังอีก request หนึ่ง เราจึงต้องมองมันไม่ใช่เป็นตัวเลขเดี่ยว แต่เป็น distribution (การแจกแจง) ของค่าที่เราสามารถวัดได้ ใน Figure 2-5 แต่ละแท่งสีเทาแทน request ไปยังบริการหนึ่ง และความสูงของมันแสดงว่า request นั้นใช้เวลานานเท่าใด request ส่วนใหญ่ค่อนข้างเร็ว แต่บางครั้ง outliers (ค่าผิดปกติ) ใช้เวลานานกว่ามาก ความแปรผันของ network delay ยังรู้จักกันในชื่อ jitter

Chart depicting response times of 100 service requests, highlighting mean, median, and 95th and 99th percentiles to illustrate distribution and outliers.

Figure 2-5. Illustrating mean and percentiles: response times for a sample of 100 requests to a service (การแสดงค่าเฉลี่ยและ percentiles: response times สำหรับตัวอย่าง 100 request ไปยังบริการ)

เป็นเรื่องปกติที่จะรายงาน average (ค่าเฉลี่ย) response time ของบริการ (ในทางเทคนิคคือ arithmetic mean ซึ่งหาได้จากการรวม response times ทั้งหมดแล้วหารด้วยจำนวน request) ค่าเฉลี่ยของ response time มีประโยชน์สำหรับการประมาณขีดจำกัดของ throughput [ 19 ] อย่างไรก็ตาม ค่าเฉลี่ยไม่ใช่ metric ที่ดีนักถ้าคุณต้องการรู้ "ค่าทั่วไป" ของ response time เพราะมันไม่ได้บอกคุณว่ามีผู้ใช้กี่คนที่ประสบกับความล่าช้านั้น

โดยปกติแล้วจะดีกว่าถ้าใช้ percentiles ถ้าคุณนำลิสต์ของ response times มาเรียงจากเร็วที่สุดไปช้าที่สุด median (มัธยฐาน) คือจุดกึ่งกลาง — ตัวอย่างเช่น ถ้า median response time คือ 200 ms นั่นหมายถึงครึ่งหนึ่งของ request ของคุณส่งคืนผลลัพธ์ในเวลาน้อยกว่า 200 มิลลิวินาที และอีกครึ่งหนึ่งใช้เวลานานกว่า ทำให้ median เป็น metric ที่ดีถ้าคุณต้องการรู้ว่าโดยทั่วไป ผู้ใช้ต้องรอนานแค่ไหน Median ยังรู้จักกันในชื่อ 50th percentile ซึ่งบางครั้งย่อว่า p50

เพื่อดูว่า outliers ของคุณแย่แค่ไหน คุณสามารถดู percentiles ที่สูงขึ้น: 95th , 99th และ 99.9th percentiles เป็นที่นิยมใช้ (ย่อว่า p95 , p99 และ p999 ) ตัวอย่างเช่น ถ้า 95th percentile response time คือ 1.5 วินาที นั่นหมายถึง 95 ใน 100 requests ใช้เวลาน้อยกว่า 1.5 วินาที และ 5 ใน 100 requests ใช้เวลา 1.5 วินาทีหรือมากกว่า ดังแสดงใน Figure 2-5

High response-time percentiles หรือที่รู้จักกันในชื่อ tail latencies (ความหน่วงส่วนท้าย) มีความสำคัญเนื่องจากมันส่งผลโดยตรงต่อประสบการณ์ของผู้ใช้ที่มีต่อบริการ ตัวอย่างเช่น Amazon อธิบายข้อกำหนด response time สำหรับ internal services ในแง่ของ 99.9th percentile แม้ว่าสิ่งนี้จะมีผลกับเพียง 1 ใน 1,000 requests เท่านั้น ทั้งนี้เพราะลูกค้าที่มี request ช้าที่สุดมักเป็นผู้ที่มีข้อมูลในบัญชีมากที่สุด เนื่องจากพวกเขาซื้อของจำนวนมาก — นั่นคือ พวกเขาเป็นลูกค้าที่มีค่าที่สุด [ 20 ] การทำให้ลูกค้าเหล่านั้นพอใจโดยทำให้เว็บไซต์เร็วสำหรับพวกเขาจึงเป็นสิ่งสำคัญ

การปรับแต่ง 99.99th percentile (request ที่ช้าที่สุด 1 ใน 10,000) ถือว่าแพงเกินไป และไม่ให้ประโยชน์เพียงพอสำหรับวัตถุประสงค์ของ Amazon การลด response times ที่ percentiles สูงมากเป็นเรื่องยากเนื่องจากมันได้รับผลกระทบจากเหตุการณ์สุ่มที่อยู่นอกเหนือการควบคุมของคุณได้ง่าย และประโยชน์ก็ลดน้อยลง

The User Impact of Response Times (ผลกระทบของ Response Times ต่อผู้ใช้)

ดูเหมือนชัดเจนว่าบริการที่เร็วนั้นดีกว่าบริการที่ช้าสำหรับผู้ใช้ [ 21 ] อย่างไรก็ตาม มันยากอย่างน่าประหลาดใจที่จะหาข้อมูลที่เชื่อถือได้ เพื่อวัดปริมาณผลกระทบที่ latency มีต่อพฤติกรรมของผู้ใช้

สถิติที่ถูกอ้างถึงบ่อย ๆ บางส่วนไม่น่าเชื่อถือ ตัวอย่างเช่น ในปี 2006 Google รายงานว่า การชะลอผลการค้นหาจาก 400 ms เป็น 900 ms สัมพันธ์กับการลดลง 20% ของ traffic และรายได้ [ 22 ] อย่างไรก็ตาม การศึกษาอื่นของ Google ในปี 2009 รายงานว่า การเพิ่ม latency 400 ms ส่งผลให้จำนวนการค้นหาต่อวันลดลงเพียง 0.6% [ 23 ] และในปีเดียวกัน Bing พบว่าการเพิ่มเวลาโหลดสองวินาที ลดรายได้จากโฆษณาลง 4.3% [ 24 ] ข้อมูลใหม่กว่าจากบริษัทเหล่านี้ดูเหมือนจะไม่เปิดเผยต่อสาธารณะ

การศึกษาล่าสุดของ Akamai อ้างว่าการเพิ่ม response time 100 ms ลด conversion rate ของเว็บไซต์ ecommerce ได้ถึง 7% [ 25 ] อย่างไรก็ตาม เมื่อดูอย่างละเอียด การศึกษาเดียวกันนี้เผยให้เห็นว่า หน้าเว็บที่โหลด เร็วมาก ก็สัมพันธ์กับ conversion rate ที่ต่ำกว่าเช่นกัน! ผลลัพธ์ที่ดูเหมือนขัดแย้งนี้อธิบายได้จากข้อเท็จจริงที่ว่าหน้าเว็บที่โหลดเร็วที่สุด มักเป็นหน้าที่ไม่มีเนื้อหาที่มีประโยชน์ (เช่น หน้า error 404) แต่เนื่องจากการศึกษาไม่ได้แยกผลกระทบของเนื้อหาหน้าเว็บออกจากผลกระทบของเวลาโหลด ผลลัพธ์ของมันจึงอาจไม่มีความหมาย

การศึกษาโดย Yahoo ที่ดำเนินการในปีถัดมาเปรียบเทียบ click-through rates บนผลการค้นหาที่โหลดเร็วกับที่โหลดช้า โดยควบคุมคุณภาพของผลการค้นหาให้เท่ากัน [ 26 ] รายงานว่ามีคลิกมากกว่า 20%–30% บนผลการค้นหาที่เร็ว เมื่อความแตกต่างระหว่างคำตอบเร็วและช้าคือ 1.25 วินาทีหรือมากกว่า

Use of Response Time Metrics (การใช้ Response Time Metrics)

High percentiles มีความสำคัญอย่างยิ่งใน backend services ที่ถูกเรียกหลายครั้ง เพื่อให้บริการ end-user request เดียว แม้ว่าคุณจะเรียกแบบขนาน request ก็ยังคงต้องรอ ให้การเรียกที่ช้าที่สุดในบรรดาการเรียกแบบขนานเสร็จสมบูรณ์ ต้องใช้การเรียกที่ช้าเพียงครั้งเดียวเพื่อทำให้ end-user request ทั้งหมดช้าลง ดังแสดงใน Figure 2-6 แม้ว่ามีเพียงเปอร์เซ็นต์เล็กน้อยของการเรียก backend ที่ช้า โอกาสที่จะได้การเรียกที่ช้าก็เพิ่มขึ้นถ้า end-user request ต้องใช้การเรียก backend หลายครั้ง ดังนั้นสัดส่วนที่สูงขึ้นของ end-user requests ดังกล่าวจึงกลายเป็นช้า (ผลกระทบที่เรียกว่า tail latency amplification (การขยายความหน่วงส่วนท้าย) [ 27 ])

Diagram illustrating how a single slow backend request can delay an entire end-user request, showing various backend response times with one significantly larger delay.

Figure 2-6. When several backend calls are needed to serve a request, just a single slow call can slow down the entire end-user request. (เมื่อต้องใช้ backend calls หลายครั้งในการให้บริการ request การเรียกที่ช้าเพียงครั้งเดียวก็สามารถทำให้ end-user request ทั้งหมดช้าลง)

Percentiles มักถูก ใช้ใน service level objectives (SLOs) และ service level agreements (SLAs) เพื่อกำหนดประสิทธิภาพและความพร้อมให้บริการที่คาดหวังของบริการ [ 28 ] ตัวอย่างเช่น SLO อาจตั้งเป้าหมายให้บริการมี median response time ต่ำกว่า 200 ms และ 99th percentile ต่ำกว่า 1 วินาที และเป้าหมายที่อย่างน้อย 99.9% ของ requests ที่ถูกต้อง ส่งผลให้ได้ response ที่ไม่มีข้อผิดพลาด SLA คือสัญญาที่ระบุว่าจะเกิดอะไรขึ้น ถ้า SLO ไม่เป็นไปตามที่กำหนด (เช่น ลูกค้าอาจมีสิทธิ์ได้รับเงินคืน) อย่างน้อยนั่นคือแนวคิดพื้นฐาน; ในทางปฏิบัติ การกำหนด availability metrics ที่ดีสำหรับ SLOs และ SLAs นั้นไม่ตรงไปตรงมา [ 29 , 30 ]

Computing Percentiles (การคำนวณ Percentiles)

ถ้า คุณต้องการเพิ่ม response time percentiles ใน monitoring dashboards สำหรับบริการของคุณ คุณต้องคำนวณมันอย่างมีประสิทธิภาพอย่างต่อเนื่อง ตัวอย่างเช่น คุณอาจต้องการเก็บ rolling window ของ response times สำหรับ requests ใน 10 นาทีที่ผ่านมา ทุกนาที คุณคำนวณ median และ percentiles ต่าง ๆ จากค่าใน window นั้น และพล็อต metrics เหล่านั้นบนกราฟ

การ implement ที่ง่ายที่สุดคือเก็บลิสต์ของ response times สำหรับ requests ทั้งหมดภายใน time window และเรียงลำดับลิสต์นั้นทุกนาที ถ้ามันไม่มีประสิทธิภาพพอสำหรับคุณ มี algorithms ที่สามารถคำนวณค่าประมาณที่ดีของ percentiles โดยใช้ CPU และหน่วยความจำน้อยที่สุด Open source percentile estimation libraries ได้แก่ HdrHistogram [ 31 ], t-digest [ 32 , 33 ], OpenHistogram [ 34 ], และ DDSketch [ 35 ]

ระวังว่าการเฉลี่ย percentiles (เช่น เพื่อลดความละเอียดของเวลาหรือรวมข้อมูลจากหลายเครื่อง) นั้นไร้ความหมายทางคณิตศาสตร์ วิธีที่ถูกต้องในการรวมข้อมูล response time คือการเพิ่ม histograms [ 36 ]

Reliability and Fault Tolerance (ความน่าเชื่อถือและการทนทานต่อข้อผิดพลาด)

ทุกคนมีความเข้าใจโดยสัญชาตญาณว่าสิ่งที่เชื่อถือได้หรือไม่เชื่อถือได้หมายถึงอะไร สำหรับซอฟต์แวร์ ความคาดหวังทั่วไปมีดังนี้:

  • แอปพลิเคชันทำงานตามฟังก์ชันที่ผู้ใช้คาดหวัง

  • แอปพลิเคชันสามารถทนต่อการที่ผู้ใช้ทำผิดพลาดหรือใช้ซอฟต์แวร์ในวิธีที่ไม่คาดคิด

  • ประสิทธิภาพของมันดีพอสำหรับกรณีการใช้งานที่ต้องการ ภายใต้ load และปริมาณข้อมูลที่คาดไว้

  • ระบบป้องกันการเข้าถึงโดยไม่ได้รับอนุญาตและการใช้งานในทางที่ผิด

ถ้าสิ่งเหล่านั้นทั้งหมดรวมกันหมายถึง "ทำงานได้อย่างถูกต้อง" เราก็สามารถเข้าใจ reliability (ความน่าเชื่อถือ) ว่าโดยคร่าว ๆ คือ "การทำงานได้อย่างถูกต้องต่อไปแม้เมื่อมีสิ่งผิดปกติเกิดขึ้น" เพื่อให้แม่นยำยิ่งขึ้นเกี่ยวกับสิ่งที่ผิดพลาด เราจะแยกความแตกต่างระหว่าง faults และ failures [ 37 , 38 , 39 ]:

Fault (ข้อบกพร่อง)

Fault เกิดขึ้นเมื่อ ส่วนหนึ่ง ของระบบหยุดทำงานได้อย่างถูกต้อง — ตัวอย่างเช่น ถ้าฮาร์ดไดรฟ์ตัวเดียวทำงานผิดปกติ หรือเครื่องเดียว crash หรือบริการภายนอก (ที่ระบบพึ่งพา) หยุดให้บริการ

Failure (ความล้มเหลว)

Failure เกิดขึ้นเมื่อระบบ โดยรวม หยุดให้บริการตามที่ต้องการแก่ผู้ใช้ — กล่าวอีกนัยหนึ่งคือเมื่อมันไม่เป็นไปตาม SLO

ความแตกต่างระหว่าง faults และ failures อาจทำให้สับสนเพราะมันเป็นสิ่งเดียวกัน เพียงแต่อยู่ในระดับที่แตกต่างกัน ตัวอย่างเช่น ถ้าฮาร์ดไดรฟ์หยุดทำงาน เราบอกว่าฮาร์ดไดรฟ์นั้น failed ; ถ้าระบบประกอบด้วยฮาร์ดไดรฟ์เพียงตัวเดียว มันก็หยุดให้บริการตามที่ต้องการ และดังนั้นจึงล้มเหลวเช่นกัน อย่างไรก็ตาม ถ้าระบบประกอบด้วยฮาร์ดไดรฟ์หลายตัว ความล้มเหลวของฮาร์ดไดรฟ์ตัวเดียวเป็นเพียง fault จากมุมมองของระบบที่ใหญ่กว่า และระบบที่ใหญ่กว่าอาจสามารถทนต่อ fault นั้นได้โดยการมีสำเนาของข้อมูลบนฮาร์ดไดรฟ์อื่น

Fault Tolerance (การทนทานต่อข้อผิดพลาด)

เราเรียกระบบว่า fault-tolerant (ทนทานต่อข้อผิดพลาด) ถ้ามันยังคงให้บริการตามที่ต้องการแก่ผู้ใช้แม้มี faults บางอย่างเกิดขึ้น ถ้าระบบไม่สามารถทนต่อส่วนใดส่วนหนึ่งที่ผิดพลาดได้ เราเรียกส่วนนั้นว่า single point of failure (SPOF) (จุดเดียวที่ทำให้ล้มเหลว) เพราะ fault ในส่วนนั้นจะบานปลายทำให้ระบบทั้งหมดล้มเหลว

ตัวอย่างเช่น ในกรณีศึกษา social network fault ที่อาจเกิดขึ้นคือในระหว่างกระบวนการ fan-out เครื่องที่เกี่ยวข้องกับการอัปเดต materialized timelines crash หรือไม่สามารถใช้งานได้ เพื่อทำให้กระบวนการนี้ทนทานต่อข้อผิดพลาด เราจะต้องมั่นใจว่าเครื่องอื่นสามารถรับงานนี้ โดยไม่พลาดโพสต์ใด ๆ ที่ควรจะถูกส่ง และไม่ทำโพสต์ซ้ำ (แนวคิดนี้เรียกว่า exactly-once semantics และเราจะตรวจสอบมันในรายละเอียดใน Chapter 12 )

Fault tolerance ถูกจำกัดอยู่ที่จำนวน faults บางประเภทที่แน่นอนเสมอ ตัวอย่างเช่น ระบบอาจทนต่อฮาร์ดไดรฟ์ที่เสียพร้อมกันได้สูงสุดสองตัว หรือโหนด crash ได้สูงสุดหนึ่งในสามโหนด มันไม่สมเหตุสมผลที่จะทนต่อ faults ได้ไม่จำกัดจำนวน; ถ้าโหนดทั้งหมด crash ก็ไม่มีอะไรที่ทำได้ ถ้าทั้งโลก (และเซิร์ฟเวอร์ทั้งหมดบนมัน) ถูกหลุมดำกลืน การทนต่อ fault นั้น จำเป็นต้องมีโฮสติ้งในอวกาศ — ขอให้โชคดีในการขออนุมัติงบประมาณนั้น

ที่ขัดกับสามัญสำนึก ในระบบที่ทนทานต่อข้อผิดพลาดเช่นนี้ มันสมเหตุสมผลที่จะ เพิ่ม อัตรา faults โดยการกระตุ้นมันอย่างตั้งใจ — ตัวอย่างเช่น โดยการฆ่า process แต่ละตัวแบบสุ่มโดยไม่มีการเตือน วิธีนี้เรียกว่า fault injection (การฉีดข้อผิดพลาด) บั๊กที่ร้ายแรงหลายตัวจริง ๆ แล้วเกิดจากการจัดการ error ที่ไม่ดี [ 40 ]; โดยการกระตุ้น faults อย่างตั้งใจ คุณมั่นใจว่ากลไกการทนทานต่อข้อผิดพลาดถูกใช้งานและทดสอบอย่างต่อเนื่อง ซึ่งสามารถเพิ่มความมั่นใจว่า faults จะได้รับการจัดการอย่างถูกต้องเมื่อมันเกิดขึ้นตามธรรมชาติ Chaos engineering (วิศวกรรมความโกลาหล) เป็นสาขาวิชาที่มุ่งเน้นการเพิ่มความมั่นใจในกลไกการทนทานต่อข้อผิดพลาด ผ่านการทดลองเช่นการฉีด faults อย่างตั้งใจ [ 41 ]

แม้ว่าเรามักจะชอบการทนทานต่อ faults มากกว่าการป้องกัน faults แต่ในบางกรณี การป้องกันดีกว่าการแก้ไข (เช่น เพราะไม่มีวิธีการแก้ไข) นี่คือกรณีของเรื่องความปลอดภัย ตัวอย่างเช่น; ถ้าผู้โจมตีบุกรุกระบบและเข้าถึงข้อมูลที่ละเอียดอ่อน เหตุการณ์นั้นไม่สามารถย้อนกลับได้ อย่างไรก็ตาม หนังสือเล่มนี้ส่วนใหญ่เกี่ยวข้องกับประเภทของ faults ที่สามารถแก้ไขได้ ดังที่อธิบายในหัวข้อถัด ๆ ไป

Hardware and Software Faults (ข้อผิดพลาดด้านฮาร์ดแวร์และซอฟต์แวร์)

เมื่อเรานึกถึงสาเหตุของความล้มเหลวของระบบ ข้อผิดพลาดด้านฮาร์ดแวร์เป็นสิ่งแรกที่เข้ามาในหัว:

  • ประมาณ 2%–5% ของ magnetic hard drives เสียต่อปี [ 42 , 43 ]; ในคลัสเตอร์จัดเก็บข้อมูลที่มี 10,000 ดิสก์ เราจึงควรคาดการณ์ โดยเฉลี่ยว่าดิสก์เสียหนึ่งครั้งต่อวัน ข้อมูลล่าสุดชี้ให้เห็นว่าดิสก์มีความน่าเชื่อถือมากขึ้น แต่อัตราความเสียหายยังคงมีนัยสำคัญ [ 44 ]

  • ประมาณ 0.5%–1% ของ solid state drives (SSDs) เสียต่อปี [ 45 ] ข้อผิดพลาดบิตจำนวนเล็กน้อยถูกแก้ไขโดยอัตโนมัติ [ 46 ] แต่ข้อผิดพลาดที่ไม่สามารถแก้ไขได้เกิดขึ้นประมาณปีละครั้งต่อไดรฟ์ แม้แต่ในไดรฟ์ที่ค่อนข้างใหม่ (นั่นคือที่มีการสึกหรอน้อย) อัตราข้อผิดพลาดนี้สูงกว่าของ magnetic hard drives [ 47 , 48 ]

  • ส่วนประกอบฮาร์ดแวร์อื่น ๆ (เช่น power supplies, RAID controllers, และ memory modules) ก็เสียเช่นกัน แม้ว่าจะน้อยกว่าฮาร์ดไดรฟ์ [ 49 , 50 ]

  • ประมาณ 1 ใน 1,000 เครื่องมี CPU core ที่บางครั้งคำนวณผลลัพธ์ผิด ซึ่งน่าจะเกิดจากข้อบกพร่องในการผลิต [ 51 , 52 , 53 ] ในบางกรณีการคำนวณที่ผิดพลาดนำไปสู่การ crash แต่ในกรณีอื่น ๆ มันนำไปสู่การที่โปรแกรมเพียงแค่ส่งคืนผลลัพธ์ที่ผิด

  • ข้อมูลใน RAM สามารถเสียหายได้ ไม่ว่าจะจากเหตุการณ์สุ่มเช่นรังสีคอสมิก หรือจากข้อบกพร่องทางกายภาพถาวร แม้เมื่อใช้หน่วยความจำ ที่มี error-correcting codes (ECC) แล้ว เครื่องมากกว่า 1% ก็พบข้อผิดพลาดที่ไม่สามารถแก้ไขได้ในปีที่กำหนด ซึ่งมักนำไปสู่การ crash ของเครื่อง และโมดูลหน่วยความจำที่ได้รับผลกระทบจำเป็นต้องถูกเปลี่ยน [ 54 ] ยิ่งไปกว่านั้น รูปแบบการเข้าถึงหน่วยความจำที่ผิดปกติบางอย่างสามารถพลิกบิต ด้วยความน่าจะเป็นสูง [ 55 ]

  • datacenter ทั้งหมดอาจไม่สามารถใช้งานได้ (เช่น เพราะไฟฟ้าดับหรือการตั้งค่าเครือข่ายผิดพลาด) หรือแม้แต่ถูกทำลายอย่างถาวร (เช่น จากไฟไหม้ น้ำท่วม หรือแผ่นดินไหว [ 56 ]) พายุสุริยะ ซึ่งทำให้เกิดกระแสไฟฟ้าขนาดใหญ่ในสายไฟระยะไกลเมื่อดวงอาทิตย์ปล่อย อนุภาคมีประจุจำนวนมาก สามารถทำลายโครงข่ายไฟฟ้าและสายเคเบิลเครือข่ายใต้ทะเล [ 57 ] แม้ว่าความล้มเหลวขนาดใหญ่เช่นนี้จะเกิดขึ้นได้ยาก ผลกระทบของมันอาจร้ายแรงถ้าบริการไม่สามารถทนต่อการสูญเสีย datacenter [ 58 ]

เหตุการณ์เหล่านี้เกิดขึ้นได้ยากพอที่คุณมักไม่ต้องกังวลเกี่ยวกับมันเมื่อทำงานกับระบบเล็ก ตราบใดที่คุณสามารถเปลี่ยนฮาร์ดแวร์ที่เสียได้ง่าย อย่างไรก็ตาม ในระบบขนาดใหญ่ ข้อผิดพลาดด้านฮาร์ดแวร์เกิดขึ้นบ่อยพอที่จะกลายเป็นส่วนหนึ่งของการทำงานปกติของระบบ

Tolerating hardware faults through redundancy (การทนต่อข้อผิดพลาดฮาร์ดแวร์ผ่านความซ้ำซ้อน)

การตอบสนองแรกต่อฮาร์ดแวร์ที่ไม่น่าเชื่อถือของเรามักจะเป็นการเพิ่มความซ้ำซ้อน ให้กับส่วนประกอบฮาร์ดแวร์แต่ละตัวเพื่อลดอัตราความล้มเหลวของระบบ ดิสก์อาจถูกตั้งค่าใน RAID configuration (กระจายข้อมูลข้ามหลายดิสก์ในเครื่องเดียวกันเพื่อให้ดิสก์ที่เสีย ไม่ทำให้ข้อมูลสูญหาย), server อาจมี dual power supplies และ CPU แบบ hot-swappable และ datacenter อาจมีแบตเตอรี่และเครื่องปั่นไฟดีเซลสำหรับพลังงานสำรอง ความซ้ำซ้อนเช่นนี้มักสามารถทำให้เครื่องทำงานได้อย่างต่อเนื่องเป็นเวลาหลายปี

ความซ้ำซ้อนมีประสิทธิภาพสูงสุดเมื่อข้อผิดพลาดของส่วนประกอบเป็นอิสระต่อกัน — นั่นคือ เมื่อการเกิด fault หนึ่งไม่ได้เปลี่ยนโอกาสที่ fault อื่นจะเกิดขึ้น อย่างไรก็ตาม ประสบการณ์แสดงให้เห็นว่ามีความสัมพันธ์กันอย่างมีนัยสำคัญระหว่างความล้มเหลวของส่วนประกอบ [ 43 , 59 , 60 ] การที่ server rack ทั้งหมดหรือ datacenter ทั้งหมดไม่สามารถใช้งานได้ ก็ยังเกิดขึ้นบ่อยกว่าที่เราต้องการ

ความซ้ำซ้อนของฮาร์ดแวร์เพิ่ม uptime ของเครื่องเดียว; อย่างไรก็ตาม ดังที่กล่าวถึงใน "Distributed Versus Single-Node Systems" การใช้ระบบ distributed มีข้อดี เช่น การสามารถทนต่อการหยุดให้บริการทั้งหมดของ datacenter หนึ่งแห่ง ด้วยเหตุนี้ ระบบ cloud จึงมักให้ความสำคัญกับความน่าเชื่อถือของแต่ละเครื่องน้อยลง และมุ่งเน้นทำให้บริการพร้อมใช้งานสูงโดยการทนต่อโหนดที่เสียในระดับซอฟต์แวร์ ผู้ให้บริการ cloud ใช้ availability zones (โซนความพร้อมใช้งาน) เพื่อระบุทรัพยากรที่อยู่ร่วมกันทางกายภาพ; ทรัพยากรในสถานที่เดียวกันมีแนวโน้มที่จะล้มเหลวพร้อมกันมากกว่าทรัพยากรที่แยกจากกันทางภูมิศาสตร์

เทคนิคการทนทานต่อข้อผิดพลาดที่เรากล่าวถึงในหนังสือเล่มนี้ ออกแบบมาให้ทนต่อการสูญเสียเครื่องจักรทั้งหมด ชั้นวาง หรือ availability zones โดยทั่วไปมันทำงานโดยให้เครื่องใน datacenter หนึ่งรับช่วงต่อเมื่อเครื่องในอีก datacenter เสียหรือไม่สามารถเข้าถึงได้ เราจะพูดถึงเทคนิคดังกล่าวสำหรับการทนทานต่อข้อผิดพลาด ในบท 6 , 10 และจุดอื่น ๆ ในหนังสือเล่มนี้

ระบบที่สามารถทนต่อการสูญเสียเครื่องจักรทั้งหมดยังมีข้อดีในด้านการปฏิบัติการ ระบบ single-server ต้องการ downtime ที่วางแผนไว้ถ้าคุณต้องการรีบูตเครื่อง (เช่น เพื่อติดตั้งแพตช์ความปลอดภัยของระบบปฏิบัติการ) ในขณะที่ระบบ multi-node ที่ทนทานต่อข้อผิดพลาดสามารถถูกแพตช์ได้ โดยการรีสตาร์ททีละโหนด โดยไม่ส่งผลกระทบต่อบริการสำหรับผู้ใช้ วิธีนี้เรียกว่า rolling upgrade (การอัปเกรดแบบโรลลิง) และเราจะพูดถึงมันเพิ่มเติมใน Chapter 5

Software faults (ข้อผิดพลาดของซอฟต์แวร์)

แม้ว่าความล้มเหลวของฮาร์ดแวร์อาจมีความสัมพันธ์กันเล็กน้อย แต่โดยส่วนใหญ่แล้วยังคงเป็นอิสระ — ตัวอย่างเช่น ถ้าดิสก์ตัวหนึ่งเสีย ดิสก์อื่นในเครื่องเดียวกันมักจะยังโอเค อย่างน้อยก็ชั่วขณะหนึ่ง ในทางกลับกัน ข้อผิดพลาดของซอฟต์แวร์มักมีความสัมพันธ์กันสูงมาก เพราะเป็นเรื่องปกติที่หลายโหนดจะรันซอฟต์แวร์เดียวกันและดังนั้นจึงมีบั๊กเดียวกัน [ 61 , 62 ] faults ดังกล่าวยากที่จะคาดการณ์ และมันมักทำให้เกิดความล้มเหลวของระบบ มากกว่า hardware faults ที่ไม่มีความสัมพันธ์กัน [ 49 ] ตัวอย่างได้แก่:

  • บั๊กซอฟต์แวร์ที่ทำให้ทุกโหนดล้มเหลวพร้อมกันในสถานการณ์เฉพาะ ตัวอย่างเช่น ในวันที่ 30 มิถุนายน 2012 อธิกวินาที (leap second) ทำให้แอปพลิเคชัน Java จำนวนมาก หยุดค้างพร้อมกันเนื่องจากบั๊กใน Linux kernel ทำให้บริการอินเทอร์เน็ตหลายแห่งล่ม [ 63 ] และเนื่องจากบั๊กใน firmware SSDs บางรุ่นทั้งหมดเสียกะทันหัน หลังจากทำงานครบ 32,768 ชั่วโมง (น้อยกว่าสี่ปี) ทำให้ข้อมูลบนนั้นกู้คืนไม่ได้ [ 64 ]

  • process ที่ทำงานผิดปกติและใช้ทรัพยากรที่มีจำกัดร่วมกัน เช่น CPU time, หน่วยความจำ, พื้นที่ดิสก์, แบนด์วิธเครือข่าย, หรือ threads [ 65 ] ตัวอย่างเช่น process ที่ใช้หน่วยความจำมากเกินไปในขณะที่ประมวลผล request ขนาดใหญ่อาจถูก kill โดยระบบปฏิบัติการ หรือบั๊กใน client library อาจทำให้มีปริมาณ request สูงกว่าที่คาดไว้มาก [ 66 ]

  • บริการที่ระบบพึ่งพาทำงานช้าลง ไม่ตอบสนอง หรือเริ่มส่งคืนคำตอบที่เสียหาย

  • ปฏิสัมพันธ์ระหว่างระบบต่าง ๆ ทำให้เกิดพฤติกรรมฉุกเฉิน ที่ไม่เกิดขึ้นเมื่อแต่ละระบบถูกทดสอบแยกกัน [ 67 ]

  • Cascading failures (ความล้มเหลวแบบลูกโซ่) ที่ปัญหาของส่วนประกอบหนึ่ง ทำให้ส่วนประกอบอื่นโอเวอร์โหลดและช้าลง ซึ่งในทางกลับกันทำให้อีกส่วนประกอบล้ม [ 68 , 69 ]

บั๊กที่ทำให้เกิด software faults ประเภทนี้มักซ่อนตัวอยู่เป็นเวลานาน จนกว่าจะถูกกระตุ้นโดยชุดสถานการณ์ที่ไม่ปกติ ในสถานการณ์เหล่านั้น มันถูกเปิดเผยว่าซอฟต์แวร์กำลังตั้งสมมติฐานบางอย่างเกี่ยวกับสภาพแวดล้อมของมัน — และในขณะที่สมมติฐานนั้น โดยปกติ เป็นจริง ในที่สุดมันก็หยุดเป็นจริงด้วยเหตุผลบางประการ [ 70 , 71 ]

ปัญหาของ systematic faults ในซอฟต์แวร์ไม่มีวิธีแก้ที่รวดเร็ว สิ่งเล็ก ๆ มากมายสามารถช่วยได้: การคิดอย่างรอบคอบเกี่ยวกับสมมติฐานและปฏิสัมพันธ์ในระบบ; การทดสอบอย่างละเอียด; การมั่นใจใน process isolation; การอนุญาตให้ process crash และรีสตาร์ท; การหลีกเลี่ยง feedback loops เช่น retry storms (ดู "When an Overloaded System Won't Recover" ); การวัด, การตรวจสอบ, และการวิเคราะห์พฤติกรรมของระบบใน production

Humans and Reliability (มนุษย์และความน่าเชื่อถือ)

มนุษย์ออกแบบและสร้างระบบซอฟต์แวร์ และผู้ปฏิบัติการที่ทำให้ระบบทำงานต่อไปก็เป็นมนุษย์เช่นกัน แตกต่างจากเครื่องจักร มนุษย์ไม่ได้แค่ทำตามกฎ; จุดแข็งอย่างหนึ่งของพวกเขาคือการมีความคิดสร้างสรรค์และปรับตัว ในการทำงานให้สำเร็จ อย่างไรก็ตาม ลักษณะนี้ยังนำไปสู่ความไม่สามารถคาดเดาได้ และบางครั้งนำไปสู่ความผิดพลาดที่อาจทำให้เกิดความล้มเหลว แม้จะมีความตั้งใจดีที่สุด ตัวอย่างเช่น การศึกษาหนึ่งของบริการอินเทอร์เน็ตขนาดใหญ่พบว่าการเปลี่ยนแปลงการกำหนดค่าโดยผู้ปฏิบัติการ เป็นสาเหตุหลักของการหยุดให้บริการ ในขณะที่ข้อผิดพลาดของฮาร์ดแวร์ (เซิร์ฟเวอร์หรือเครือข่าย) มีบทบาทในเพียง 10%–25% ของกรณี [ 72 ]

มันเป็นการดึงดูดที่จะติดป้ายปัญหาดังกล่าวว่าเป็น "ความผิดพลาดของมนุษย์" และหวังว่ามันจะแก้ไขได้ โดยการควบคุมพฤติกรรมมนุษย์ให้ดีขึ้นผ่านขั้นตอนที่เข้มงวดขึ้นและการปฏิบัติตามกฎ อย่างไรก็ตาม การตำหนิคนอื่นสำหรับความผิดพลาดนั้นไม่เกิดประโยชน์ สิ่งที่เราเรียกว่า "ความผิดพลาดของมนุษย์" ไม่ใช่สาเหตุของเหตุการณ์จริง ๆ แต่เป็นอาการของปัญหา ในระบบสังคมและเทคนิคที่ผู้คนพยายามอย่างดีที่สุดในการทำงานของพวกเขา [ 73 ] บ่อยครั้งที่ระบบที่ซับซ้อนมีพฤติกรรมฉุกเฉิน ซึ่งการโต้ตอบที่ไม่คาดคิดระหว่างส่วนประกอบ อาจนำไปสู่ความล้มเหลวได้เช่นกัน [ 74 ]

มาตรการทางเทคนิคต่าง ๆ สามารถช่วยลดผลกระทบจากความผิดพลาดของมนุษย์ได้ รวมถึงการทดสอบอย่างละเอียด (ทั้งการทดสอบที่เขียนด้วยมือและ property testing บน input สุ่มจำนวนมาก) [ 40 ], กลไก rollback สำหรับการย้อนกลับการเปลี่ยนแปลงการกำหนดค่าอย่างรวดเร็ว, การค่อย ๆ ปล่อยโค้ดใหม่, การตรวจสอบที่ละเอียดและชัดเจน, เครื่องมือ observability สำหรับการวินิจฉัยปัญหาใน production (ดู "Problems with Distributed Systems" ), และอินเทอร์เฟซที่ออกแบบมาอย่างดีที่ส่งเสริม "สิ่งที่ถูกต้อง" และไม่สนับสนุน "สิ่งที่ผิด"

อย่างไรก็ตาม สิ่งเหล่านี้ทั้งหมดต้องการการลงทุนทั้งเวลาและเงิน และในความเป็นจริงเชิงปฏิบัติการของธุรกิจในชีวิตประจำวัน องค์กรมักให้ความสำคัญกับกิจกรรม ที่สร้างรายได้ มากกว่ามาตรการที่เพิ่มความทนทานของระบบต่อความผิดพลาด เมื่อต้องเลือกระหว่างฟีเจอร์มากขึ้นหรือการทดสอบมากขึ้น หลายองค์กรเลือกฟีเจอร์อย่างเข้าใจได้ จากนั้น เมื่อความผิดพลาดที่ป้องกันได้เกิดขึ้นอย่างหลีกเลี่ยงไม่ได้ การตำหนิคนที่ทำผิดพลาดนั้นไม่สมเหตุสมผล; ปัญหาอยู่ที่ลำดับความสำคัญขององค์กร

มากขึ้นเรื่อย ๆ องค์กรต่าง ๆ กำลังนำวัฒนธรรมของ blameless postmortems (การวิเคราะห์หลังเหตุการณ์ที่ไม่มีการตำหนิ) มาใช้: หลังจากเกิดเหตุการณ์ คนที่เกี่ยวข้องถูกส่งเสริมให้แบ่งปันรายละเอียดทั้งหมดเกี่ยวกับสิ่งที่เกิดขึ้น โดยไม่ต้องกลัวการลงโทษ เพราะสิ่งนี้ช่วยให้ผู้อื่นในองค์กรเรียนรู้วิธีป้องกันปัญหาที่คล้ายกันในอนาคต [ 75 ] กระบวนการนี้อาจเปิดเผยความจำเป็นในการเปลี่ยนแปลงลำดับความสำคัญทางธุรกิจ ลงทุนในพื้นที่ที่ถูกละเลย เปลี่ยนสิ่งจูงใจสำหรับคนที่เกี่ยวข้อง หรือนำประเด็นปัญหาเชิงระบบอื่นมาสู่ความสนใจของผู้บริหาร

ตามหลักการทั่วไป เมื่อสืบสวนเหตุการณ์ คุณควรระวังคำตอบที่ง่ายเกินไป "บ็อบควรระมัดระวังมากขึ้นเมื่อ Deploy การเปลี่ยนแปลงนั้น" ไม่เกิดประโยชน์ แต่ "เราต้องเขียน backend ใหม่ใน Haskell" ก็ไม่เกิดประโยชน์เช่นกัน แต่ผู้บริหารควรใช้โอกาสนี้เรียนรู้รายละเอียดว่าระบบสังคมและเทคนิคทำงานอย่างไร จากมุมมองของคนที่ทำงานกับมันทุกวัน และดำเนินการปรับปรุงมันตามคำติชมนี้ [ 73 ]

How Important Is Reliability? (ความน่าเชื่อถือสำคัญแค่ไหน?)

ความน่าเชื่อถือไม่ใช่เพียงสำหรับโรงไฟฟ้านิวเคลียร์และการควบคุมจราจรทางอากาศ แอปพลิเคชันทั่วไปก็ถูกคาดหวังให้ทำงานได้อย่างน่าเชื่อถือเช่นกัน บั๊กในแอปพลิเคชันทางธุรกิจนำไปสู่การสูญเสียผลผลิต (และความเสี่ยงทางกฎหมาย ถ้าตัวเลขถูกรายงานอย่างไม่ถูกต้อง) และการหยุดให้บริการของเว็บไซต์ ecommerce สามารถมีต้นทุนมหาศาลในแง่ของรายได้ที่สูญเสียและความเสียหายต่อชื่อเสียง

ในหลายแอปพลิเคชัน การหยุดให้บริการชั่วคราวสองสามนาทีหรือไม่กี่ชั่วโมงก็ทนได้ [ 76 ] แต่การสูญเสียข้อมูลถาวรหรือความเสียหายของข้อมูลจะร้ายแรง ลองนึกถึงพ่อแม่ที่เก็บรูปภาพและวิดีโอทั้งหมดของลูกไว้ในแอปพลิเคชันรูปภาพของคุณ [ 77 ] พวกเขาจะรู้สึกอย่างไรถ้าฐานข้อมูลนั้นเสียหายกระทันหัน? พวกเขาจะรู้วิธีกู้คืนคอลเล็กชันของพวกเขาจาก backup หรือไม่?

อีกตัวอย่างหนึ่งว่าซอฟต์แวร์ที่ไม่น่าเชื่อถือสามารถทำร้ายผู้คนได้อย่างไร ลองพิจารณาเรื่องอื้อฉาวของ Post Office Horizon ระหว่างปี 1999 ถึง 2019 ผู้คนหลายร้อยคนที่จัดการสาขาของไปรษณีย์ในอังกฤษถูกตัดสินว่ามีความผิดฐานลักทรัพย์หรือฉ้อโกง เพราะซอฟต์แวร์บัญชีแสดงว่ามียอดเงินขาดในบัญชีของพวกเขา ในที่สุดก็ชัดเจนว่ายอดเงินขาดเหล่านี้หลายกรณีเกิดจากบั๊กในซอฟต์แวร์ ส่งผลให้คำตัดสินหลายคำถูกพลิกกลับ [ 78 ] สิ่งที่นำไปสู่นี้ ซึ่งน่าจะเป็นความยุติธรรมที่คลาดเคลื่อนครั้งใหญ่ที่สุดในประวัติศาสตร์อังกฤษ คือข้อสมมติของกฎหมายอังกฤษที่ว่าคอมพิวเตอร์ทำงานได้อย่างถูกต้อง (และดังนั้น หลักฐานที่ผลิตโดยคอมพิวเตอร์จึงเชื่อถือได้) เว้นแต่จะมีหลักฐานที่ตรงกันข้าม [ 79 ] วิศวกรซอฟต์แวร์อาจหัวเราะกับแนวคิดที่ว่าซอฟต์แวร์จะไม่มีบั๊กได้ แต่นี่เป็นสิ่งปลอบใจเพียงเล็กน้อยสำหรับคนที่ถูกจำคุกอย่างไม่เป็นธรรม ถูกประกาศล้มละลาย หรือแม้แต่ฆ่าตัวตายอันเป็นผลมาจากคำตัดสินที่ผิด เนื่องจากระบบคอมพิวเตอร์ที่ไม่น่าเชื่อถือ

ในบางสถานการณ์เราอาจเลือกที่จะเสียสละความน่าเชื่อถือเพื่อลดต้นทุนการพัฒนา (เช่น เมื่อพัฒนาต้นแบบผลิตภัณฑ์สำหรับตลาดที่ไม่ได้รับการพิสูจน์) — แต่เราควรตระหนักอย่างมากเมื่อเรากำลังใช้ทางลัด และจำไว้ว่าอาจมีผลตามมา

Scalability (ความสามารถในการปรับขนาด)

แม้ว่าระบบจะทำงานได้อย่างน่าเชื่อถือในวันนี้ ก็ไม่ได้หมายความว่าระบบจะทำงานได้อย่างน่าเชื่อถือในอนาคต สาเหตุทั่วไปประการหนึ่งของการเสื่อมประสิทธิภาพคือโหลดที่เพิ่มขึ้น บางทีระบบอาจเติบโตจากผู้ใช้พร้อมกัน 10,000 คนเป็น 100,000 คน หรือจาก 1 ล้านเป็น 10 ล้าน บางทีมันอาจประมวลผลข้อมูลในปริมาณที่มากกว่าเดิมมาก

Scalability (ความสามารถในการปรับขนาด) คือคำที่เราใช้เพื่ออธิบายความสามารถของระบบในการรับมือกับโหลดที่เพิ่มขึ้น บางครั้ง เมื่อพูดถึง scalability คนอาจแสดงความคิดเห็นในทำนองว่า "คุณไม่ใช่ Google หรือ Amazon หยุดกังวลเรื่อง scale แค่ใช้ relational database" ไม่ว่าคติพจน์นี้จะใช้กับคุณหรือไม่ ขึ้นอยู่กับประเภทของแอปพลิเคชันที่คุณกำลังสร้าง

ถ้าคุณกำลังสร้างผลิตภัณฑ์ใหม่ที่ปัจจุบันมีผู้ใช้เพียงจำนวนน้อย เช่นใน startup เป้าหมายทางวิศวกรรมหลักมักจะเป็นการทำให้ระบบเรียบง่ายและยืดหยุ่นที่สุดเท่าที่จะเป็นไปได้ เพื่อให้คุณสามารถปรับเปลี่ยนและปรับคุณสมบัติของผลิตภัณฑ์ได้ง่าย เมื่อคุณเรียนรู้เพิ่มเติมเกี่ยวกับความต้องการของลูกค้า [ 80 ] ในสภาพแวดล้อมเช่นนี้ การกังวลเกี่ยวกับ scale ที่เป็นสมมุติ ซึ่งอาจจำเป็นในอนาคตเป็นการต่อต้าน อย่างดีที่สุด การลงทุนใน scalability คือความพยายามที่สูญเปล่าและการปรับแต่งก่อนเวลาอันควร; ที่แย่ที่สุด มันล็อกคุณไว้ในการออกแบบที่ไม่ยืดหยุ่นและทำให้การพัฒนาต่อยอดแอปพลิเคชันของคุณยากขึ้น

Scalability ไม่ใช่ป้ายกำกับมิติเดียว — มันไม่มีความหมายที่จะพูดว่า " X สามารถปรับขนาดได้" หรือ " Y ไม่สามารถปรับขนาดได้" แต่การพูดถึง scalability หมายถึงการพิจารณาคำถามเช่น:

  • ถ้าระบบเติบโตในลักษณะที่กำหนด เรามีทางเลือกอะไรในการรับมือกับการเติบโตนั้น?

  • เราจะเพิ่มทรัพยากรการคำนวณเพื่อจัดการกับโหลดที่เพิ่มขึ้นได้อย่างไร?

  • จากการคาดการณ์การเติบโตในปัจจุบัน เมื่อไหร่เราจะถึงขีดจำกัดของสถาปัตยกรรมปัจจุบันของเรา?

ถ้าคุณประสบความสำเร็จในการทำให้แอปพลิเคชันของคุณเป็นที่นิยม และดังนั้นจึงจัดการกับโหลดที่เพิ่มขึ้น คุณจะเรียนรู้ว่าจุดคอขวดของประสิทธิภาพอยู่ที่ไหนและในมิติใดที่คุณต้องปรับขนาด เมื่อถึงจุดนั้น ก็ถึงเวลาเริ่มกังวลเกี่ยวกับเทคนิคสำหรับ scalability

Understanding Load (การทำความเข้าใจโหลด)

ก่อนอื่น คุณต้องมีความเข้าใจที่ชัดเจนเกี่ยวกับโหลดปัจจุบันบนระบบ จากนั้นคุณจึงจะสามารถพูดคุยเกี่ยวกับคำถามการเติบโต ("จะเกิดอะไรขึ้นถ้าโหลดของเราเพิ่มขึ้นเป็นสองเท่า?") บ่อยครั้งที่นี่จะเป็นหน่วยวัด throughput — ตัวอย่างเช่น จำนวน requests ต่อวินาทีที่ส่งไปยังบริการ จำนวนกิกะไบต์ของข้อมูลใหม่ที่มาถึงต่อวัน หรือจำนวนการชำระเงินในตะกร้าสินค้าต่อชั่วโมง บางครั้งคุณสนใจค่าสูงสุดของปริมาณที่แปรผัน เช่น จำนวนผู้ใช้ที่ออนไลน์พร้อมกัน ในกรณีศึกษา social network ของเรา

บ่อยครั้งที่คุณลักษณะทางสถิติอื่น ๆ ของโหลดส่งผลต่อรูปแบบการเข้าถึง และดังนั้นข้อกำหนด scalability ตัวอย่างเช่น คุณอาจต้องรู้อัตราส่วนของการอ่านต่อการเขียนใน database อัตราการชน cache หรือจำนวนรายการข้อมูลต่อผู้ใช้ (ผู้ติดตาม ในกรณีศึกษาของเรา) บางทีกรณีทั่วไปคือสิ่งที่สำคัญสำหรับคุณ หรือบางทีคอขวดของคุณถูกครอบงำ โดยกรณีที่รุนแรงจำนวนเล็กน้อย ทั้งหมดขึ้นอยู่กับรายละเอียดของแอปพลิเคชันเฉพาะของคุณ

เมื่อคุณเข้าใจโหลดบนระบบแล้ว คุณสามารถตรวจสอบสิ่งที่เกิดขึ้นเมื่อโหลดเพิ่มขึ้น คุณสามารถดูได้ในสองทาง:

  • เมื่อคุณเพิ่มโหลดในลักษณะที่กำหนดโดยไม่เปลี่ยนทรัพยากรระบบ (CPU, หน่วยความจำ, แบนด์วิธเครือข่าย ฯลฯ) ประสิทธิภาพของระบบของคุณได้รับผลกระทบอย่างไร?

  • เมื่อคุณเพิ่มโหลดในลักษณะที่กำหนด คุณต้องเพิ่มทรัพยากรมากแค่ไหน ถ้าคุณต้องการให้ประสิทธิภาพคงเดิม?

โดยปกติเป้าหมายคือการรักษาประสิทธิภาพของระบบให้อยู่ภายในข้อกำหนดของ SLA (ดู "Use of Response Time Metrics" ) ในขณะที่ลดต้นทุนการรันระบบให้เหลือน้อยที่สุด ยิ่งทรัพยากรการคำนวณที่ต้องการมากเท่าไหร่ ต้นทุนก็ยิ่งสูงขึ้นเท่านั้น ฮาร์ดแวร์บางประเภทอาจคุ้มค่ากว่าประเภทอื่น และปัจจัยเหล่านี้อาจเปลี่ยนแปลงไปตามกาลเวลาเมื่อฮาร์ดแวร์ประเภทใหม่พร้อมใช้งาน

ถ้าการเพิ่มทรัพยากรเป็นสองเท่าทำให้คุณสามารถจัดการโหลดเป็นสองเท่า ในขณะที่รักษาประสิทธิภาพให้เท่าเดิม เรากล่าวว่าคุณมี linear scalability (การปรับขนาดเชิงเส้น) และนี่ถือเป็นสิ่งที่ดี ในบางครั้ง เป็นไปได้ที่จะจัดการโหลดเป็นสองเท่า โดยใช้ทรัพยากรน้อยกว่าสองเท่า เนื่องจาก economies of scale หรือการกระจายของ peak load ที่ดีขึ้น [ 81 , 82 ] มีความเป็นไปได้มากกว่าที่ต้นทุนจะเติบโตเร็วกว่าเชิงเส้น อาจมีหลายสาเหตุของความไม่มีประสิทธิภาพ; ตัวอย่างเช่น ถ้าคุณมีข้อมูลจำนวนมาก การประมวลผล request เขียนหนึ่งครั้งอาจเกี่ยวข้องกับงานมากกว่าถ้าคุณมีข้อมูลจำนวนเล็กน้อย แม้ว่าขนาดของ request จะเท่ากัน

Shared-Memory, Shared-Disk, and Shared-Nothing Architectures (สถาปัตยกรรม Shared-Memory, Shared-Disk และ Shared-Nothing)

วิธีที่ง่ายที่สุดในการเพิ่มทรัพยากรฮาร์ดแวร์ของบริการคือการย้ายไปยังเครื่องที่ทรงพลังกว่า CPU cores แต่ละตัวไม่ได้เร็วขึ้นอย่างมีนัยสำคัญอีกต่อไป แต่คุณสามารถซื้อเครื่อง (หรือเช่า cloud instance) ที่มี CPU cores มากกว่า RAM มากกว่า และพื้นที่ดิสก์มากกว่า วิธีนี้เรียกว่า vertical scaling (การปรับขนาดแนวตั้ง) หรือ scaling up

คุณสามารถทำ parallelism บนเครื่องเดียวโดยใช้หลาย processes หรือ threads threads ทั้งหมดที่อยู่ใน process เดียวกันสามารถเข้าถึง RAM เดียวกัน และดังนั้นวิธีนี้จึงเรียกว่า shared-memory architecture (สถาปัตยกรรมหน่วยความจำร่วม) ปัญหาของแนวทาง shared-memory คือต้นทุนเติบโตเร็วกว่าเชิงเส้น; เครื่องระดับสูงที่มีทรัพยากรฮาร์ดแวร์เป็นสองเท่าของเครื่องระดับล่างมักมีราคาแพงกว่าสองเท่าอย่างมีนัยสำคัญ และเนื่องจากคอขวด เครื่องนั้นไม่น่าจะสามารถจัดการโหลดได้เป็นสองเท่าจริง

อีกแนวทางหนึ่งคือ shared-disk architecture (สถาปัตยกรรมดิสก์ร่วม) ซึ่งใช้หลายเครื่องที่มี CPU และ RAM อิสระ แต่เก็บข้อมูลบนอาร์เรย์ของดิสก์ที่แชร์ระหว่างเครื่อง ซึ่งเชื่อมต่อผ่านเครือข่ายที่เร็ว: network-attached storage (NAS) หรือ storage area network (SAN) สถาปัตยกรรมนี้ถูกใช้แบบดั้งเดิมสำหรับ workload data warehousing ในองค์กร แต่การแย่งชิงและค่าใช้จ่ายของการล็อกจำกัดความสามารถในการปรับขนาดของแนวทาง shared-disk [ 83 ]

ในทางตรงกันข้าม shared-nothing architecture (สถาปัตยกรรมที่ไม่มีการแชร์อะไรเลย) [ 84 ] (หรือเรียกอีกอย่างว่า horizontal scaling หรือ scaling out ) เกี่ยวข้องกับระบบ distributed ที่มีหลายโหนด แต่ละโหนดมี CPU, RAM และดิสก์ของตัวเอง การประสานงานระหว่างโหนดทำที่ระดับซอฟต์แวร์ ผ่านเครือข่ายทั่วไป

ข้อดีของแนวทางนี้ ซึ่งได้รับความนิยมในช่วงไม่กี่ปีที่ผ่านมา คือมันมีศักยภาพในการปรับขนาดเชิงเส้น มันสามารถใช้ฮาร์ดแวร์ใดก็ได้ที่มีอัตราส่วนราคาต่อประสิทธิภาพที่ดีที่สุด (โดยเฉพาะใน cloud) มันสามารถปรับทรัพยากรฮาร์ดแวร์ได้ง่ายขึ้นเมื่อโหลดเพิ่มขึ้นหรือลดลง และมันสามารถทนทานต่อข้อผิดพลาดได้ดีกว่าโดยการกระจายระบบข้ามหลาย datacenter และภูมิภาค ข้อเสียคือมันต้องการ explicit sharding (ดู Chapter 7 ) และมีความซับซ้อนทั้งหมดของระบบ distributed (กล่าวถึงใน Chapter 9 )

ระบบฐานข้อมูล cloud native บางระบบใช้บริการแยกกันสำหรับ storage และการ execute transaction (ดู "Separation of storage and compute" ) โดยมี compute nodes หลายตัวที่แชร์การเข้าถึงบริการ storage เดียวกัน โมเดลนี้มีความคล้ายคลึงบางอย่างกับ shared-disk architecture แต่มันหลีกเลี่ยงปัญหาความสามารถในการปรับขนาดของระบบรุ่นเก่า แทนที่จะให้ abstraction เป็น filesystem (NAS) หรือ block device (SAN) บริการ storage นำเสนอ API ที่ออกแบบมาเฉพาะสำหรับความต้องการของ database [ 85 ]

Principles for Scalability (หลักการสำหรับ Scalability)

สถาปัตยกรรมของระบบที่ทำงานใน scale ใหญ่มักจะเฉพาะเจาะจงกับแอปพลิเคชันเป็นอย่างมาก ไม่มีสิ่งที่เรียกว่าสถาปัตยกรรมที่ปรับขนาดได้แบบทั่วไปที่ใช้ได้กับทุกกรณี (เรียกอย่างไม่เป็นทางการว่า magic scaling sauce ) ตัวอย่างเช่น ระบบที่ออกแบบมาเพื่อจัดการ 100,000 requests ต่อวินาที แต่ละ request ขนาด 1 kB จะดูแตกต่างจากระบบที่ออกแบบมาสำหรับ 3 requests ต่อนาที แต่ละ request ขนาด 2 GB มาก — แม้ว่าทั้งสองระบบจะมี throughput ของข้อมูลเท่ากัน (100 MB/วินาที)

ยิ่งไปกว่านั้น สถาปัตยกรรมที่เหมาะสมกับโหลดระดับหนึ่งไม่น่าจะรับมือกับโหลดที่มากกว่า 10 เท่า ถ้าคุณทำงานบนบริการที่เติบโตอย่างรวดเร็ว ดังนั้นจึงเป็นไปได้ว่าคุณจะต้องคิดทบทวน สถาปัตยกรรมของคุณใหม่ทุกครั้งที่โหลดเพิ่มขึ้นหนึ่ง order of magnitude เนื่องจากความต้องการของแอปพลิเคชันมีแนวโน้มที่จะพัฒนา โดยปกติแล้วมันไม่คุ้มที่จะวางแผนความต้องการปรับขนาดในอนาคตเกินกว่าหนึ่ง order of magnitude

หลักการทั่วไปที่ดีสำหรับ scalability คือการแบ่งระบบออกเป็นส่วนประกอบเล็ก ๆ ที่สามารถทำงานได้อย่างอิสระจากกันเป็นส่วนใหญ่ นี่คือหลักการพื้นฐานเบื้องหลัง microservices (ดู "Microservices and Serverless" ), sharding ( Chapter 7 ), stream processing ( Chapter 12 ), และ shared-nothing architectures ความท้าทายคือการรู้ว่าจะลากเส้นแบ่งระหว่างสิ่งที่ควรอยู่ด้วยกัน และสิ่งที่ควรแยกจากกันที่ไหน แนวทางการออกแบบสำหรับ microservices สามารถพบได้ในหนังสืออื่น ๆ [ 86 ] และเราจะพูดถึง sharding ของ shared-nothing systems ใน Chapter 7

หลักการที่ดีอีกประการหนึ่งคืออย่าทำให้สิ่งต่าง ๆ ซับซ้อนเกินความจำเป็น ถ้า database บนเครื่องเดียวทำงานได้ ก็อาจดีกว่าการตั้งค่า distributed ที่ซับซ้อน ระบบ autoscaling (ที่เพิ่มหรือลดทรัพยากรโดยอัตโนมัติตามความต้องการ) เจ๋งดี แต่ถ้าโหลดของคุณค่อนข้าง predictable ระบบที่ปรับขนาดด้วยมืออาจมีเซอร์ไพรส์ในการปฏิบัติการน้อยกว่า (ดู "Operations: Automatic Versus Manual Rebalancing" ) ระบบที่มี 5 บริการนั้นง่ายกว่าระบบที่มี 50 บริการ สถาปัตยกรรมที่ดีมักเกี่ยวข้องกับการผสมผสาน แนวทางอย่าง pragmatically

Maintainability (ความสามารถในการบำรุงรักษา)

ซอฟต์แวร์ไม่สึกหรอหรือเสื่อมสภาพทางวัตถุ ดังนั้นมันจึงไม่พังในแบบเดียวกับวัตถุเชิงกล แต่ข้อกำหนดของแอปพลิเคชันมักจะพัฒนาอยู่ตลอดเวลา สภาพแวดล้อมที่ซอฟต์แวร์ทำงานก็เปลี่ยนแปลงไป (เช่น dependencies และ platform พื้นฐาน) และมันอาจมีบั๊กที่ต้องแก้ไข

เป็นที่ยอมรับกันอย่างกว้างขวางว่าต้นทุนส่วนใหญ่ของซอฟต์แวร์ไม่ได้อยู่ที่การพัฒนาเริ่มต้น แต่อยู่ที่การบำรุงรักษาอย่างต่อเนื่อง — การแก้ไขบั๊ก, การทำให้ระบบทำงานต่อไปได้, การสืบสวนความล้มเหลว, การปรับให้เข้ากับ platform ใหม่, การปรับเปลี่ยนสำหรับกรณีการใช้งานใหม่, การชำระหนี้ทางเทคนิค, และการเพิ่มฟีเจอร์ใหม่ [ 87 , 88 ]

การบำรุงรักษาอาจซับซ้อน โดยเฉพาะสำหรับระบบ legacy ระบบที่ทำงานได้สำเร็จมาเป็นเวลานาน อาจใช้เทคโนโลยีที่ล้าสมัยที่วิศวกรในปัจจุบันไม่มากนักที่เข้าใจ (เช่น mainframes และโค้ด COBOL) และความรู้ในองค์กรว่าระบบถูกออกแบบมาอย่างไรและทำไม อาจสูญหายไปเมื่อผู้คนออกจากองค์กร การแก้ไขความผิดพลาดของผู้อื่นก็อาจจำเป็นเช่นกัน เพราะระบบคอมพิวเตอร์มักจะเชื่อมโยงกับองค์กรมนุษย์ที่มันสนับสนุน การบำรุงรักษาระบบดังกล่าวจึงเป็นปัญหาของคนพอ ๆ กับปัญหาทางเทคนิค [ 89 ]

ทุกระบบที่เราสร้างขึ้นในวันนี้จะกลายเป็นระบบ legacy ในวันหนึ่ง ถ้ามันมีค่าพอที่จะอยู่รอดได้นาน เพื่อลดความเจ็บปวดสำหรับคนรุ่นหลังที่ต้องบำรุงรักษาซอฟต์แวร์ของเรา เราควรออกแบบมันโดยคำนึงถึงการบำรุงรักษา แม้ว่าเราไม่สามารถคาดเดาได้เสมอไป ว่าการตัดสินใจใดอาจสร้างปัญหาการบำรุงรักษาในอนาคต ในหนังสือเล่มนี้เราจะให้ความสนใจ กับหลักการหลายประการที่สามารถนำไปใช้ได้อย่างกว้างขวาง:

Operability (ความสามารถในการปฏิบัติการ)

ทำให้ง่ายสำหรับองค์กรในการทำให้ระบบทำงานได้อย่างราบรื่น

Simplicity (ความเรียบง่าย)

ทำให้วิศวกรใหม่เข้าใจระบบได้ง่าย โดยการ implement โดยใช้ patterns และโครงสร้าง ที่เป็นที่รู้จักและสอดคล้องกัน และหลีกเลี่ยงความซับซ้อนที่ไม่จำเป็น

Evolvability (ความสามารถในการพัฒนา)

ทำให้วิศวกรสามารถเปลี่ยนแปลงระบบในอนาคตได้ง่าย ปรับและขยายสำหรับกรณีการใช้งานที่ไม่คาดคิด เมื่อข้อกำหนดเปลี่ยนแปลง

Operability: Making Life Easy for Operations (Operability: ทำให้ชีวิตของทีมปฏิบัติการง่ายขึ้น)

เราได้พูดถึงบทบาทของการปฏิบัติการก่อนหน้านี้ใน "Operations in the Cloud Era" และเราเห็นว่ากระบวนการของมนุษย์มีความสำคัญสำหรับการปฏิบัติการที่เชื่อถือได้ อย่างน้อยเท่ากับเครื่องมือซอฟต์แวร์ ในความเป็นจริง มีการเสนอว่า "การปฏิบัติการที่ดีมักสามารถทำงานรอบข้อจำกัดของซอฟต์แวร์ที่ไม่ดี (หรือไม่สมบูรณ์) ได้ แต่ซอฟต์แวร์ที่ดีไม่สามารถทำงานได้อย่างน่าเชื่อถือด้วยการปฏิบัติการที่ไม่ดี" [ 62 ]

ในระบบขนาดใหญ่ที่ประกอบด้วยเครื่องหลายพันเครื่อง การบำรุงรักษาด้วยมือจะมีค่าใช้จ่ายสูงเกินไป และระบบอัตโนมัติเป็นสิ่งสำคัญ อย่างไรก็ตาม ระบบอัตโนมัติอาจเป็นดาบสองคม จะมีกรณีขอบ (เช่น สถานการณ์ความล้มเหลวที่หายาก) ที่ต้องการการแทรกแซงด้วยมือ จากทีมปฏิบัติการ และเนื่องจากกรณีที่ไม่สามารถจัดการได้โดยอัตโนมัติมักจะซับซ้อนที่สุด ระบบอัตโนมัติที่มากขึ้นจึงต้องการทีมปฏิบัติการที่ มีทักษะมากขึ้น เพื่อแก้ไขปัญหาเหล่านั้น [ 90 ]

นอกจากนี้ ระบบอัตโนมัติที่ทำงานผิดปกติมักจะแก้ไขปัญหาได้ยากกว่า ระบบที่พึ่งพาผู้ปฏิบัติการในการดำเนินการบางอย่างด้วยมือ ด้วยเหตุผลนั้น ระบบอัตโนมัติที่มากขึ้นไม่ได้ดีกว่าเสมอไปสำหรับ operability อย่างไรก็ตาม ระบบอัตโนมัติในระดับหนึ่งก็สำคัญ — จุดที่เหมาะสมจะขึ้นอยู่กับรายละเอียดเฉพาะของแอปพลิเคชัน และองค์กรของคุณ

operability ที่ดีหมายถึงการทำให้งานประจำง่ายขึ้น ทำให้ทีมปฏิบัติการสามารถมุ่งเน้นไปที่ กิจกรรมที่มีมูลค่าสูง ระบบข้อมูลสามารถช่วยได้โดยการทำสิ่งต่อไปนี้ [ 91 ]:

  • อนุญาตให้เครื่องมือ monitoring ตรวจสอบ metrics หลักของระบบ และสนับสนุนเครื่องมือ observability (ดู "Problems with Distributed Systems" ) เพื่อให้ข้อมูลเชิงลึกเกี่ยวกับพฤติกรรมรันไทม์ของระบบ เครื่องมือเชิงพาณิชย์และโอเพนซอร์สที่หลากหลายสามารถช่วยได้ที่นี่ [ 92 ]

  • หลีกเลี่ยงการพึ่งพาเครื่องจักรแต่ละเครื่อง (อนุญาตให้นำเครื่องลงเพื่อบำรุงรักษาในขณะที่ระบบโดยรวมยังคงทำงานได้อย่างต่อเนื่อง)

  • มีเอกสารที่ดีและโมเดลการปฏิบัติการที่เข้าใจง่าย ("ถ้าฉันทำ X, Y จะเกิดขึ้น")

  • มีพฤติกรรมเริ่มต้นที่ดี แต่ยังให้อิสระแก่ผู้ดูแลระบบในการแทนที่ค่าเริ่มต้นเมื่อจำเป็น

  • การรักษาตัวเองเมื่อเหมาะสม แต่ยังให้การควบคุมด้วยมือแก่ผู้ดูแลระบบ เกี่ยวกับสถานะของระบบเมื่อจำเป็น

  • แสดงพฤติกรรมที่คาดเดาได้ ลดความประหลาดใจ

Simplicity: Managing Complexity (Simplicity: การจัดการความซับซ้อน)

โปรเจกต์ซอฟต์แวร์ขนาดเล็กสามารถมีโค้ดที่เรียบง่ายและสื่อความหมายได้อย่างน่าพอใจ แต่เมื่อโปรเจกต์ใหญ่ขึ้น มันมักจะซับซ้อนและเข้าใจยาก ความซับซ้อนนี้ทำให้ทุกคน ที่ต้องทำงานบนระบบช้าลง ซึ่งเพิ่มต้นทุนการบำรุงรักษามากขึ้นไปอีก โปรเจกต์ซอฟต์แวร์ที่จมอยู่กับความซับซ้อนบางครั้งถูกอธิบายว่าเป็น big ball of mud (ก้อนโคลนก้อนใหญ่) [ 93 ]

เมื่อความซับซ้อนทำให้การบำรุงรักษายาก งบประมาณและกำหนดการมักจะเกินควบคุม ในซอฟต์แวร์ที่ซับซ้อน ยังมีความเสี่ยงสูงที่จะเกิดบั๊กเมื่อทำการเปลี่ยนแปลง เมื่อระบบยากที่นักพัฒนาจะเข้าใจและใช้เหตุผลเกี่ยวกับมัน สมมติฐานที่ซ่อนอยู่ ผลที่ตามมาที่ไม่ได้ตั้งใจ และปฏิสัมพันธ์ที่ไม่คาดคิดก็จะถูกมองข้ามได้ง่าย [ 71 ] ในทางกลับกัน การลดความซับซ้อนช่วยปรับปรุงความสามารถในการบำรุงรักษาของซอฟต์แวร์ได้อย่างมาก ดังนั้นความเรียบง่ายควรเป็นเป้าหมายสำคัญสำหรับระบบที่เราสร้าง

ระบบที่เรียบง่ายนั้นเข้าใจง่ายกว่า ดังนั้นเราควรพยายามแก้ปัญหาที่กำหนด ในวิธีที่ง่ายที่สุดเท่าที่จะเป็นไปได้ น่าเสียดายที่พูดง่ายกว่าทำ ว่าสิ่งไหนเรียบง่ายหรือไม่นั้นมักเป็นเรื่องของอัตวิสัย เนื่องจากไม่มีมาตรฐานวัตถุวิสัยของความเรียบง่าย [ 94 ] ตัวอย่างเช่น ระบบหนึ่งอาจซ่อนการ implement ที่ซับซ้อนไว้หลังอินเทอร์เฟซที่เรียบง่าย ในขณะที่อีกระบบหนึ่งอาจมีการ implement ที่เรียบง่ายแต่เปิดเผยรายละเอียดภายในให้ผู้ใช้เห็นมากขึ้น — ระบบไหนเรียบง่ายกว่ากัน?

ความพยายามครั้งหนึ่งในการใช้เหตุผลเกี่ยวกับความซับซ้อนแบ่งมันออกเป็นสองประเภท: essential และ accidental [ 95 ] แนวคิดคือ essential complexity (ความซับซ้อนที่จำเป็น) มีอยู่ใน domain ปัญหาของแอปพลิเคชัน ในขณะที่ accidental complexity (ความซับซ้อนที่เกิดขึ้นโดยบังเอิญ) เกิดขึ้นเนื่องจากข้อจำกัดของเครื่องมือของเราเท่านั้น น่าเสียดายที่ความแตกต่างนี้ก็มีข้อบกพร่องเช่นกัน เพราะขอบเขตระหว่าง essential และ accidental เปลี่ยนไปเมื่อเครื่องมือของเราพัฒนาขึ้น [ 96 ]

หนึ่งในเครื่องมือที่ดีที่สุดที่เรามีสำหรับการจัดการความซับซ้อนคือ abstraction (นามธรรม) abstraction ที่ดีสามารถซ่อนรายละเอียดการ implement จำนวนมาก ไว้เบื้องหลัง façade ที่สะอาดและเข้าใจง่าย abstraction ที่ดียังสามารถใช้ได้ สำหรับแอปพลิเคชันที่หลากหลาย ไม่เพียงแต่การ reuse นี้จะมีประสิทธิภาพ มากกว่าการ implement สิ่งที่คล้ายกันซ้ำหลายครั้ง แต่มันยังนำไปสู่ซอฟต์แวร์ที่มีคุณภาพสูงขึ้น เพราะการปรับปรุงคุณภาพใน component ที่ถูกทำเป็น abstraction จะเป็นประโยชน์ต่อทุกแอปพลิเคชันที่ใช้มัน

ตัวอย่างเช่น ภาษาโปรแกรมระดับสูงเป็น abstractions ที่ซ่อน machine code, CPU registers, และ system calls SQL คือ abstraction ที่ซ่อนโครงสร้างข้อมูลบนดิสก์และในหน่วยความจำที่ซับซ้อน requests ที่แข่งขันจาก client อื่น ๆ และความไม่สอดคล้องหลังจาก crash แน่นอน เมื่อเขียนโปรแกรมด้วยภาษาระดับสูง เรายังคงใช้ machine code; เราแค่ไม่ได้ใช้มัน โดยตรง เพราะ abstraction ของภาษาโปรแกรมช่วยเราไม่ต้องคิดถึงมัน

Abstractions สำหรับโค้ดแอปพลิเคชันที่มุ่งหวังจะลดความซับซ้อนของมัน สามารถสร้างขึ้นได้โดยใช้ methodologies เช่น design patterns (รูปแบบการออกแบบ) [ 97 ] และ domain-driven design (DDD) (การออกแบบที่ขับเคลื่อนด้วยโดเมน) [ 98 ] หนังสือเล่มนี้ไม่ได้เกี่ยวกับ abstractions ที่เฉพาะเจาะจงกับแอปพลิเคชันแบบนั้น แต่เกี่ยวกับ abstractions ที่ใช้งานทั่วไปซึ่งคุณสามารถสร้างแอปพลิเคชันของคุณบนมันได้ เช่น database transactions, indexes, และ event logs ถ้าคุณต้องการใช้เทคนิคเช่น DDD คุณสามารถ implement มันบนพื้นฐานที่อธิบายไว้ในหนังสือเล่มนี้

Evolvability: Making Change Easy (Evolvability: ทำให้การเปลี่ยนแปลงง่าย)

เป็นไปได้อย่างยิ่งที่ข้อกำหนดของระบบของคุณจะไม่คงอยู่ตลอดไป พวกมันมีแนวโน้มที่จะเปลี่ยนแปลงอยู่ตลอดเวลา: คุณเรียนรู้ข้อเท็จจริงใหม่ กรณีการใช้งานที่ไม่เคยคาดคิดมาก่อนเกิดขึ้น ลำดับความสำคัญทางธุรกิจเปลี่ยน ผู้ใช้ขอฟีเจอร์ใหม่ แพลตฟอร์มใหม่แทนที่แพลตฟอร์มเก่า ข้อกำหนดทางกฎหมายหรือข้อบังคับเปลี่ยน การเติบโตของระบบบังคับให้ต้องเปลี่ยนแปลงสถาปัตยกรรม ฯลฯ

ในแง่ของกระบวนการขององค์กร Agile working patterns (รูปแบบการทำงานแบบ Agile) เป็นกรอบสำหรับการปรับตัวต่อการเปลี่ยนแปลง ชุมชน Agile ยังได้พัฒนาเครื่องมือและกระบวนการทางเทคนิคที่มีประโยชน์ เมื่อสร้างซอฟต์แวร์ในสภาพแวดล้อมที่เปลี่ยนแปลงบ่อย เช่น test-driven development (TDD) และ refactoring ในหนังสือเล่มนี้ เราค้นหาวิธีการเพิ่มความคล่องตัวในระดับของระบบ ที่ประกอบด้วยหลายแอปพลิเคชันหรือบริการที่มีคุณลักษณะต่างกัน

ความง่ายในการปรับเปลี่ยนระบบข้อมูลและปรับให้เข้ากับข้อกำหนดที่เปลี่ยนแปลง เชื่อมโยงอย่างใกล้ชิดกับความเรียบง่ายและ abstractions ของมัน ระบบที่ loosely coupled และเรียบง่ายมักจะปรับเปลี่ยนได้ง่ายกว่าระบบที่ tightly coupled และซับซ้อน เนื่องจากนี่เป็นแนวคิดที่สำคัญ เราจะใช้คำที่แตกต่างกัน เพื่ออ้างถึงความคล่องตัวในระดับระบบข้อมูล: evolvability (ความสามารถในการพัฒนา) [ 99 ]

ปัจจัยสำคัญประการหนึ่งที่ทำให้การเปลี่ยนแปลงในระบบขนาดใหญ่ทำได้ยากคือการไม่สามารถย้อนกลับได้ [ 100 ] ตัวอย่างเช่น สมมติว่าคุณกำลังย้ายจาก database หนึ่งไปยังอีก database หนึ่ง ถ้าคุณไม่สามารถกลับไปยังระบบเก่าได้ในกรณีที่มีปัญหากับระบบใหม่ ความเสี่ยงจะสูงกว่ามากเมื่อเทียบกับถ้าคุณสามารถกลับไปได้ง่าย ดังนั้น การกระทำที่ไม่สามารถย้อนกลับได้จึงต้องดำเนินการอย่างระมัดระวังมาก การลดการไม่สามารถย้อนกลับได้ช่วยปรับปรุงความยืดหยุ่น

Summary (สรุป)

ในบทนี้เราตรวจสอบตัวอย่างหลายประการของ nonfunctional requirements: performance, reliability, scalability, และ maintainability ผ่านหัวข้อเหล่านี้ เรายังได้พบกับหลักการและคำศัพท์ ที่จะเกี่ยวข้องตลอดทั้งเล่มที่เหลือ

เราเริ่มต้นด้วยกรณีศึกษาของการ implement home timelines ใน social network ซึ่งแสดงให้เห็นถึงความท้าทายบางอย่างที่เกิดขึ้นใน scale จากนั้นเราได้พูดถึงวิธีการวัด performance (เช่น การใช้ response time percentiles) และโหลดบนระบบ (เช่น การใช้ throughput metrics) และวิธีการที่ metrics เหล่านี้ถูกใช้ใน SLAs Scalability เป็นแนวคิดที่เกี่ยวข้องอย่างใกล้ชิด: มันมุ่งเน้นที่การมั่นใจว่า performance ยังคงเหมือนเดิมเมื่อโหลดเพิ่มขึ้น เราเห็นหลักการทั่วไปบางประการสำหรับ scalability เช่น การแบ่งงานเป็นส่วนย่อย ๆ ที่สามารถทำงานได้อย่างอิสระ และเราจะลงรายละเอียดทางเทคนิคเพิ่มเติมเกี่ยวกับเทคนิค scalability ในบทต่อ ๆ ไป

เพื่อให้บรรลุ reliability คุณสามารถใช้เทคนิคการทนทานต่อข้อผิดพลาด ซึ่งช่วยให้ระบบยังคงให้บริการต่อไปได้แม้ว่าส่วนประกอบ (เช่น ดิสก์, เครื่องจักร, หรือบริการอื่น) จะมีข้อบกพร่อง เราเห็นตัวอย่างของ hardware faults ที่สามารถเกิดขึ้นได้ และแยกความแตกต่างจาก software faults ซึ่งจัดการได้ยากกว่าเพราะมันมักมีความสัมพันธ์กันสูง อีกแง่มุมหนึ่งของการบรรลุ reliability คือการสร้างความทนทานต่อความผิดพลาดของมนุษย์ และเราได้เห็น blameless postmortems เป็นเทคนิคในการเรียนรู้จากเหตุการณ์

สุดท้าย เราตรวจสอบหลายแง่มุมของ maintainability รวมถึงการสนับสนุนการทำงานของทีมปฏิบัติการ การจัดการความซับซ้อน และการทำให้ง่ายต่อการพัฒนาฟังก์ชันการทำงานของแอปพลิเคชันเมื่อเวลาผ่านไป ไม่มีคำตอบง่าย ๆ สำหรับวิธีการบรรลุเป้าหมายเหล่านี้ แต่วิธีการหนึ่งที่ช่วยได้คือการสร้างแอปพลิเคชัน โดยใช้ building blocks ที่เข้าใจดีซึ่งให้ abstractions ที่มีประโยชน์ ส่วนที่เหลือของหนังสือเล่มนี้จะครอบคลุม building blocks ที่เลือกสรรแล้ว ซึ่งพิสูจน์แล้วว่ามีค่าในทางปฏิบัติ