Chương 1 — Reliable, Scalable, and Maintainable Applications
Phần I — Foundations of Data Systems

Chương 1 — Reliable, Scalable, and Maintainable Applications

12 phút đọc DDIA · Martin Kleppmann

🎯 Mục tiêu chương: Định nghĩa rõ ràng ba mối quan tâm cốt lõi của mọi hệ thống data-intensive — reliability, scalability, maintainability — và đưa ra cách tư duy định lượng (load parameters, percentiles) để thảo luận về chúng thay vì dùng như buzzword.

Bối cảnh: Data-intensive vs compute-intensive

  • Phần lớn ứng dụng hiện nay là data-intensive: giới hạn chính không phải CPU mà là lượng dữ liệu, độ phức tạp của dữ liệu và tốc độ dữ liệu thay đổi.
  • Chúng được lắp ráp từ các "building block" tiêu chuẩn:
    • Database — lưu dữ liệu để đọc lại sau.
    • Cache — nhớ kết quả của thao tác đắt đỏ để tăng tốc đọc.
    • Search index — tìm theo keyword / filter.
    • Stream processing — gửi message đến process khác để xử lý bất đồng bộ.
    • Batch processing — định kỳ "nghiền" một lượng lớn dữ liệu tích lũy.
  • Hiếm ai tự viết storage engine; nhưng việc chọn đúng công cụ và ghép chúng lại khi một công cụ không đủ vẫn là bài toán khó — đó là chủ đề của cả cuốn sách.

Thinking About Data Systems

Tại sao gộp chung dưới khái niệm "data systems"?

  • Database và message queue bề ngoài giống nhau (đều lưu dữ liệu một thời gian) nhưng access pattern khác nhau → performance khác → implementation khác. Vậy vì sao gộp chung?
    • Ranh giới đang mờ đi: Redis là datastore nhưng cũng được dùng như message queue; Apache Kafka là message queue nhưng có durability như database.
    • Một công cụ không còn đủ: yêu cầu ứng dụng đa dạng đến mức phải chia việc cho nhiều công cụ, rồi dùng application code để "khâu" chúng lại.
  • Ví dụ (Figure 1-1): ứng dụng có main database + cache do ứng dụng tự quản lý (Memcached) + full-text search server (Elasticsearch/Solr). Việc giữ cache và index đồng bộ với main DB là trách nhiệm của application code.
  • Khi bọc tất cả sau một API, bạn đã tạo ra một special-purpose data system từ các thành phần general-purpose. Hệ thống ghép này có thể cam kết các guarantee (ví dụ: cache được invalidate đúng khi ghi). → Bạn không chỉ là application developer mà còn là data system designer.

Các câu hỏi khó khi thiết kế

  • Làm sao đảm bảo dữ liệu đúng và đủ ngay cả khi có lỗi nội bộ?
  • Làm sao giữ performance tốt cho client khi một phần hệ thống bị degraded?
  • Làm sao scale khi load tăng? API tốt trông như thế nào?
  • Ngoài ra còn nhiều yếu tố phụ thuộc bối cảnh: kỹ năng team, hệ thống legacy, deadline, khẩu vị rủi ro của tổ chức, ràng buộc pháp lý…

Ba mối quan tâm chính

Thuộc tínhĐịnh nghĩa ngắnCâu hỏi cốt lõi
ReliabilityHệ thống tiếp tục hoạt động đúng (đúng chức năng, đủ performance) kể cả khi gặp nghịch cảnh (lỗi hardware, software, con người)Khi có sự cố, hệ thống còn đúng không?
ScalabilityKhi hệ thống lớn lên (data, traffic, độ phức tạp) phải có cách hợp lý để xử lý sự tăng trưởng đóNếu load tăng theo kiểu X, ta có những lựa chọn gì?
MaintainabilityTheo thời gian, nhiều người khác nhau có thể làm việc với hệ thống một cách productiveNgười sau có vận hành, hiểu, thay đổi được không?

Reliability

Định nghĩa

  • Kỳ vọng thông thường với phần mềm: làm đúng chức năng user mong đợi; chịu được user dùng sai/bất ngờ; performance đủ tốt với load và data volume dự kiến; ngăn truy cập trái phép.
  • Reliability ≈ "tiếp tục hoạt động đúng, ngay cả khi có chuyện sai".

Fault vs Failure

