Lỗi hay gặp khi viết ADR, runbook và README Trung cấp
Thời gian đọc ước tính: 9 phút
Cần đọc trước
Trang này gom các lỗi hay gặp khi review ADR, runbook và README. Mỗi lỗi có bản hay gặp, bản nên viết, và hậu quả cụ thể nếu để nguyên.
| Nhóm | Lỗi |
|---|---|
| ADR | Sửa đè ADR cũ, Chỉ có hệ quả tốt, Tiêu đề danh từ, không có phương án, ADR trong wiki, ADR cho mọi thứ, Trùng số |
| Vận hành | Runbook mơ hồ, Alert không có runbook |
| README | README trộn bốn loại |
Sửa đè ADR cũ
Phần tiêu đề “Sửa đè ADR cũ”Sửa ADR-0012 cho khớp hiện trạng sau khi chuyển sang CDC
PR hay gặp
# 0012. Phát event đơn hàng qua transactional outboxMột relay riêng claim dòng pending bằng FOR UPDATE SKIP LOCKED...Chúng ta chưa dùng CDC. Xem lại khi throughput vượt khả năng polling.Debezium đọc WAL của bảng outbox và publish lên Kafka.PR nên làm
# 0012. Phát event đơn hàng qua transactional outbox-- Status: accepted+- Status: Superseded by ADR-0031
# 0031. Chuyển relay outbox sang Debezium CDC (file mới)+- Supersedes: ADR-0012## ContextThroughput outbox vượt khả năng polling; platform team đã có Kafka Connect(đúng điều kiện "xem lại khi..." của ADR-0012).Vì sao
- Sửa đè xoá mất lý do vì sao từng chọn polling. Khi CDC gặp sự cố replication slot, không ai biết phương án cũ đã được cân nhắc thế nào.
- Nygard: quyết định cũ được giữ lại và đánh dấu superseded. Chỉ được sửa status và link.
ADR chỉ có hệ quả tốt
Phần tiêu đề “ADR chỉ có hệ quả tốt”Consequences như một bài bán hàng
ADR tồi
## Consequences- Không bao giờ mất event- Hệ thống scale tốt hơn- Kiến trúc hiện đại, chuẩn microservicesADR tốt
## ConsequencesTốt:- Không còn đơn paid mà không phát event khi process chết giữa chừng.Xấu:- Vẫn at-least-once: relay có thể publish trùng. Consumer mới BẮT BUỘC idempotent theo eventId.- Event trễ thêm tối đa một chu kỳ poll.- Bảng outbox chỉ tăng: cần job dọn và alert cho dòng pending cũ.Vì sao
- Hệ quả xấu không ghi vẫn xảy ra. Team mới viết consumer không idempotent, relay publish trùng, Shipping tạo hai vận đơn cho một đơn hàng.
- Người sau gặp nó như bất ngờ và kết luận quyết định là sai, rồi đảo ngược mù quáng. Nygard yêu cầu ghi mọi hệ quả, không chỉ cái tích cực.
Tiêu đề là danh từ, không có phương án bị loại
Phần tiêu đề “Tiêu đề là danh từ, không có phương án bị loại”ADR không trả lời được 'sao không làm X'
ADR tồi
# ADR 15: Isolation level
## DecisionDùng READ COMMITTED.ADR tốt
# 0015. Trừ tồn kho bằng READ COMMITTED + SELECT ... FOR UPDATE thay vì SERIALIZABLE
## ContextHai checkout đồng thời có thể cùng đọc stock = 1 rồi cùng trừ (lost update).Phương án đã cân nhắc:1. SERIALIZABLE toàn transaction: đúng, nhưng phải retry khi serialization failure, và mọi code path checkout phải có vòng retry.2. UPDATE ... SET stock = stock - 1 WHERE stock >= 1: rẻ nhất, nhưng không dùng được khi cần đọc nhiều dòng rồi quyết định.3. READ COMMITTED + SELECT ... FOR UPDATE trên các dòng sẽ trừ.
## DecisionChúng ta sẽ khoá các dòng inventory bằng SELECT ... FOR UPDATE theo thứ tự product_id tăng dần.Vì sao
- Tiêu đề danh từ không cho biết đã chọn gì; index ADR thành danh sách vô nghĩa.
- Không có phương án bị loại thì sáu tháng sau có người đề xuất “chuyển sang SERIALIZABLE cho an toàn”, và cuộc tranh luận bắt đầu lại từ đầu.
- MADR bắt buộc
Considered Optionschính vì lý do này.
ADR nằm trong wiki tách khỏi repo
Phần tiêu đề “ADR nằm trong wiki tách khỏi repo”Code trỏ tới wiki, không ai thấy khi review
Hay gặp
// Xem quyết định ở https://wiki.example.com/pages/viewpage.action?pageId=884213await this.outbox.insert(tx, event);Nên viết
// ADR-0012: event đi qua outbox, KHÔNG gọi producer.send() trực tiếp ở đây.await this.outbox.insert(tx, event);Vì sao
- Wiki tách repo: PR đổi code không đụng tới ADR, reviewer không thấy nó khi review. Link vỡ khi công ty đổi công cụ wiki.
- ADR trong
docs/adr/đi cùng version code, grep được, và được review trong cùng PR.
Viết ADR cho mọi thứ
Phần tiêu đề “Viết ADR cho mọi thứ”Decision log thành bãi rác
Decision log tồi
docs/adr/ 0041-doi-ten-bien-orderSvc-thanh-orderService.md 0042-nang-lodash-4-17-20-len-4-17-21.md 0043-them-index-orders-created-at.md 0044-dung-prettier-print-width-100.md 0045-giu-modular-monolith-chua-tach-shipping.mdDecision log tốt
docs/adr/ 0045-giu-modular-monolith-chua-tach-shipping.md
# Phần còn lại: commit message, migration, config lintergit log --oneline a1c3e9f refactor: đổi tên orderSvc thành orderService 7b20d4e chore(deps): nâng lodash 4.17.21 (patch bảo mật) e91f0aa perf: thêm index orders(created_at) cho báo cáo ngàyVì sao
- 300 file về tên biến làm ADR quan trọng chìm mất; không ai đọc index nữa.
- Lọc bằng hai câu hỏi: khó đảo ngược không? người khác sẽ hỏi lại không? Không thì commit message là đủ.
Runbook “kiểm tra log và xử lý phù hợp”
Phần tiêu đề “Runbook “kiểm tra log và xử lý phù hợp””Runbook không dùng được lúc 3 giờ sáng
Runbook tồi
# Outbox alertKiểm tra log của relay và xử lý phù hợp.Nếu cần thì xoá các dòng lỗi trong bảng outbox.Runbook tốt
# Runbook: OutboxBacklogHighOwner: team Checkout. Cập nhật lần cuối: 2026-09-12
## Ảnh hưởngShipping nhận OrderPaid chậm. Thanh toán KHÔNG bị ảnh hưởng.
## Chẩn đoán1. kubectl get pods -l app=outbox-relay2. SELECT id, event_type, created_at FROM outbox WHERE published_at IS NULL ORDER BY id LIMIT 20;
## Xử lý- Dòng độc: KHÔNG xoá. Chuyển sang outbox_parked, mở ticket.Vì sao
- Không có lệnh, không có ngưỡng: người on-call (thường không viết code này) phải đoán, MTTR kéo dài.
- “Xoá các dòng lỗi” là mất event vĩnh viễn: đơn đã paid nhưng Shipping không bao giờ nhận. Runbook tốt ghi rõ việc không được làm.
Alert không có link runbook
Phần tiêu đề “Alert không có link runbook”Alert kêu nhưng không chỉ đường
Alert rule tồi
- alert: OutboxBacklogHigh expr: outbox_oldest_pending_age_seconds > 60 labels: severity: pageAlert rule tốt
- alert: OutboxBacklogHigh expr: outbox_oldest_pending_age_seconds > 60 for: 5m labels: severity: page annotations: summary: Outbox có dòng pending cũ hơn 60s trong 5 phút runbook_url: https://git.example.com/order-service/-/blob/main/docs/runbooks/outbox-backlog.mdVì sao
- Không có
for, alert fire ngay ở lần evaluate đầu tiên vượt ngưỡng, kể cả spike ngắn lúc deploy: on-call bị đánh thức vô ích. - Không có link runbook, người nhận page phải đi tìm docs giữa đêm. Prometheus mô tả annotations chính là chỗ để mô tả alert và link runbook.
README trộn tutorial, reference và explanation
Phần tiêu đề “README trộn tutorial, reference và explanation”README là kho thay vì cửa vào
README tồi
# order-service## Lịch sửNăm 2023 team chọn Kafka thay vì RabbitMQ vì... (3 trang)## Cài đặtNếu bạn dùng Mac M1 thì..., nếu dùng Windows thì..., nếu dùng Postgres 14 thì...## Env varsDB_URL, KAFKA_BROKERS, OUTBOX_POLL_MS (=500) <- viết tay, code đã đổi default thành 200README tốt
# order-serviceQuản lý vòng đời đơn hàng (bounded context Ordering). KHÔNG xử lý vận chuyển.
## Quickstartdocker compose up -d && npm ci && npm run migrate && npm run dev
## Tài liệu- Kiến trúc (C4): docs/architecture.md- Quyết định: docs/adr/ (vì sao Kafka: ADR-0004)- Runbook: docs/runbooks/- Env vars: sinh từ schema config, xem docs/reference/config.mdOwner: team Checkout, #team-checkoutVì sao
- Theo Diátaxis, “vì sao Kafka” là explanation (thuộc ADR), bảng env var là reference (nên sinh từ code).
- Bảng env var viết tay lệch default: dev mới cấu hình theo README, chạy được nhưng sai hành vi so với production.
- Quickstart rẽ nhánh khắp nơi thì người mới không chạy nổi service trong ngày đầu.
Hai ADR cùng số
Phần tiêu đề “Hai ADR cùng số”Hai nhánh song song cùng tạo 0013
Hay gặp
docs/adr/ 0013-dung-redis-cho-idempotency-key.md # merge từ nhánh A 0013-tach-schema-reporting.md # merge từ nhánh BNên làm
# CI: fail nếu hai file ADR trùng tiền tố sốdupes=$(ls docs/adr | grep -oE '^[0-9]{4}' | sort | uniq -d)if [ -n "$dupes" ]; then echo "ADR trùng số: $dupes" exit 1fiVì sao
- “Xem ADR-0013” giờ trỏ tới hai quyết định khác nhau; link trong code và commit message trở nên mơ hồ.
- Người merge sau đổi số trước khi merge; CI chặn nếu quên. Không cần đổi sang đánh số theo ngày.
Tiếp theo: quay về Tổng quan cụm ADR và tài liệu, hoặc xem cụm Code review.