AI SREClickHouse Workshops

07 Kiểm thử, thất bại và sửa lỗi

Ghi chú giảng viên cho module 07 — thời lượng, nội dung dẫn giảng, lỗi thường gặp và các bước khôi phục.

Your computer
macOS terminal: Run workshop commands in Terminal using zsh or bash.

Tài liệu đồng hành của người điều phối cho bài học 07 Kiểm thử, thất bại và sửa lỗi.

Thời lượng

Khoảng 20 phút. Đây là phòng lab sự cố; hãy bảo vệ đủ khoảng chạy để phần chẩn đoán đi đến kết luận trước module 08 và phần tổng kết. Phòng lab chạy fault 01 (chẩn đoán 5-15 phút). Fault 02 (5-10 phút) và 03 (10-20 phút, và chỉ với bộ dữ liệu đầy đủ khoảng 30 triệu dòng) vẫn có sẵn như các sự cố bổ sung tuỳ chọn cho một phòng học chạy nhanh. Hãy đặt một mốc dừng cứng trong buổi tổng duyệt để phần tổng kết không bị bó hẹp.

Nội dung dẫn giảng

  • Đây là phần thành quả: dùng mọi thứ đã xây được đến giờ để xử lý một sự cố thật.
  • Hãy mô tả triệu chứng, không mô tả nguyên nhân, và để các agent tự hội tụ từ telemetry.
  • Cân nhắc trình chiếu chẩn đoán của vài người tham dự cạnh nhau trên máy chiếu.
  • Phòng lab chạy fault 01. Nếu thời gian cho phép thêm một vòng, hãy thêm fault 02 (và chỉ thêm fault 03 khi bộ dữ liệu đầy đủ đã được nạp mồi trước); dùng bước khôi phục stash-and-switch dùng chung bên dưới giữa các fault.

Đáp án

Đừng chia sẻ phần này với học viên; nó chỉ tồn tại trong playbook và không bao giờ được commit vào repo của ứng dụng. Mỗi fault là một thay đổi nhỏ trên nhánh riêng của nó, tách ra từ build-workshop-v1; cách sửa là revert nó. Hãy chạy từng fault một. Sự cố của phòng lab là fault 01 (5-15 phút, đòn bất ngờ — backend vô can). Các phần bổ sung tuỳ chọn cho một phòng học chạy nhanh: fault 02 (5-10 phút, khởi động, trace nói hết mọi thứ) và fault 03 chỉ khi có bộ dữ liệu đầy đủ (10-20 phút, suy luận sâu hơn về ClickHouse).

Bước khôi phục dùng chung cho mọi fault (lệnh stash bảo toàn các chỉnh sửa của người tham dự và tránh việc chuyển nhánh bị chặn):

git stash push --include-untracked -m "module-07-fix"
git switch build-workshop-v1
docker compose --env-file .env.workshop \
  -f docker-compose.workshop.yml -f docker-compose.otel.yml up -d --build

Fault 02 — zone stats trả về 500 (fault/02-zone-stats-500)

Thông điệp commit mà người tham dự thấy: "backend: align zone stats column names with API params" (chỉ chạm vào zone_stats_sql trong backend/app/query_builders.py). Nó group by pickup_zone_id, một cột không tồn tại trên taxi_trips, thay vì pickup_location_id.

  • Triệu chứng: bản đồ choropleth trống (các polygon vẫn vẽ ra, nhưng mọi zone đều nằm trong nhóm màu nhạt nhất) và chú thích "Query Nms" biến mất; hàng loạt lỗi 500 trên GET /api/metrics/zone_stats (React Query thử lại ba lần). Mọi card khác vẫn ổn.
  • Nơi tín hiệu nằm ở đó: một span clickhouse.query trong ClickStack với error=True, error.category="query_failed", và db.statement chứa pickup_zone_id AS zone_id; ngoại lệ được ghi lại là ClickHouse Code 47 UNKNOWN_IDENTIFIER. Log ERROR tương ứng: "ClickHouse query failed (category=query_failed, elapsed_ms=...): ... | sql=...".
  • Đường chẩn đoán: tìm span lỗi 500 -> đọc db.statement -> UNKNOWN_IDENTIFIER trên pickup_zone_id -> DESCRIBE taxi_trips (hoặc so sánh với các query builder tương tự) -> cột thật là pickup_location_id.
  • Cách sửa: đổi pickup_zone_id trở lại thành pickup_location_id trong zone_stats_sql (hoặc git revert commit gây lỗi); build lại backend.
  • Khôi phục: dùng bước khôi phục dùng chung ở trên.