Khái niệmÝ nghĩaPhạm vi
FaultMột thành phần lệch khỏi spec của nóCục bộ
FailureToàn bộ hệ thống ngừng cung cấp dịch vụ cho userToàn cục
  • Không thể đưa xác suất fault về 0 → mục tiêu là thiết kế cơ chế fault-tolerance để fault không lan thành failure. Đây là ý tưởng "xây hệ thống reliable từ những phần unreliable".
  • "Fault-tolerant" (hay resilient) hơi gây hiểu lầm: không thể chịu mọi loại fault (ví dụ Trái Đất bị hố đen nuốt). Chỉ nói về việc chịu được một số loại fault nhất định.
  • Chủ động gây fault: nghe ngược đời nhưng hợp lý — nhiều bug nghiêm trọng nằm ở error handling kém. Cố tình kill process ngẫu nhiên giúp cơ chế fault-tolerance luôn được "tập dượt". Ví dụ nổi tiếng: Netflix Chaos Monkey.
  • Thường ưu tiên tolerate hơn prevent, nhưng có ngoại lệ: security — dữ liệu nhạy cảm đã lộ thì không "hoàn tác" được, nên phải phòng ngừa.

Hardware Faults

  • Ổ cứng hỏng, RAM lỗi, mất điện, rút nhầm dây mạng — ở datacenter lớn, chuyện này xảy ra liên tục.
  • Con số đáng nhớ: MTTF của ổ cứng khoảng 10–50 năm → cluster 10.000 ổ đĩa thì trung bình mỗi ngày chết 1 ổ.
  • Phản ứng đầu tiên: redundancy ở mức hardware — RAID, nguồn kép, CPU hot-swap, pin + máy phát diesel cho datacenter. Cách này giữ một máy chạy liên tục nhiều năm; kết hợp với restore backup nhanh là đủ cho phần lớn ứng dụng trước đây.
  • Xu hướng mới: chuyển sang software fault-tolerance (chịu được mất nguyên một máy) vì:
    • Data volume và nhu cầu tính toán tăng → dùng nhiều máy hơn → tỉ lệ hardware fault tăng tương ứng.
    • Cloud như AWS ưu tiên flexibility/elasticity hơn độ tin cậy của một máy; VM có thể biến mất không báo trước.
    • Lợi ích vận hành: hệ thống chịu được mất máy cho phép rolling upgrade — patch từng node một, không cần downtime toàn hệ thống (xem Chương 4).

Software Errors

  • Hardware fault thường ngẫu nhiên và độc lập (tương quan yếu, ví dụ nhiệt độ rack). Software error là systematic error, tương quan giữa các node → gây ra nhiều failure hơn.
  • Ví dụ sách đưa ra:
    • Bug khiến mọi instance crash với cùng một input xấu — ví dụ leap second ngày 30/6/2012 làm nhiều ứng dụng treo đồng thời do bug trong Linux kernel.
    • Runaway process ngốn tài nguyên chung (CPU, memory, disk, network bandwidth).
    • Service phụ thuộc bị chậm, không phản hồi, hoặc trả dữ liệu hỏng.
    • Cascading failures: lỗi nhỏ ở một thành phần kích hoạt lỗi ở thành phần khác, lan dây chuyền.
  • Đặc điểm: bug "ngủ đông" rất lâu, chỉ lộ ra khi một giả định về môi trường (thường đúng) bỗng không còn đúng.
  • Không có giải pháp nhanh, chỉ có nhiều biện pháp nhỏ: suy nghĩ kỹ về giả định và tương tác; test kỹ; process isolation; cho phép process crash và restart; đo đạc, monitor production; tự kiểm tra guarantee khi chạy (ví dụ message queue: số message vào = số message ra, lệch thì alert).

