Nguyen Le Phong

Nền tảng Kiến trúc Phần mềmPhần 11/11

Bạn Không Thể Sửa Thứ Bạn Không Thấy: Logs, Metrics, Traces và SLO ở Quy mô Lớn

Trong monolith, debug gần như ấm cúng — một log file, một process, một chỗ sự thật cư ngụ. Distributed systems lặng lẽ lấy đi điều đó: một request giờ toả ra cả tá service, và khi nó hỏng chẳng còn một log file để đọc. Một hướng dẫn không màu mè về việc nhìn thấy hệ thống ở quy mô lớn: ba trụ cột (metrics, logs, traces) và câu hỏi mỗi cái trả lời, vì sao một trace ID lan khắp hệ thống là thói quen đòn bẩy cao nhất, SLO biến reliability thành một error budget bạn có thể tiêu, và alert vào triệu chứng để on-call không kiệt sức.

Khi hệ thống còn là một monolith trên một máy, debug gần như ấm cúng. Một log file. Một process để gắn debugger vào. Một chỗ sự thật cư ngụ. Reproduce con bug, đọc stack trace, sửa. Distributed systems lặng lẽ lấy đi sự tiện nghi đó. Một request checkout giờ toả ra cả tá service trên cả tá máy, và khi nó chậm hay hỏng, chẳng có một log file nào và chẳng có ai để ssh vào. Bạn không thể sửa thứ bạn không thấy — và ở quy mô lớn, việc nhìn thấy là một kỷ luật riêng.

Kỷ luật đó đáng được tách khỏi người anh em già hơn của nó. Monitoring là ngồi canh những dashboard bạn dựng sẵn, cho những thất bại bạn đã đoán trước — “CPU cao,” “disk đầy.” Observability là khả năng đặt những câu hỏi hoàn toàn mới cho một hệ thống đang chạy — kể cả những câu bạn chưa từng nghĩ tới — mà không phải ship code mới để trả lời. Monitoring báo cho bạn rằng có gì sai; observability cho bạn tìm ra vì sao. Ở quy mô lớn bạn cần cả hai, và cả hai được dựng từ ba loại dữ liệu.

Ba cột mang nhãn Metrics, Logs và Traces, mỗi cột trả lời một câu hỏi khác nhau, được buộc lại ở dưới bằng một trace ID chung. BA GÓC NHÌN CỦA MỘT HỆ THỐNG ĐANG CHẠY Metrics “Có gì sai không?” số theo thời gian · rẻ · tổng hợp Logs “Chính xác đã xảy ra gì?” sự kiện chi tiết · đắt khi nhiều Traces “Thời gian đi đâu mất?” một request, xuyên service một trace ID đóng dấu lên cả ba → mười log file thành một câu chuyện
Ba trụ cột trả lời ba câu hỏi khác nhau — sai lầm là bắt một cái làm cả ba việc. Một trace ID chung là sợi chỉ khâu chúng thành một câu chuyện duy nhất.

Ba trụ cột: metrics, logs, traces

Mỗi trụ cột trả lời một câu hỏi khác nhau. Sai lầm kinh điển là cố bắt một cái làm cả ba việc — chết đuối trong logs để tính thứ một metric nên tính, hay dựng dashboard để đuổi theo thứ một trace chỉ ra trong vài giây.

  • Metrics là những con số theo thời gian: số request, số lỗi, p99 latency, độ sâu hàng đợi. Chúng rẻ để lưu vì đã được tổng hợp, nên hoàn hảo cho dashboard và alert “có gì sai ngay bây giờ không?” Thứ chúng không nói được là request nào hay user nào. Metrics là chuông báo khói, không phải cuộc điều tra.
  • Logs là những sự kiện rời rạc, chi tiết: “order 8123 fail validation: thiếu địa chỉ giao hàng.” Chúng trả lời “chính xác đã xảy ra gì trong ca cụ thể này?” Cái giá là khối lượng — ở quy mô lớn, logs thường là dòng đắt nhất trên hóa đơn — và một log dạng chuỗi không cấu trúc thì gần như vô dụng để truy vấn. Hãy cấu trúc hóa chúng.
  • Traces theo dõi một request khi nó đi xuyên qua các service, chia thành những span có đo thời gian. Đây là trụ cột mà distributed systems buộc phải sinh ra: nó trả lời “thời gian đi đâu mất?” và “chặng nào lỗi?” khi câu trả lời bị trải ra trên mười cái máy.
