Mở đầu — vì sao bài này quan trọng
Hãy tưởng tượng bạn là BA tại một sàn thương mại điện tử như Tiki hay Shopee. Một khách hàng phàn nàn: "Tôi bấm đặt hàng, màn hình quay vòng 8 giây rồi báo lỗi." Đội vận hành nhìn nhau, chẳng ai biết chuyện gì xảy ra. Bởi vì một cú bấm "đặt hàng" đó không chạy trên một hệ thống duy nhất — nó đi qua 5, 7, có khi 12 service khác nhau: API Gateway nhận request, service xác thực kiểm tra token, service giỏ hàng, service kiểm kho, service tính khuyến mãi, service thanh toán, service ghi đơn... Mỗi service do một team khác viết, deploy riêng, có log riêng.
Khi có lỗi, câu hỏi đau đầu nhất là: request này đã đi qua đâu, dừng ở đâu, và chậm ở chỗ nào? Nếu chỉ có log rời rạc của từng service, bạn sẽ như người mù sờ voi — mỗi team báo "phía tôi bình thường" nhưng tổng thể vẫn lỗi.
Distributed tracing (truy vết phân tán) chính là công cụ ghép lại bức tranh đó. Nó cho phép bạn nhìn một request duy nhất xuyên suốt qua tất cả các service như một dòng thời gian liền mạch. Với BA, hiểu distributed tracing không phải để bạn tự cấu hình hệ thống, mà để bạn đọc được trace khi điều tra sự cố, đặt câu hỏi đúng với team kỹ thuật, và viết yêu cầu observability (khả năng quan sát) vào spec ngay từ đầu — thay vì để nó thành "nợ kỹ thuật" phát hiện khi đã quá muộn.
Khái niệm cốt lõi
Vấn đề gốc rễ: log rời rạc trong kiến trúc nhiều service
Trong một hệ thống monolith (khối đơn) ngày xưa, mọi thứ chạy trong một tiến trình, một file log. Bạn lần theo log từ trên xuống dưới là ra. Nhưng khi hệ thống tách thành microservices (bài 38 đã nói về sự đánh đổi này), mỗi service ghi log vào nơi của riêng nó. Một request duy nhất sinh ra log nằm rải rác ở 5 nơi, không có sợi dây nào nối chúng lại. Đó là lý do distributed tracing ra đời.
Trace, Span và Trace ID
Có ba khái niệm bạn bắt buộc phải nắm:
Trace là toàn bộ hành trình của một request từ lúc vào hệ thống đến lúc trả về. Nó là "câu chuyện đầy đủ" của một lần khách bấm nút.
Span là một đoạn công việc trong trace đó — ví dụ "service thanh toán gọi cổng VNPay" là một span, "service kho truy vấn database" là một span khác. Mỗi span có thời điểm bắt đầu, thời điểm kết thúc (nên biết được nó mất bao lâu), tên thao tác, và trạng thái (thành công hay lỗi). Một trace là tập hợp nhiều span ghép lại.
Trace ID là chìa khóa quan trọng nhất. Đó là một mã định danh duy nhất (ví dụ 7f3a9c1e...) được gán cho request ngay khi nó bước vào hệ thống. Mã này được truyền đi cùng request qua mọi service — thường nằm trong HTTP header (bài 10 đã giới thiệu header). Nhờ Trace ID, công cụ tracing gom tất cả span có cùng ID lại thành một trace duy nhất. Đây chính là "sợi dây" nối các log rời rạc mà chúng ta nói ở trên.
Ngoài Trace ID, mỗi span còn có Span ID riêng và biết được Parent Span ID (span cha) — nhờ đó hệ thống dựng được quan hệ cha-con: span nào gọi span nào, lồng nhau ra sao.
Context propagation — truyền ngữ cảnh
Để Trace ID đi xuyên các service, mỗi service khi gọi service tiếp theo phải đính kèm Trace ID vào header của request đi tiếp. Cơ chế này gọi là context propagation (truyền ngữ cảnh). Chuẩn phổ biến nhất hiện nay là W3C Trace Context, dùng header tên traceparent. Nếu một service trong chuỗi "quên" truyền tiếp header này, sợi dây đứt đoạn — trace bị gãy và bạn mất dấu từ điểm đó. Đây là lỗi rất hay gặp mà BA cần biết để hỏi đúng.
Sơ đồ waterfall — cách đọc trace
Công cụ tracing thường hiển thị một trace dưới dạng biểu đồ thác nước (waterfall): mỗi span là một thanh ngang, độ dài thanh tương ứng thời gian, các span con thụt vào dưới span cha. Nhìn vào đó bạn thấy ngay span nào dài nhất (nghẽn ở đâu), span nào màu đỏ (lỗi ở đâu), và service nào "chờ" service nào.
Sampling — lấy mẫu
Một hệ thống lớn có hàng triệu request mỗi giờ. Lưu trace cho tất cả sẽ tốn kém khủng khiếp về lưu trữ và băng thông. Vì vậy người ta dùng sampling — chỉ giữ lại một tỷ lệ nhất định, ví dụ 1% hoặc 10% số trace. Có hai kiểu chính: head-based sampling (quyết định giữ hay bỏ ngay khi request vào) và tail-based sampling (đợi request chạy xong, ưu tiên giữ những trace bị lỗi hoặc chậm). BA cần biết khái niệm này vì nó giải thích vì sao đôi khi "tìm mãi không thấy trace của đúng request khách phàn nàn" — có thể nó đã bị bỏ qua khi sampling.
Hệ sinh thái công cụ
Bạn sẽ nghe các tên: OpenTelemetry (OTel) là chuẩn mở để sinh và thu thập trace, gần như là tiêu chuẩn chung của ngành hiện nay. Các công cụ hiển thị và phân tích trace gồm Jaeger, Zipkin (mã nguồn mở), và các nền tảng thương mại như Datadog, New Relic, Grafana Tempo, AWS X-Ray. Distributed tracing là một trong ba trụ cột của observability — hai trụ cột còn lại là logs và metrics (chúng ta sẽ học sâu hơn ở bài 49).
Tình huống thực tế
Tình huống 1: Sàn TMĐT và cú "đặt hàng 8 giây"
Quay lại ví dụ đầu bài. Một sàn TMĐT giả định tên GiaoNhanh có luồng đặt hàng đi qua 6 service. Khách phàn nàn đặt hàng chậm và đôi khi lỗi. Trước khi có tracing, mỗi team chỉ thấy phần của mình và đều nói "bình thường".
Sau khi triển khai OpenTelemetry + Jaeger, BA cùng team kỹ thuật mở trace của một đơn hàng lỗi. Biểu đồ waterfall cho thấy: API Gateway 20ms, service giỏ hàng 50ms, service kiểm kho 80ms — tất cả nhanh. Nhưng span "service khuyến mãi" kéo dài tận 7.200ms, bên trong nó có một span con gọi tới một database khuyến mãi cũ kỹ. Hóa ra service khuyến mãi đang quét toàn bộ bảng mã giảm giá mỗi lần đặt hàng, và khi bảng phình to vào dịp sale, nó chậm thê thảm.
Bài học rút ra: trace biến một cuộc tranh cãi mơ hồ "lỗi tại ai" thành một sự thật đo được — nghẽn nằm chính xác ở một truy vấn database trong service khuyến mãi. BA dùng trace này làm bằng chứng để ưu tiên backlog, thay vì đoán mò.
Tình huống 2: Ví điện tử và lỗi 500 chập chờn
Một ví điện tử Việt Nam giả định tên ViPay gặp tình trạng: khoảng 2% giao dịch nạp tiền trả về lỗi 500, nhưng không lặp lại được khi test. Đây là kiểu lỗi "ma" khó chịu nhất.
Nhờ tail-based sampling (ưu tiên giữ trace lỗi), team tìm được vài chục trace của đúng các giao dịch lỗi đó. Khi xếp chồng lên nhau, tất cả đều có điểm chung: span gọi tới service đối tác ngân hàng có trạng thái timeout sau đúng 3.000ms, sau đó service nạp tiền của ViPay không xử lý timeout này mà trả về 500 thô. Trace còn cho thấy chuỗi đứt đoạn: span phía ngân hàng không truyền Trace ID về, nên ban đầu họ tưởng lỗi nằm bên mình.
Bài học rút ra: distributed tracing đặc biệt mạnh với lỗi chập chờn và lỗi ở ranh giới giữa hai hệ thống. Nó cũng lộ ra một yêu cầu nghiệp vụ bị bỏ sót: "khi đối tác timeout, hệ thống phải trả lỗi gì cho khách?" — một câu hỏi BA lẽ ra phải đưa vào spec từ đầu.
Tình huống 3: Startup giao đồ ăn truyền thiếu Trace ID
Một startup giao đồ ăn ở Đông Nam Á tích hợp distributed tracing nhưng trace luôn bị "cụt" tại service tính phí giao hàng — sau service đó là một khoảng trống, không thấy gì nữa dù logic vẫn chạy. Điều tra ra: service tính phí được viết bằng một ngôn ngữ khác và đội cũ tự viết client gọi HTTP mà quên đính kèm header traceparent khi gọi service bản đồ. Sợi dây đứt từ đó.
Bài học rút ra: distributed tracing chỉ hoàn chỉnh khi mọi service trong chuỗi đều truyền ngữ cảnh đúng cách. Một mắt xích quên là cả đoạn sau biến mất. Với BA, đây là lý do để đưa "yêu cầu propagate trace context" thành tiêu chí khi review tích hợp hoặc khi onboard một service mới của bên thứ ba.
Hướng dẫn từng bước
Khi bạn là BA và cần dùng distributed tracing để điều tra một sự cố, hãy theo các bước sau:
- Xác định request cần truy vết. Thu thập thông tin từ khách: thời điểm gần đúng, tài khoản, mã đơn/giao dịch. Nếu hệ thống trả Trace ID cho người dùng (nhiều hệ thống in mã này trong màn hình lỗi hoặc email hỗ trợ), hãy lấy đúng mã đó — nhanh hơn rất nhiều.
- Mở công cụ tracing và tìm trace. Vào Jaeger/Datadog/New Relic, tìm theo Trace ID nếu có, hoặc lọc theo service, khoảng thời gian, và trạng thái lỗi để khoanh vùng.
- Đọc biểu đồ waterfall từ trên xuống. Tìm thanh dài nhất (điểm nghẽn về thời gian) và thanh màu đỏ (điểm lỗi). Hai dấu hiệu này thường chỉ thẳng ra service có vấn đề.
- Đào vào span nghi vấn. Bấm vào span đó để xem chi tiết: nó gọi gì, mất bao lâu, thông báo lỗi là gì, các thẻ (tag) như mã trạng thái HTTP, tên database, endpoint đối tác.
- Kiểm tra tính liên tục của trace. Nếu trace bị cụt giữa chừng, rất có thể một service không truyền context. Ghi nhận điểm đứt để báo team.
- Diễn giải thành ngôn ngữ nghiệp vụ. Đừng dừng ở "span X chậm 7 giây". Hãy viết: "Khi đặt hàng trong giờ cao điểm, bước áp dụng khuyến mãi mất 7 giây vì truy vấn database không tối ưu, gây timeout cho khách." Đây là phần giá trị nhất mà BA mang lại.
- Đưa phát hiện vào backlog hoặc spec. Gắn trace làm bằng chứng cho ticket, đề xuất ngưỡng SLA (bài 55) hoặc yêu cầu xử lý lỗi rõ ràng.
Lỗi thường gặp & mẹo
Lỗi: Nhầm tracing với logging. Log trả lời "chuyện gì đã xảy ra ở một điểm"; trace trả lời "request đã đi qua những đâu và mỗi chặng mất bao lâu". Chúng bổ trợ nhau, không thay thế nhau. Mẹo: trace tốt nhất khi có thêm Trace ID đính vào log — lúc đó bạn nhảy qua lại giữa trace và log của cùng một request dễ dàng.
Lỗi: Tưởng cứ bật tracing là thấy mọi request. Vì sampling, không phải request nào cũng được lưu. Mẹo: với các luồng quan trọng (thanh toán, đăng nhập), hãy yêu cầu cấu hình giữ trace ở tỷ lệ cao hơn hoặc ưu tiên giữ trace lỗi (tail-based sampling).
Lỗi: Bỏ qua đoạn trace bị cụt. Nhiều người thấy trace cụt thì cho rằng "hết rồi". Thực ra điểm cụt thường chính là nơi có vấn đề propagation. Mẹo: coi mỗi đoạn đứt là một dấu hỏi cần làm rõ với team sở hữu service đó.
Lỗi: BA yêu cầu observability quá muộn. Thêm tracing sau khi hệ thống đã chạy thường tốn công và dễ sót service. Mẹo: đưa yêu cầu "mọi service phải phát span theo chuẩn OpenTelemetry và truyền W3C Trace Context" vào tiêu chí chấp nhận (acceptance criteria) ngay từ spec ban đầu.
Mẹo về chi phí: trace lưu trữ tốn tiền. Khi viết yêu cầu, hãy cân đối giữa độ phủ và chi phí — không cần giữ 100% trace của mọi luồng, chỉ cần đủ để điều tra các luồng quan trọng.
Mẹo về quyền riêng tư: tuyệt đối không để dữ liệu nhạy cảm (mật khẩu, số thẻ, OTP) lọt vào tag của span. Đây là rủi ro tuân thủ mà BA nên nêu rõ.
Bài tập thực hành
- Vẽ trace bằng tay. Lấy luồng "đăng nhập bằng tài khoản" của một app bạn quen thuộc. Liệt kê các service có thể tham gia (gateway, xác thực, người dùng, gửi thông báo...), rồi vẽ biểu đồ waterfall ước lượng thời gian mỗi span. Đánh dấu span nào bạn nghĩ dễ nghẽn nhất.
- Đọc hiểu một trace lỗi. Giả sử bạn nhận được trace: Gateway (15ms) → Auth (40ms) → User DB (span đỏ, lỗi "connection timeout", 5.000ms). Hãy viết một đoạn 3-4 câu diễn giải sự cố này bằng ngôn ngữ nghiệp vụ để gửi cho quản lý sản phẩm.
- Viết acceptance criteria. Soạn 3 tiêu chí chấp nhận liên quan observability cho một service thanh toán mới, trong đó có ít nhất một tiêu chí về Trace ID và một tiêu chí về xử lý timeout của đối tác.
- Tình huống tranh luận. Hai team đổ lỗi cho nhau khi một API chậm. Viết kịch bản 5 câu hỏi bạn sẽ đặt dựa trên trace để chấm dứt tranh cãi bằng dữ liệu thay vì cảm tính.
Tóm tắt
Distributed tracing giải quyết bài toán cốt lõi của kiến trúc nhiều service: khi một request đi qua nhiều hệ thống và log nằm rời rạc, làm sao biết nó đã đi đâu và chậm/lỗi ở đâu. Câu trả lời là gắn một Trace ID duy nhất cho mỗi request và truyền nó xuyên mọi service (context propagation), để các đoạn công việc (span) được gom lại thành một trace liền mạch, hiển thị dưới dạng biểu đồ waterfall.
Với BA, ba điều cần nhớ: thứ nhất, biết đọc trace để biến tranh cãi "lỗi tại ai" thành sự thật đo được; thứ hai, hiểu các cạm bẫy như sampling (không phải request nào cũng được lưu) và trace bị cụt (do quên truyền context); thứ ba, chủ động đưa yêu cầu observability vào spec từ đầu — chuẩn OpenTelemetry, W3C Trace Context, và quy tắc xử lý lỗi ở ranh giới giữa các hệ thống. Khi nắm vững, bạn không chỉ điều tra sự cố nhanh hơn mà còn thiết kế ra những hệ thống dễ quan sát, dễ vận hành ngay từ trang giấy đầu tiên.