Fault 03 — dashboard chậm (fault/03-slow-dashboard)

Thông điệp commit: "backend: window the trend series by bucket in HAVING" (chạm vào timeseries_sql). Nó chuyển vị từ điều kiện cửa sổ thời gian từ WHERE sang một HAVING trên alias của bucket, nên WHERE trở thành 1. ClickHouse không còn có thể lược bỏ theo primary key (car_type, pickup_datetime) nữa và quét toàn bộ taxi_trips với hai trạng thái quantileTDigest, làm nổ mức max_execution_time 5s.

  • Triệu chứng: chỉ card xu hướng "What's happening now?" thất bại — nó quay vòng, rồi hiện "504 Query timed out...". Mọi card cùng cấp vẫn nhanh.
  • Nơi tín hiệu nằm ở đó: một span clickhouse.query với error.category="timeout", db.elapsed_ms khoảng 5000, và db.statement hiện WHERE 1 ... GROUP BY ts HAVING ts >= ...; ngoại lệ là Code 159 TIMEOUT_EXCEEDED. Sự tương phản với các span 200 nhanh trên các endpoint khác chính là dấu hiệu nhận biết.
  • Đường chẩn đoán: một card chậm giữa các card cùng cấp nhanh -> mở span bị timeout -> đọc câu SQL -> bộ lọc thời gian nằm trong HAVING và WHERE không có khoảng pickup_datetime -> một anti-pattern về lược bỏ theo primary key / đẩy điều kiện xuống dưới (các card cùng cấp đặt cửa sổ thời gian trong WHERE).
  • Cách sửa: đưa cửa sổ thời gian trở lại WHERE (revert commit gây lỗi); build lại backend.
  • Khôi phục: dùng bước khôi phục dùng chung ở trên.
  • Lưu ý cho giảng viên: mức nghiêm trọng tỉ lệ với khối lượng dữ liệu và kích thước service. Trên bộ dữ liệu đầy đủ đã nạp mồi (~30 triệu dòng) với một service ở mức workshop-tier (có thể đang thức dậy từ trạng thái nghỉ), mốc timeout 5s nổ ra một cách đáng tin cậy; còn trên bộ dữ liệu mồi nhỏ thì nó chỉ chậm hơn, không phải một lỗi 504 dứt khoát. send_receive_timeout phía client và max_execution_time phía server đều là 5s, nên một cuộc đua timeout ở tầng socket có thể đôi khi hiện ra thành lỗi 500 thay vì lỗi 504 gọn gàng — đó là đặc tính của cấu hình cơ sở, không phải của fault.

Fault 01 — bản đồ không tải được (fault/01-map-not-loading)

Thông điệp commit: "frontend: serve map geojson from /static asset path" (chạm vào đường dẫn fetch trong frontend/src/ui/ZoneMap.tsx). Nó fetch /static/taxi_zones.geojson, thứ không tồn tại. Điểm tinh tế then chốt: cơ chế fallback SPA của nginx (try_files ... /index.html) trả về HTTP 200 kèm tài liệu HTML thay vì lỗi 404, nên r.ok vẫn vượt qua và r.json() ném ra một SyntaxError kiểu Unexpected token '<'.

  • Triệu chứng: card bản đồ đứng chết — các tile nền vẫn vẽ nhưng không có polygon NYC hay choropleth nào, và một dòng lỗi màu đỏ hiện ngay tại chỗ. Mọi thứ khác vẫn ổn.
  • Nơi tín hiệu nằm ở đó: không có gì trong ClickStack phía backend — asset đó chưa bao giờ đến được FastAPI. Console của trình duyệt hiện lỗi phân tích JSON có nêu tên /static/taxi_zones.geojson; tab Network hiện request đó trả về text/html với trạng thái 200; access log của nginx hiện cơ chế fallback.
  • Đường chẩn đoán: các trace ở backend đều sạch -> chuyển sang console / tab Network của trình duyệt -> một request .geojson trả về 200 text/html -> nhận ra cái bẫy fallback của SPA -> file thực ra nằm ở web root, /taxi_zones.geojson.
  • Cách sửa: revert đường dẫn fetch (hai dòng) hoặc git revert commit gây lỗi; build lại frontend.
  • Khôi phục: dùng bước khôi phục dùng chung ở trên.
  • Điểm giảng dạy: không phải mọi thất bại đều hiện ra trong các trace ở backend, và một lỗi 404 có thể giả dạng thành 200 phía sau một cơ chế fallback của SPA — vậy nên hãy đọc chính nội dung phản hồi và content-type, không chỉ mã trạng thái.
  • Ghi chú (SDK trình duyệt): với SDK trình duyệt của HyperDX đã bật (overlay của module 05), frontend giờ hiện lỗi này ngay trong ClickStack — lỗi phân tích của ZoneMap được ghi lại thành một span console.error dưới ServiceName=nyc-taxi-frontend, đi kèm với span resource của phản hồi 200 text/html đó. Nhờ vậy agent có thể định vị nó từ telemetry mà không cần rời khỏi ClickStack; console / tab Network của trình duyệt là phương án dự phòng, không phải con đường duy nhất.