Human Errors

  • Nghiên cứu về các internet service lớn: lỗi cấu hình của operator là nguyên nhân hàng đầu gây outage; hardware fault chỉ góp phần trong 10–25% outage.
  • Các cách kết hợp để hệ thống reliable dù con người unreliable:
    • Thiết kế giảm cơ hội mắc lỗi: abstraction, API, admin UI tốt khiến làm "đúng" dễ hơn làm "sai". Nhưng quá chặt thì người ta sẽ lách → cần cân bằng.
    • Tách nơi dễ sai khỏi nơi gây hậu quả: cung cấp sandbox non-production đầy đủ, dùng dữ liệu thật mà không ảnh hưởng user thật.
    • Test ở mọi cấp: unit → integration toàn hệ thống → manual. Automated test đặc biệt quý cho corner case hiếm gặp.
    • Phục hồi nhanh: rollback config nhanh, roll out code dần dần (bug chỉ ảnh hưởng một nhóm nhỏ user), có công cụ recompute data nếu logic cũ sai.
    • Monitoring chi tiết (performance metrics, error rate) — gọi là telemetry trong các ngành kỹ thuật khác (như tên lửa đã rời bệ phóng).
    • Quản lý và đào tạo tốt (ngoài phạm vi sách).

How Important Is Reliability?

  • Không chỉ nhà máy hạt nhân hay kiểm soát không lưu mới cần reliability: bug trong business app gây mất năng suất (và rủi ro pháp lý nếu báo cáo sai số liệu); e-commerce sập thì mất doanh thu và uy tín.
  • Ví dụ cảm xúc: phụ huynh lưu toàn bộ ảnh/video con cái trong app ảnh của bạn — nếu database bị corrupt thì sao?
  • Có thể chủ động hy sinh reliability để giảm chi phí phát triển (prototype cho thị trường chưa kiểm chứng) hoặc chi phí vận hành (margin thấp) — nhưng phải ý thức rõ mình đang cắt góc.

Scalability

  • Hệ thống reliable hôm nay chưa chắc reliable ngày mai — lý do phổ biến là load tăng (10K → 100K user đồng thời, 1M → 10M).
  • Scalability = khả năng đối phó với load tăng. Nó không phải nhãn một chiều: nói "X scalable" hay "Y không scale" là vô nghĩa. Câu hỏi đúng: "Nếu hệ thống tăng trưởng theo cách cụ thể này, ta có những lựa chọn nào?" và "Thêm tài nguyên thế nào để xử lý load thêm?"

Describing Load

  • Trước hết phải mô tả load hiện tại bằng vài con số — load parameters. Tùy kiến trúc: requests/sec vào web server, tỉ lệ read/write của DB, số user active đồng thời trong chat room, cache hit rate… Có khi average quan trọng, có khi bottleneck do một số ít trường hợp cực đoan.

Case study: Twitter (số liệu 11/2012)

  • Hai thao tác chính:
    • Post tweet: trung bình 4.6k req/s, peak >12k req/s.
    • Home timeline: 300k req/s.
  • 12k writes/s thì dễ. Thách thức thật sự là fan-out: mỗi user follow nhiều người và được nhiều người follow.
Approach 1: tính lúc đọc (pull)Approach 2: tính lúc ghi (push / fan-out on write)
Cách làmPost tweet chỉ insert vào bảng tweets toàn cục. Đọc timeline = JOIN tweets, users, follows để lấy tweet của mọi người mình follow, merge theo thời gian (Figure 1-2)Mỗi user có một home timeline cache như "hộp thư". Khi post, tìm tất cả follower và chèn tweet vào cache của từng người (Figure 1-3)
Chi phí ghiRẻĐắt: trung bình ~75 follower/tweet → 4.6k tweets/s thành 345k writes/s vào cache; celebrity >30 triệu follower → một tweet = 30 triệu writes
Chi phí đọcĐắt (query nặng, 300k req/s)Rẻ — kết quả đã tính sẵn
Twitter dùngPhiên bản đầu, nhưng không theo kịp load đọcChuyển sang vì read rate cao hơn write rate gần 2 bậc độ lớn
  • Twitter cố gắng giao tweet tới follower trong vòng 5 giây — với celebrity đây là thách thức lớn.
  • Load parameter then chốt ở đây: phân phối số follower mỗi user (có thể có trọng số theo tần suất tweet), vì nó quyết định fan-out load.
  • Kết cục: hybrid. Đa số user vẫn fan-out on write; một số ít celebrity được loại khỏi fan-out — tweet của họ được fetch riêng và merge lúc đọc (như approach 1). Kết quả: performance ổn định. (Sách quay lại ví dụ này ở Chương 12.)