// Không cấu trúc — ok trên một máy, vô dụng xuyên năm mươi máy
console.log(`Order ${id} fail cho ${userId}: ${err.message}`)

// Có cấu trúc — truy vấn được, và buộc vào cả request qua trace_id
log.error({
  event: "order_failed",
  trace_id: ctx.traceId,   // cùng một id chảy qua mọi service
  order_id: id,
  user_id: userId,
  error: err.message,
})

Sợi chỉ buộc chúng lại: một trace ID

Đây là thói quen có đòn bẩy cao nhất trong cả bài này. Cấp cho mỗi request một ID ngay ở biên, và lan nó qua mọi service nó chạm tới — đóng dấu nó lên mọi dòng log và mọi span. Đây là context propagation, và nó là khác biệt giữa mười log file rời rạc vô dụng và một câu chuyện mạch lạc: “cho tôi xem mọi thứ đã xảy ra với request abc-123.”

Chuẩn mở để sinh và lan dữ liệu này là OpenTelemetry (OTel) — một cách trung lập với nhà cung cấp để phát metrics, logs và traces, để bạn không bị cưới chết vào một backend. Hãy áp dụng sớm; gắn trace propagation vào một đàn service đã trưởng thành là công việc thực sự khổ sở.

Đọc một request phân tán: trace waterfall

Một trace vẽ mọi span của một request trên một timeline. Đột nhiên span chậm hiện ra ngay tắp lự — “checkout chậm” trở thành “payment-service chậm” trong vài giây, thay vì cả buổi chiều đào bới Slack xuyên team. Đây là phần thưởng biện minh cho chi phí instrument mọi thứ.

Một distributed trace vẽ dưới dạng waterfall: span của mỗi service là một thanh trên một trục thời gian chung, và span của payment-service rộng nhất, lộ ra nó là nút thắt. MỘT REQUEST · MỌI CHẶNG · TRÊN MỘT TIMELINE API Gateway ├ auth-service ├ orders-service │ └ db query └ payment-service payment · 200 ms ← nút thắt 0 100ms 200ms 300ms trace biến “checkout chậm” thành “payment-service chậm” trong vài giây
Một distributed trace vẽ mọi span của một request trên cùng một timeline. Span chậm hiện ra ngay tắp lự — không cần đào bới xuyên team.

Metrics scale được: RED, USE và cái bẫy cardinality

Bạn không cần một nghìn dashboard; bạn cần vài tín hiệu có kỷ luật. Hai công thức quen thuộc che phần lớn:

  • RED, cho service hướng request: Rate (request/giây), Errors (lỗi/giây), Duration (phân phối latency). Ba con số mỗi service bắt được phần lớn nỗi đau của người dùng.
  • USE, cho tài nguyên như CPU, disk và connection pool: Utilization (mức dùng), Saturation (mức bão hòa), Errors.

Rồi tới cái bẫy lặng lẽ làm phá sản các team: cardinality. Một label của metric với giá trị vô hạn — user_id, request_id — nhân lên thành hàng triệu time series riêng biệt, và hóa đơn metrics của bạn nổ tung. Giữ label của metric cardinality thấp (status code, endpoint, region). Chi tiết cardinality cao — user nào, request nào — thuộc về traces và logs, không phải trên một metric.

Cái label làm nổ hóa đơn

Đừng bao giờ đặt một giá trị vô hạn (một user ID, một email, một URL đầy đủ kèm query param) vào label của metric. Mỗi giá trị duy nhất là một time series mới toánh, lưu mãi mãi. Đây là cách phổ biến nhất để một hóa đơn metrics nhỏ biến thành đáng sợ sau một đêm. Câu hỏi cardinality cao thì hỏi traces và logs.

