Bug đáng sợ nhất với dashboard không phải là chart sai layout. Mà là chart trông đúng, layout đẹp, nhưng con số sai. Stakeholder nhìn vào ra quyết định, và tới khi phát hiện thì đã chi tiền nhầm chỗ. Trong Kibana, hầu hết loại bug này đến từ hai chỗ: aggregation được dùng sai bản chất, và time bucket lệch ngầm.
Bài này gom các trường hợp đã debug trong vài năm vận hành Kibana. Không phải tutorial nhập môn; mà là một danh sách “bẫy” với cách phát hiện và fix.
Mục tiêu:
- Nhận diện 6 loại pitfall phổ biến: cardinality approximate, terms shard size, time bucket UTC, doc_count vs metric sum, sum-of-rates, missing data
- Biết cách validate dashboard trước khi share cho stakeholder
- Có pattern reproducible để test aggregation độ chính xác
Phần 1: Cardinality không phải đếm chính xác
cardinality aggregation trong Elasticsearch là approximate. Nó dùng thuật toán HyperLogLog++ với sai số khoảng 1-6% mặc định.
Một lần mình thấy report tháng ghi “12,847 unique users”. Tháng sau “12,801”. Stakeholder hỏi: “Tại sao users giảm?”. Thực ra users không giảm, mà cardinality estimator return giá trị khác nhau giữa hai lần chạy.
Cách nhận diện
Trong Lens, khi pick metric type, có hai option dễ nhầm:
| Metric | Bản chất | Khi nào dùng |
|---|---|---|
| Unique count | cardinality aggregation, approximate | Khi cardinality cao (triệu unique value) và sai số 1-6% chấp nhận được |
| Count | value_count, đếm chính xác | Khi đếm tổng số document |
Nếu number nhỏ (< 40k) và bạn muốn exact, ép precision_threshold lên cao:
"aggs": {
"unique_users": {
"cardinality": {
"field": "user_id.keyword",
"precision_threshold": 40000
}
}
}
Kibana Lens không expose precision_threshold qua UI, nhưng nếu bạn dùng Vega hoặc TSVB thì có thể chỉnh. Hoặc workaround: dùng terms aggregation với size lớn rồi đếm bucket. Chính xác hơn nhưng tốn ES hơn.
Validate
Chạy hai query song song và so sánh:
GET app-logs-*/_search
{
"size": 0,
"aggs": {
"approx": { "cardinality": { "field": "user_id.keyword" } },
"exact": {
"terms": { "field": "user_id.keyword", "size": 100000 },
"aggs": { "count": { "value_count": { "field": "user_id.keyword" } } }
}
}
}
Nếu lệch dưới 5%, OK dùng cardinality. Nếu lệch trên 10%, cardinality không phù hợp; phải refactor.
Phần 2: Terms aggregation cắt mất nhóm nhỏ
Terms aggregation chỉ return top N bucket. Mặc định trong Kibana là 5 hoặc 10. Phần còn lại bị gom vào “Other” hoặc bị bỏ qua hoàn toàn.
Ca thực tế: pie chart “Top 10 lỗi” hiển thị error A chiếm 40%, error B 20%, … còn lại 30% gom vào “Other”. Dev đọc xong nghĩ “chỉ cần fix A và B”. Thực tế “Other” có 50 loại lỗi nhỏ, mỗi loại 0.5-2%, và một trong số đó là security incident chưa được phát hiện.
Fix
- Tăng size trong Lens panel: bên phải dropdown “Top values” → “Number of values” set 20-50.
- Bật “Group remaining as Other” chỉ khi chắc chắn không cần nhìn nhóm nhỏ.
- Tách dashboard: một panel “Top 10” + một panel “Long tail” với filter loại bỏ top 10.
Shard size pitfall
Còn một issue tinh tế hơn. Terms aggregation chạy distributed: mỗi shard return top N local, rồi node coordinator merge. Nếu một term phổ biến global nhưng không lọt top N ở shard nào, kết quả sẽ thiếu.
Setting cần biết: shard_size. Mặc định 1.5 * size + 10. Nếu data skew (vài shard có hầu hết document), tăng shard_size lên.
"aggs": {
"top_errors": {
"terms": {
"field": "error_code.keyword",
"size": 10,
"shard_size": 1000
}
}
}
Tradeoff: shard_size lớn = chính xác hơn nhưng tốn ES hơn. Bắt đầu với shard_size: 100 và tăng nếu thấy lệch.
Phần 3: Time bucket lệch múi giờ
Histogram trên @timestamp chia data thành bucket theo thời gian. Bug ngầm: bucket boundary phụ thuộc múi giờ.
Ví dụ: bucket “daily” trong UTC bắt đầu lúc 00:00 UTC = 07:00 giờ Việt Nam. Nếu user xem dashboard giờ VN nhưng ES bucket theo UTC, ngày “2026-05-17” trong dashboard sẽ chứa data từ 07:00 ngày 17 đến 06:59 ngày 18 giờ VN. Báo cáo “tổng giao dịch ngày 17” sẽ tính cả phần đầu ngày 18.
Fix qua time_zone
Kibana từ version 7+ tự dùng browser timezone cho time bucket khi render. Nhưng nếu bạn export query ra Vega, hoặc gọi ES trực tiếp, cần set time_zone explicit:
"aggs": {
"by_day": {
"date_histogram": {
"field": "@timestamp",
"calendar_interval": "day",
"time_zone": "Asia/Ho_Chi_Minh"
}
}
}
Lưu ý calendar_interval (day, week, month) tôn trọng timezone. fixed_interval (24h) thì không. Khi data cần align với boundary lịch (như “ngày 17” chính xác), luôn dùng calendar_interval.
Kiểm tra trong Kibana
Vào Stack Management → Advanced Settings → dateFormat:tz. Nếu để Browser thì cell time format hiển thị theo browser, nhưng bucket boundary có thể vẫn UTC nếu ES bị query với time_zone mặc định.
Cách validate đơn giản: trong Discover, hover một bucket trên histogram, popup hiển thị “From X to Y”. Kiểm tra X và Y có align với 00:00 giờ local không.
Phần 4: doc_count vs metric sum
Một bar chart “tổng doanh thu theo ngày” có thể dùng:
countcủa document (mỗi document = 1 đơn hàng)sumcủa fieldrevenue
Hai cái rất khác. Nếu order có nested item, ES có thể index thành nhiều document (parent + nested), count sẽ phồng số lượng. Hoặc nếu order bị retry và index trùng, count sẽ tính trùng.
Best practice:
| Bạn muốn đo | Metric đúng |
|---|---|
| Số đơn hàng | cardinality của order_id |
| Doanh thu | sum của revenue (lưu ý dedup) |
| Số user duy nhất | cardinality của user_id (với precision) |
| Số request | value_count (số document) |
Pitfall storytelling: một dashboard hiển thị “10000 orders” theo count of records, nhưng số đơn hàng thật chỉ 6500. Lý do: pipeline retry index khi ES throttle, nên 35% order bị duplicate. Fix bằng cách dùng cardinality(order_id) thay vì count, hoặc dedupe ở ingest layer (Logstash filter fingerprint + override _id).
Phần 5: Sum of rates (anti-pattern)
Một dashboard SRE: “tổng request per second” tính bằng sum của metric rps (mỗi service báo cáo rps của nó). Nghe có lý? Sai.
Nếu service A báo cáo rps = 100 và service B báo cáo rps = 200, sum = 300 chỉ đúng nếu hai metric đo cùng một thời điểm. Nhưng nếu A báo cáo lúc 10:00:01 và B lúc 10:00:03, dashboard có thể double-count vì ES tính cả hai trong cùng 1-minute bucket.
Quy tắc: sum trên rate (per-second metric) phải qua avg-over-time trước. Trong Lens, dùng formula:
sum(average_over_time(rps, kql='', timeShift='1m'))
Hoặc tốt hơn: thay vì lưu rps, lưu raw count requests_total (counter monotonic increasing), rồi tính rate trong Kibana bằng differences() hoặc derivative aggregation.
"aggs": {
"by_minute": {
"date_histogram": { "field": "@timestamp", "fixed_interval": "1m" },
"aggs": {
"total_requests": { "max": { "field": "requests_total" } },
"rate": {
"derivative": { "buckets_path": "total_requests" }
}
}
}
}
Pattern này là cách Prometheus tính rate. Kibana hỗ trợ tương tự qua derivative pipeline aggregation.
Phần 6: Missing data và sparse buckets
Khi data không tới đều (sensor offline 5 phút), bar chart sẽ có gap. Kibana mặc định không hiện gap mà skip bucket, tạo cảm giác “data liên tục”.
Vấn đề: nếu metric là avg(temperature) thì bucket trống → không hiện điểm → đường lookup line chart bị nối qua, làm dữ liệu nhìn smooth giả tạo.
Fix
- Trong Lens, settings panel của axis: chọn Missing values → Show as zero hoặc Hide.
- Với line chart, chọn Connect null values: Off để gap hiện rõ.
- Trong query, dùng
extended_boundsđể force date_histogram tạo bucket trống:
"aggs": {
"by_minute": {
"date_histogram": {
"field": "@timestamp",
"fixed_interval": "1m",
"extended_bounds": {
"min": "2026-05-17T00:00:00",
"max": "2026-05-17T23:59:59"
}
}
}
}
Tradeoff: hiện gap đúng nhưng dashboard “trông xấu” hơn. Đó là intentional. Stakeholder thấy gap mới hỏi “tại sao mất data?” và discover được upstream issue.
Phần 7: Validation workflow
Trước khi share dashboard cho stakeholder, chạy checklist:
- Cross-check với source: lấy 1-2 con số trên dashboard, compare với COUNT() từ DB nguồn hoặc log file gốc. Lệch dưới 1% thì OK.
- Time zone test: chuyển browser timezone (Chrome DevTools → Sensors → Timezone), refresh dashboard. Số ổn định không? Nếu nhảy nhiều thì có bug timezone.
- Top N + Other: nếu có “Other” bucket, expand xem nó chiếm bao nhiêu %. Quá 10% là dashboard không phản ánh đúng.
- Sample size: cardinality < 40000 → ép precision_threshold cao. Cardinality > 1M → chấp nhận sai số nhưng note rõ.
- Outlier check: chạy Lens với percentile 95th, 99th để xem có giá trị bất thường skew metric không. Một order $1M nhập nhầm có thể làm trung bình tháng tăng 30%.
- Date range edge: thử time range “Last 1 hour” và “Last 30 days”. Số có scale tỉ lệ thuận hợp lý không?
Phần 8: Bảng pitfall tổng kết
| Pitfall | Triệu chứng | Fix |
|---|---|---|
| Cardinality approximate | Unique count nhảy giữa các lần xem | Ép precision_threshold hoặc dùng terms count |
| Terms top N cut-off | Pie chart có “Other” lớn | Tăng size, tách Top vs Long tail panel |
| Shard size skew | Terms result lệch khi data unbalanced | Tăng shard_size |
| Time bucket UTC | Bucket “ngày X” lệch 7h | time_zone: Asia/Ho_Chi_Minh, dùng calendar_interval |
| Duplicate document count | Order count cao bất thường | cardinality(order_id) hoặc dedup ingest |
| Sum of rates | RPS double-count | Lưu counter, tính rate bằng derivative |
| Sparse data hidden | Line chart nhìn smooth giả | extended_bounds + “Connect null values: Off” |
Cheatsheet
| Việc | Aggregation đúng |
|---|---|
| Đếm document | value_count (chính xác) |
| Đếm unique low cardinality | terms với size lớn |
| Đếm unique high cardinality | cardinality với precision_threshold |
| Sum chính xác | sum field numeric |
| Tổng theo thời gian | date_histogram + sub-aggregation |
| Rate per second | derivative trên counter |
| Top N | terms với shard_size cao |
| Percentile | percentiles (HDR algorithm khi cần precision) |
Lời kết
Dashboard sai không phải vì Kibana sai, mà vì aggregation được dùng không khớp với câu hỏi business. Hai loại lỗi đắt nhất là cardinality approximate trên số nhỏ và time bucket lệch múi giờ. Cả hai đều ngầm; cả hai đều dẫn tới quyết định sai. Đầu tư 30 phút validate dashboard trước khi share luôn tiết kiệm hơn 3 ngày debug sau khi stakeholder phản hồi.
Bài tiếp theo trong series Kibana từ A đến Z mở Part 3 về Alerts. Bắt đầu với alert rules: ES query, threshold, và burn rate. Đây là cách biến log từ kho lưu trữ thành cảnh báo proactive. Nếu bạn đang gặp pitfall nào khác lạ với aggregation, comment để mình thêm vào bản update.