Describing Performance

  • Hai cách nhìn khi load tăng:
    • Giữ nguyên tài nguyên (CPU, RAM, bandwidth) → performance bị ảnh hưởng thế nào?
    • Muốn giữ nguyên performance → phải tăng tài nguyên bao nhiêu?
  • Throughput (records/s, hoặc tổng thời gian chạy job) quan trọng với batch system như Hadoop. Response time quan trọng với online system.
    • Lý tưởng: thời gian batch job = kích thước dataset / throughput; thực tế lâu hơn do skew và phải chờ task chậm nhất.

Latency vs Response time

Thuật ngữÝ nghĩa
Response timeThời gian client thực sự thấy: service time + network delay + queueing delay
Service timeThời gian thực sự xử lý request
LatencyKhoảng thời gian request chờ được xử lý (đang "latent")
  • Response time phải được coi là phân phối (distribution), không phải một con số. Ngay cả request giống hệt cũng có thời gian khác nhau do: context switch, mất packet + TCP retransmission, GC pause, page fault đọc disk, thậm chí rung động cơ học trong rack (Figure 1-4).

Mean vs Percentiles

  • Mean (trung bình cộng) không cho biết bao nhiêu user thực sự trải nghiệm độ trễ đó → không tốt để đo "trải nghiệm điển hình".
  • Median (p50): một nửa request nhanh hơn, một nửa chậm hơn. Ví dụ median 200 ms. Lưu ý: median tính theo một request; nếu user gửi nhiều request trong session/page, xác suất gặp ít nhất một request chậm hơn median cao hơn nhiều so với 50%.
  • p95, p99, p999: ví dụ p95 = 1.5s nghĩa là 95/100 request nhanh hơn 1.5s. Các percentile cao gọi là tail latencies.
  • Ví dụ Amazon:
    • Đặt yêu cầu response time nội bộ ở p999 (dù chỉ ảnh hưởng 1/1000 request) vì request chậm nhất thường thuộc khách có nhiều dữ liệu nhất = mua nhiều nhất = khách giá trị nhất.
    • Tăng 100 ms response time → giảm 1% doanh số; nơi khác báo chậm 1 giây → giảm 16% chỉ số hài lòng.
    • Nhưng tối ưu p9999 bị coi là quá đắt, lợi ích giảm dần, và dễ bị chi phối bởi sự kiện ngẫu nhiên ngoài tầm kiểm soát.
  • SLO / SLA: ví dụ SLA quy định service "up" nếu median dưới 200 ms và p99 dưới 1 s, và phải up ≥ 99.9% thời gian. Không đạt → khách đòi hoàn tiền.

Queueing delay & head-of-line blocking

  • Ở percentile cao, queueing delay chiếm phần lớn response time. Server chỉ xử lý song song được ít việc (giới hạn bởi số core) → vài request chậm chặn các request sau (head-of-line blocking), dù bản thân các request sau xử lý rất nhanh.
  • Hệ quả: đo response time ở phía client.
  • Khi load test: client tạo load phải gửi request độc lập với response time. Nếu chờ request trước xong mới gửi tiếp, hàng đợi sẽ ngắn giả tạo → số đo sai.

Percentiles in Practice

  • Tail latency amplification (Figure 1-5): một request của end-user gọi nhiều backend song song → phải chờ call chậm nhất. Chỉ cần một call chậm là cả request chậm. Càng nhiều backend call, tỉ lệ end-user request bị chậm càng cao.
  • Tính percentile liên tục cho dashboard: ví dụ rolling window 10 phút, mỗi phút tính lại median và percentile.
    • Naive: giữ list và sort mỗi phút.
    • Hiệu quả hơn: thuật toán xấp xỉ như forward decay, t-digest, HdrHistogram.
    • ⚠️ Lấy trung bình các percentile (gộp theo thời gian hoặc gộp nhiều máy) là vô nghĩa về mặt toán học — cách đúng là cộng các histogram.

Approaches for Coping with Load

  • Kiến trúc phù hợp với một mức load khó chịu được gấp 10 lần load đó → service tăng trưởng nhanh thường phải nghĩ lại kiến trúc ở mỗi bậc độ lớn (hoặc thường xuyên hơn).