Ví dụ phản hồi của agent (fault 01, qua ClickStack MCP):

Phân tích nguyên nhân gốc của agent cho fault 01: nó nêu tên /static/taxi_zones.geojson bị thiếu đang trả về index.html của SPA dưới dạng 200 text/html, lỗi phân tích JSON của ZoneMap được ghi lại thành một span console.error của nyc-taxi-frontend, đối chiếu với các span /api và backend khoẻ mạnh, đánh dấu các lỗi self-test của SDK là nhiễu, và đề xuất phát hành asset đó hoặc bọc bảo vệ cho bước phân tích

Chẩn đoán mong đợi: agent gắn thất bại này với request geojson trả về 200 text/html (fallback của SPA), lỗi JSON.parse phát sinh được ghi lại dưới nyc-taxi-frontend, và khuyến nghị phát hành asset vào /static/ hoặc bọc bảo vệ cho bước phân tích .json().

Lỗi thường gặp

  • Không đủ đường cơ sở telemetry để agent định vị được lỗi; module 05 phải đã chạy từ sớm.
  • Người tham dự nhảy ngay vào cách sửa trước khi xác nhận nguyên nhân gốc từ bằng chứng.
  • Triệu chứng của fault chưa hiện ra vì stack vừa mới được dựng lên — hoặc, với fault 03, vì có quá ít dữ liệu lịch sử được nạp. Đã đo trong buổi tổng duyệt: với bộ dữ liệu mồi một tháng mặc định (~3,17 triệu dòng), fault WHERE-vs-HAVING không quan sát được — lần quét toàn bảng chạy trong khoảng 300-560ms, không phân biệt được với đường cơ sở. Hãy nạp mồi vài tháng trước khi demo fault 03, hoặc cứ dùng fault 01 (tái hiện ngay lập tức) và coi fault 03 là một minh hoạ ở quy mô lớn.
  • Fault 01 không sinh ra trace nào ở backend cả, và request lỗi trả về 200 text/html qua cơ chế fallback của SPA thay vì lỗi 404; người tham dự có thể tìm mãi trong các trace. Hãy nhắc họ chuyển sang console / tab Network của trình duyệt, nơi lỗi phân tích JSON và phản hồi text/html hiện ra.
  • Quên --build sau khi checkout một nhánh fault, nên image cũ vẫn tiếp tục chạy.

Các bước khôi phục

  • Bước khôi phục stash-and-switch dùng chung phục hồi ứng dụng hoàn chỉnh mà không bỏ đi bản sửa mà người tham dự đã thử.
  • Tiêm lại một fault bằng cách checkout một trong các nhánh fault/01-map-not-loading / fault/02-zone-stats-500 / fault/03-slow-dashboard và build lại với các file compose của workshop cộng với của otel.
  • Triệu chứng, tín hiệu và cách sửa của từng fault nằm trong phần Đáp án ở trên. Hãy giữ đáp án chỉ trong playbook này; nó không bao giờ được commit vào repo của ứng dụng.

Trên trang này

VI