Từ “nó còn sống không?” sang “nó đủ tốt chưa?”: SLI, SLO và error budget

Cú nhảy trưởng thành thật sự là ngừng đo cái máy và bắt đầu đo trải nghiệm của người dùng — như một con số bạn có thể hành động theo.

  • Một SLI (Service Level Indicator) là một tỉ lệ đo được của các sự kiện tốt: phần trăm request được phục vụ dưới 300 ms, hay phần trăm request không phải 5xx.
  • Một SLO (Objective) là mục tiêu bạn cam kết: 99.9% trong 30 ngày cuốn.
  • Error budget là điều kỳ diệu rơi ra từ đó: 100% − 99.9% = 0.1%, qua một tháng là khoảng 43 phút hỏng “được phép.” Reliability trở thành một ngân sách bạn được phép tiêu.
Một thanh ngang biểu diễn error budget của một tháng ở SLO 99.9%, đã được tô một phần để cho thấy đã tiêu bao nhiêu downtime cho phép. RELIABILITY LÀ MỘT CON SỐ BẠN CÓ THỂ TIÊU đã tiêu: ≈ 19 phút còn ≈ 24 phút SLO 99.9% trong 30 ngày ≈ 43 phút “được phép” hỏng mỗi tháng budget khỏe → ship tính năng · budget cạn → dừng lại ổn định
Error budget biến reliability thành thứ bạn có thể tiêu. Khi nó còn khỏe bạn ship mạnh dạn; khi gần cạn, chính con số đó bảo bạn dừng lại và ổn định.

Điều này đặt lại cuộc chiến muôn thuở giữa product (“ship nhanh hơn”) và engineering (“phải đáng tin”) thành một con số chung thay vì một trận cãi vã. Budget khỏe? Ship mạnh dạn, chấp nhận rủi ro. Budget gần cạn? Dừng ship tính năng và ổn định. Và, quan trọng nhất, nó cho bạn biết thứ gì thực sự đáng đánh thức ai đó dậy.

Alert đánh thức bạn vì đúng lý do

Cách nhanh nhất để biến on-call thành địa ngục là alert vào nguyên nhân. “Disk 80%” lúc 3 giờ sáng, mỗi đêm, cho một disk tự động giãn — và chẳng mấy chốc không ai đọc alert nữa, kể cả cái đêm con thật sự nổ. Đó là alert fatigue, và đó là cách những sự cố thật lọt qua.

# Alert vào TRIỆU CHỨNG (người dùng đau), không vào nguyên nhân (một CPU nóng).
# Chỉ page khi ta đang đốt error budget tháng nhanh.
alert: HighErrorBudgetBurn
expr:  error_rate_5m > 14.4 * (1 - 0.999)   # SLO 99.9%, đốt nhanh
for:   5m
then: page on-call · đính kèm link runbook

Hai quy tắc sửa phần lớn. Alert vào triệu chứng, không vào nguyên nhân — page khi người dùng thật sự đau (tốc đốt error budget, error rate tăng, latency vượt SLO), không phải khi một metric nội bộ giật nhẹ. Và mọi page phải vừa khẩn vừa làm được — nếu chẳng có gì để làm ngay phút này, nó là một ticket hay một dashboard, không phải một page lúc 3 giờ sáng. Đính một runbook vào mỗi alert để kỹ sư bị đánh thức có một bước đầu tiên, chứ không chỉ là một cơn hoảng.

Hóa đơn là có thật: sampling và retention

Dữ liệu observability có thể lặng lẽ tốn hơn chính ứng dụng nó canh chừng — đặc biệt là traces và logs. Ba đòn bẩy giữ nó tỉnh táo. Sample traces: giữ 100% lỗi và request chậm, nhưng chỉ một phần nhỏ những request thành công nhàm chán (“tail sampling” quyết định sau khi thấy kết quả). Phân tầng retention: nóng và tìm kiếm được trong vài ngày, rẻ và lạnh trong vài tháng. Và giữ lằn ranh cardinality. Hãy quyết định mấy thứ này có chủ đích — hoặc nhà cung cấp sẽ quyết định giùm bạn, bằng một hóa đơn.