Khía cạnhScaling up (vertical)Scaling out (horizontal / shared-nothing)
Cách làmChuyển sang máy mạnh hơnPhân tán load ra nhiều máy nhỏ hơn
Ưu điểmĐơn giản hơnVượt được giới hạn một máy, chi phí phần cứng hợp lý
Nhược điểmMáy cao cấp rất đắt, có trầnPhức tạp hơn nhiều, nhất là với stateful data system
  • Thực tế: kiến trúc tốt thường kết hợp thực dụng — vài máy khá mạnh có thể đơn giản và rẻ hơn rất nhiều VM nhỏ.
  • Elastic (tự thêm tài nguyên khi phát hiện load tăng) hữu ích khi load khó đoán; scale thủ công đơn giản hơn, ít bất ngờ khi vận hành (xem "Rebalancing Partitions", Chương 6).
  • Stateless service phân tán dễ; stateful data system từ single node lên distributed thêm rất nhiều phức tạp → "common wisdom" trước đây: giữ DB trên một node (scale up) cho đến khi chi phí hoặc yêu cầu high availability buộc phải phân tán. Khi công cụ phân tán tốt dần, điều này có thể thay đổi.
  • Không có "magic scaling sauce": kiến trúc ở quy mô lớn rất đặc thù ứng dụng. Vấn đề có thể là volume read, volume write, lượng data, độ phức tạp data, yêu cầu response time, access pattern, hoặc (thường) tổ hợp tất cả.
    • Ví dụ: hệ thống 100.000 req/s, mỗi request 1 kB trông rất khác hệ thống 3 req/phút, mỗi request 2 GB — dù cùng data throughput.
  • Kiến trúc scale tốt được xây trên giả định về thao tác nào phổ biến/hiếm (load parameters). Giả định sai → công sức scale lãng phí, tệ hơn là phản tác dụng. Với startup/sản phẩm chưa kiểm chứng: iterate nhanh quan trọng hơn scale cho load giả định.
  • Dù đặc thù, kiến trúc scalable vẫn được xây từ building block general-purpose sắp xếp theo pattern quen thuộc — nội dung của các chương sau.

Maintainability

  • Phần lớn chi phí phần mềm nằm ở bảo trì liên tục (fix bug, vận hành, điều tra sự cố, port sang platform mới, thêm use case, trả technical debt, thêm feature), không phải phát triển ban đầu.
  • Mục tiêu: thiết kế sao cho giảm đau khi bảo trì, tránh tự tạo ra legacy system. Ba nguyên tắc:
Nguyên tắcMục tiêuPhục vụ ai
OperabilityGiúp team vận hành giữ hệ thống chạy trơn truOperations
SimplicityEngineer mới dễ hiểu hệ thống, loại bỏ complexity (không phải sự đơn giản của UI)Engineer hiện tại & tương lai
EvolvabilityDễ thay đổi hệ thống cho use case chưa lường trước (còn gọi extensibility, modifiability, plasticity)Engineer khi requirement đổi

Operability: Making Life Easy for Operations

  • Câu nổi tiếng (paraphrase): operations tốt có thể bù cho phần mềm tồi, nhưng phần mềm tốt không chạy ổn với operations tồi.
  • Trách nhiệm của một team ops tốt (tóm tắt): monitor sức khỏe và khôi phục nhanh; truy nguyên nhân sự cố/giảm performance; cập nhật phần mềm và security patch; theo dõi các hệ thống ảnh hưởng lẫn nhau; capacity planning; xây dựng công cụ deploy/config management; thực hiện việc bảo trì phức tạp (migrate platform); giữ security khi đổi config; quy trình giúp production ổn định và dự đoán được; lưu giữ tri thức tổ chức khi người đến người đi.
  • Data system có thể giúp operability bằng cách:
    • Cung cấp visibility vào runtime behavior và internals (monitoring tốt).
    • Hỗ trợ tốt automation và tích hợp công cụ chuẩn.
    • Không phụ thuộc vào từng máy riêng lẻ (có thể tắt máy để bảo trì mà hệ thống vẫn chạy).
    • Tài liệu tốt, mô hình vận hành dễ hiểu ("làm X thì Y xảy ra").
    • Default tốt nhưng admin được override.
    • Self-healing khi phù hợp, nhưng vẫn cho admin kiểm soát thủ công.
    • Hành vi dự đoán được, ít bất ngờ.

Simplicity: Managing Complexity

  • Dự án lớn dần sẽ phức tạp, làm chậm mọi người → tăng chi phí bảo trì. Dự án sa lầy gọi là big ball of mud.
  • Triệu chứng complexity: bùng nổ state space, tight coupling giữa module, dependency rối, đặt tên/thuật ngữ không nhất quán, hack để giải quyết performance, special-case để lách vấn đề ở chỗ khác…
  • Hậu quả: vượt ngân sách/tiến độ; dễ sinh bug khi thay đổi vì các giả định ẩn và tương tác bất ngờ bị bỏ sót.
  • Accidental vs essential complexity (Moseley & Marks): complexity là accidental nếu nó không vốn có trong bài toán (từ góc nhìn user) mà chỉ sinh ra từ implementation. Đơn giản hóa ≠ cắt chức năng; đơn giản hóa = loại bỏ accidental complexity.
  • Công cụ tốt nhất: abstraction — che giấu implementation detail sau một "façade" sạch sẽ, và tái sử dụng được cho nhiều ứng dụng (cải thiện chất lượng component trừu tượng thì mọi ứng dụng cùng hưởng lợi).
    • Ngôn ngữ bậc cao che machine code, register, syscall.
    • SQL che cấu trúc dữ liệu on-disk/in-memory, concurrent request, sự không nhất quán sau crash.
  • Tìm abstraction tốt rất khó, nhất là trong distributed systems: có nhiều thuật toán tốt nhưng chưa rõ cách đóng gói chúng thành abstraction giữ complexity ở mức kiểm soát được.

Evolvability: Making Change Easy

  • Requirement luôn thay đổi: học được điều mới, use case mới, ưu tiên business đổi, user đòi feature, platform mới thay cũ, luật thay đổi, tăng trưởng buộc đổi kiến trúc…
  • Agile, TDD, refactoring hỗ trợ thay đổi — nhưng thường ở quy mô nhỏ (vài file trong một app). Sách quan tâm tới agility ở cấp data system gồm nhiều service. Ví dụ: làm sao "refactor" kiến trúc timeline của Twitter từ approach 1 sang approach 2?
  • Evolvability gắn chặt với simplicity và abstraction: hệ thống đơn giản, dễ hiểu thì dễ sửa.

Summary — Tóm tắt chương

  • Ứng dụng có functional requirements (làm gì) và nonfunctional requirements (security, reliability, compliance, scalability, compatibility, maintainability).
  • Reliability: hoạt động đúng khi có fault. Fault đến từ hardware (ngẫu nhiên, không tương quan), software (systematic, khó xử lý), con người (không tránh khỏi). Kỹ thuật fault-tolerance che giấu một số loại fault khỏi user.
  • Scalability: có chiến lược giữ performance tốt khi load tăng; cần mô tả load và performance định lượng (Twitter timeline, response time percentiles).
  • Maintainability: làm cuộc sống tốt hơn cho team engineering và ops — abstraction tốt giảm complexity; operability tốt = visibility + công cụ quản lý hiệu quả.

⚠️ Hiểu lầm & cạm bẫy thường gặp

  • Nhầm fault với failure — mục tiêu không phải loại bỏ fault (bất khả thi) mà ngăn fault thành failure.
  • "Hệ thống X scalable" — câu vô nghĩa nếu không nói scale theo load parameter nào, tăng trưởng theo hướng nào.
  • Dùng average response time làm SLA — che giấu tail latency; hãy dùng p50/p95/p99/p999.
  • Lấy trung bình các percentile từ nhiều máy hoặc nhiều khoảng thời gian — sai toán học; phải gộp histogram.
  • Đo latency chỉ ở server — bỏ sót queueing delay và head-of-line blocking; hãy đo ở client.
  • Load test kiểu closed-loop (chờ response rồi mới gửi tiếp) — hàng đợi ngắn giả tạo, kết quả lạc quan sai.
  • Chỉ dựa vào hardware redundancy — không đủ khi dùng nhiều máy/cloud; cần software fault-tolerance.
  • Coi software bug là độc lập như hardware fault — software error tương quan, có thể hạ mọi node cùng lúc (leap second 2012).
  • Đổ lỗi cho con người thay vì thiết kế hệ thống giảm cơ hội sai, sandbox, rollback nhanh, rollout dần.
  • Tối ưu scale quá sớm dựa trên giả định load sai — lãng phí hoặc phản tác dụng; startup nên ưu tiên tốc độ iterate.
  • Nghĩ "simplicity" là bớt tính năng — thực ra là loại bỏ accidental complexity bằng abstraction.
  • Nghĩ có một kiến trúc scale "vạn năng" — không có magic scaling sauce.