Nên với tới gì

Câu hỏi bạn thực sự đang đặtTrụ cột trả lời nó
Có gì sai ngay bây giờ không?Metrics + alert vào tốc đốt SLO
Thời gian đi đâu mất xuyên service?Distributed traces (waterfall)
Chính xác request này đã gặp gì?Structured logs, tìm theo trace ID
Ta đủ đáng tin để ship, hay nên ổn định?SLO + error budget
Vì sao cả một loạt lỗi bắt đầu lúc 14:02?Cả ba, tương quan qua trace ID

Góc nhìn thành thật theo quy mô công ty

  • Solo / startup giai đoạn đầu. Bạn không cần một observability platform. Một error tracker (alert crash và exception), structured logs tìm kiếm được, và hai ba alert uptime/latency che phần lớn sự cố. Nhưng hãy làm một việc rẻ ngay bây giờ: đặt một trace ID lên mọi dòng log. Nó gần như miễn phí hôm nay và bạn sẽ biết ơn lần đầu tiên bạn có service thứ hai.
  • Scale-up đang lớn. Áp dụng OpenTelemetry trước khi bị cưới chết, và lan trace ID qua mọi service. Dựng RED metrics và một bộ dashboard nhỏ. Viết SLO đầu tiên cho một hai hành trình thực sự quan trọng (checkout, login). Bắt đầu một lịch on-call và xóa không thương tiếc mọi alert không làm được gì. Bắt đầu sample traces trước khi hóa đơn dạy bạn.
  • Doanh nghiệp lớn. Ba trụ cột là một platform có chủ sở hữu. Chính sách error budget gác cổng release một cách chính thức. Mỗi team sở hữu SLO và on-call của mình. Sampling, retention và cardinality được quản trị tập trung, vì chi phí giờ đã là một dòng tiền thực. Đến đây phần khó không còn là thu dữ liệu — mà là văn hóa: thực sự hành động theo budget, giữ alert trung thực, và viết runbook trước sự cố, không phải trong lúc sự cố.

Những điều cốt lõi

  • Bạn không thể sửa thứ bạn không thấy. Distributed systems lấy đi cái log file duy nhất, nên nhìn thấy hệ thống trở thành một kỷ luật phải xây có chủ đích.
  • Ba trụ cột, ba công việc. Metrics hỏi “có gì sai?”, logs hỏi “chính xác đã xảy ra gì?”, traces hỏi “thời gian đi đâu?” Đừng bắt một cái làm cả ba.
  • Một trace ID lan khắp nơi là thói quen đòn bẩy cao nhất. Nó khâu mười log file thành một câu chuyện — áp dụng OpenTelemetry sớm để không bị cưới chết.
  • Đo người dùng, đừng đo cái máy. SLI và SLO biến reliability thành một error budget: ship mạnh dạn khi nó khỏe, ổn định khi nó cạn.
  • Alert vào triệu chứng, không vào nguyên nhân. Mọi page phải khẩn và làm được, không thì bạn dạy mọi người bỏ qua đúng cái alert quan trọng.
  • Observability tốn tiền thật. Sample traces, phân tầng retention, và giữ cardinality có chủ đích — hoặc hóa đơn sẽ làm giùm bạn.

Resilience là xây hệ thống để sống sót qua thất bại; observability là giác quan cho bạn nhận ra nó đang hỏng, tìm ra nguyên nhân trong vài phút thay vì vài giờ, và học từ nó sau đó. Xây cho thất bại, rồi làm thất bại hiện ra. Cùng nhau, đó là phần lớn của việc “vận hành” một hệ thống lớn thực sự nghĩa là gì — và đó là khác biệt lặng lẽ giữa một bí ẩn lúc 3 giờ sáng và một cú sửa lúc 3 giờ sáng.

Bạn thấy bài viết thế nào?