💼 Áp dụng thực tế & phỏng vấn

  • Mở đầu mọi bài system design bằng load parameters: DAU, QPS read/write, tỉ lệ read:write, kích thước dữ liệu, phân phối (ví dụ follower distribution, hot key). Đây chính là bước "Describing Load".
  • Bài "Design Twitter / News Feed / Instagram feed": gần như chắc chắn bị hỏi fan-out on write vs fan-out on read, và cách xử lý celebrity → trả lời bằng hybrid như Twitter. Nêu con số: 4.6k tweets/s × 75 follower ≈ 345k writes/s.
  • Non-functional requirements trong phỏng vấn: nói rõ SLO dạng percentile, ví dụ "p99 dưới 200 ms cho đọc timeline, availability 99.9%".
  • Tail latency amplification giải thích vì sao microservice fan-out nhiều cần timeout, hedged requests, hoặc giảm số call đồng bộ.
  • Monitoring thực tế: Prometheus histogram, HdrHistogram, t-digest; dashboard hiển thị p50/p95/p99 thay vì mean.
  • Reliability practices: chaos engineering (Chaos Monkey), canary/gradual rollout, feature flag, rollback nhanh, staging/sandbox với dữ liệu thật đã ẩn danh.
  • Scale up vs scale out: trong phỏng vấn nên thể hiện sự thực dụng — "bắt đầu với một DB mạnh + read replica, chỉ shard khi cần" và giải thích vì sao stateful khó phân tán.
  • Maintainability: khi được hỏi về trade-off, nhắc đến operability (observability, runbook, không phụ thuộc máy đơn lẻ, rolling upgrade) cho thấy tư duy senior.

❓ Câu hỏi ôn tập

Bấm vào câu hỏi để xem đáp án
1Phân biệt fault và failure. Tại sao lại cố tình gây fault trong production?
Fault là một thành phần lệch khỏi spec; failure là cả hệ thống ngừng phục vụ user. Cố tình gây fault (Chaos Monkey) giúp cơ chế fault-tolerance liên tục được kiểm thử vì nhiều bug nghiêm trọng nằm ở error handling kém.
2Vì sao software error thường nguy hiểm hơn hardware fault?
Hardware fault ngẫu nhiên và gần như độc lập; software error là systematic, tương quan giữa các node (ví dụ bug leap second 2012, cascading failure) nên có thể làm sập nhiều node cùng lúc.
3Twitter đã chuyển từ cách nào sang cách nào để xây home timeline, và tại sao cuối cùng dùng hybrid?
Từ query/JOIN lúc đọc sang fan-out on write vào cache timeline mỗi user, vì đọc nhiều hơn ghi gần 100 lần. Nhưng celebrity có hàng chục triệu follower khiến fan-out quá đắt, nên tweet của celebrity được fetch và merge lúc đọc.
4Load parameter quan trọng nhất trong ví dụ Twitter là gì?
Phân phối số follower mỗi user (có thể có trọng số theo tần suất tweet), vì nó quyết định fan-out load.
5Vì sao percentile tốt hơn mean để đo response time? Amazon chọn percentile nào và tại sao?
Mean không cho biết bao nhiêu user thực sự chịu độ trễ đó; percentile mô tả phân phối và tail. Amazon dùng p999 vì request chậm nhất thường thuộc khách hàng nhiều dữ liệu nhất, giá trị nhất; p9999 thì quá đắt để tối ưu.
6Tail latency amplification là gì?
Khi một request end-user cần nhiều backend call, nó phải chờ call chậm nhất; càng nhiều call thì xác suất gặp ít nhất một call chậm càng cao, nên tỉ lệ request end-user chậm tăng lên.
7Tại sao không được lấy trung bình các giá trị p99 từ nhiều server?
Trung bình các percentile vô nghĩa về mặt toán học; cách đúng là gộp (cộng) histogram rồi tính percentile trên histogram tổng.
8Ba nguyên tắc của maintainability là gì, và công cụ chính để đạt simplicity?
Operability, Simplicity, Evolvability. Công cụ chính cho simplicity là abstraction tốt nhằm loại bỏ accidental complexity (ví dụ SQL che giấu storage, concurrency, crash recovery).

Đây là bản tóm tắt và ghi chú, không thay thế sách gốc. Hãy ủng hộ tác giả bằng cách đọc bản gốc.