Channel #alerts của một team SRE từng đạt mốc 14000 message một tuần. Không ai đọc nữa. Khi sự cố thật xảy ra, message trigger nằm trong rừng noise, mất 40 phút mới có người để ý. Đó là alert fatigue: hệ thống cảnh báo mất tác dụng vì bão hoà.

Bài này là phần khép Part 3 của series. Sau khi có rule, có connector, có SLO, mảnh ghép cuối là làm sao mỗi alert tới on-call là một alert đáng đọc.

Mục tiêu:

  • Hiểu khác biệt giữa alert state, notification, action
  • Setup dedup ở rule level và connector level
  • Throttle action để giảm spam mà không miss critical
  • Tạo pattern routing theo severity
  • Audit và tune rule dựa trên acknowledge rate

Phần 1: Mental model về notification flow

Trước khi tune, phải biết exactly cái gì gây spam.

Sequence một alert đi qua:

1. Rule chạy theo schedule (mỗi 1m)
2. Evaluate condition -> match document/metric
3. Alert instance state đổi (recovered -> active, active -> recovered)
4. Notification triggered (theo notifyWhen setting)
5. Action runs (mỗi connector)
6. Connector dispatches (HTTP/SMTP/PagerDuty)
7. External service deduplicates (nếu support)

Spam có thể xảy ra ở bước 4, 5, 6, 7. Mỗi bước có pattern fix riêng.

Phần 2: notifyWhen

Setting quan trọng nhất ở rule level. Bốn lựa chọn:

notifyWhenHành viKhi dùng
onActionGroupChangeNotify khi state đổi (active->recovered hoặc ngược lại)Default tốt cho hầu hết case
onActiveAlertNotify mỗi lần rule chạy nếu vẫn activeCần update liên tục (rare)
onThrottleIntervalNotify mỗi N phút nếu vẫn activeLong-running incident, muốn ping reminder
onActionGroupChange (mới) + RecoveryTrigger + recover, không spam giữaStandard

Setup qua UI: trong rule editor, mục Notify chọn dropdown.

Setup qua API:

{
  "notify_when": "onActionGroupChange",
  "throttle": null
}

Hoặc với throttle interval:

{
  "notify_when": "onThrottleInterval",
  "throttle": "1h"
}

Kết hợp: rule check mỗi phút, alert state active liên tục 6 giờ, với throttle: 1h thì on-call nhận đúng 6 ping (mỗi giờ một lần), không phải 360 ping.

Phần 3: Throttle ở action level

Kibana 8.5+ cho phép throttle riêng cho từng action, không chỉ rule level. Quan trọng khi một rule gửi cả Slack (high frequency OK) và PagerDuty (cần dedup chặt).

Trong UI: trong action editor, mục Run when có dropdown:

  • onActionGroupChange: standard
  • onActiveAlert: spam mỗi check
  • Custom: chọn frequency

Pattern điển hình:

ActionThrottleLý do
Slack #alerts10 phútChannel có thể chịu reminder
Email backup1 giờEmail khó chịu nếu quá nhiều
PagerDuty1 phút (dedupKey)Page on-call, dedup tại PagerDuty
Webhook logonActionGroupChangeChỉ log state change

Phần 4: Group alerts ở rule level

Thay vì rule trigger 100 alert song song (mỗi alert cho 1 entity), gom lại thành 1 alert tổng.

Use case: top N service failing

Rule “Error rate > 5%” với group by service.name. Nếu deploy bad code, 30 service đều fail. Rule sinh 30 alert song song → 30 Slack message → channel spam.

Fix 1: thay vì group by, dùng aggregate filter:

"params": {
  "searchType": "esQuery",
  "esQuery": "...",
  "size": 0,
  "threshold": [100],
  "thresholdComparator": ">",
  "aggType": "count",
  "groupBy": "all"
}

groupBy: all thay vì groupBy: top. Rule sinh 1 alert “total > 100” thay vì 30 alert.

Fix 2: dùng metadata context.groups trong message template để liệt kê:

Alert: high error rate across {{context.groups.length}} services
Services: {{#context.groups}}{{.}}, {{/context.groups}}
View: {{context.viewInAppUrl}}

Một message với context đầy đủ, không phải 30 message rời.

Phần 5: Dedup ở connector level

Mỗi loại connector có cơ chế dedup riêng.

PagerDuty dedupKey

{
  "dedupKey": "{{rule.id}}-{{context.group}}",
  "eventAction": "trigger"
}

PagerDuty merge tất cả event cùng dedupKey thành 1 incident. Khi alert recover, gửi event với cùng dedupKey và eventAction: "resolve" để auto-close.

Pitfall: nếu dedupKey thay đổi giữa các lần trigger (do template variable thay đổi), mỗi event là incident riêng. Test template bằng cách trigger 2 lần liên tiếp và xem có dedup không.

Slack thread

Không native dedup, nhưng pattern: gửi message đầu tiên là parent, các update tiếp theo gửi vào thread.

Slack API support thread_ts field, nhưng Kibana connector mặc định không có. Workaround: dùng webhook connector thay vì Slack connector native, custom payload:

{
  "channel": "alerts-prod",
  "text": "{{rule.name}}",
  "thread_ts": "{{rule.id}}"
}

Lưu ý: thread_ts phải là timestamp message gốc, không phải rule id. Cần custom code ở receiver side để track.

Email aggregation

Email khó dedup. Pattern: gửi email digest mỗi giờ, không gửi từng alert.

Setup: rule với notifyWhen: onThrottleInterval, throttle 1 giờ. Mỗi giờ, action gửi 1 email với toàn bộ alert active trong giờ qua (qua context.results nếu rule support).

Webhook idempotency

Endpoint custom phải tự dedup. Pattern: dùng rule.id + alert.id + state làm idempotency key trong DB.

INSERT INTO alerts (key, ...) VALUES (?, ...) ON CONFLICT (key) DO UPDATE SET ...

Hoặc dùng Redis SETNX với TTL:

SETNX alert:rule_xxx:alert_yyy:active 1 EX 3600

Nếu key đã tồn tại → skip notification.

Phần 6: Severity routing

Không phải alert nào cũng cần wake up on-call. Pattern routing:

SeverityChannelLatency
Critical (SLO burn 14.4x, prod down)PagerDutyPage ngay
High (SLO burn 6x, partial outage)Slack mention @sre< 15 phút
Medium (SLO burn 1x, anomaly)Slack channel< 1 giờ
Low (warning, capacity)Email digestDaily

Implement: tạo nhiều connector riêng cho mỗi channel. Trong rule action config, gắn nhiều action với group khác nhau:

"actions": [
  {
    "group": "default",
    "id": "<SLACK_INFO>",
    "params": {"message": "{{rule.name}}: anomaly"},
    "frequency": {"notify_when": "onThrottleInterval", "throttle": "1h"}
  },
  {
    "group": "critical",
    "id": "<PAGERDUTY_ID>",
    "params": {"severity": "critical"},
    "frequency": {"notify_when": "onActionGroupChange"}
  }
]

Rule type quyết định action group available (default, recovered, critical, etc.). Burn rate rule có nhiều action group cho từng window threshold.

Phần 7: Audit và tune

Sau 2-4 tuần chạy, audit rule để loại bỏ rule không value.

Metric đo

  • Trigger count: rule trigger bao nhiêu lần trong tuần
  • Acknowledge rate: trong PagerDuty, % alert được ack vs auto-resolve
  • Resolution time: alert active bao lâu trước khi recover

Query metric qua Kibana saved object index:

GET .kibana_alerting_cases/_search
{
  "query": {
    "term": { "type": "alert" }
  },
  "_source": ["alert.name", "alert.params", "alert.actions"]
}

Kết hợp với event log index .kibana-event-log-* để biết execution history.

Quy tắc audit

Rule có trigger > 100 lần/tuần, ack < 10%: alert hoặc quá nhạy hoặc không actionable. Cân nhắc:

  • Tăng threshold (nhạy hơn = nhiều noise hơn)
  • Thêm time window (1m -> 5m sustained)
  • Giảm severity (Critical -> Medium)
  • Disable nếu không actionable

Rule có trigger > 50 lần/tuần, auto-resolve > 80%: alert flapping. Fix bằng:

  • for N consecutive checks (Kibana 8.6+)
  • Tăng time window
  • Threshold dải rộng hơn

Rule có trigger 0 lần trong 30 ngày: không value hoặc condition không match thực tế. Disable hoặc revisit query.

Phần 8: Pitfall storytelling

Ca 1: PagerDuty incident “không close”

Rule recover sau 30 phút, PagerDuty incident vẫn open. Lý do: recovery action có dedupKey {{rule.id}}-{{context.group}} nhưng trigger action có {{rule.name}}-{{context.group}} (nhân viên copy paste sai). PagerDuty thấy hai event khác key → không dedup.

Fix: chuẩn hoá dedupKey format trong cả trigger và recovery, hardcode string thay vì template nếu có thể.

Ca 2: Throttle bị bypass

Rule với throttle: 1h. Bị OOM restart Kibana node. Sau restart, throttle counter reset, alert active gửi notification ngay lập tức cho dù vừa gửi 5 phút trước.

Fix: chấp nhận edge case này, hoặc dùng PagerDuty dedup làm fallback (vì PagerDuty không reset state khi Kibana restart).

Ca 3: Action chạy 4 lần cho 1 alert

Rule trigger 1 alert nhưng channel nhận 4 Slack message. Debug:

  1. Check Kibana cluster có nhiều node Kibana không.
  2. Một node giàn nó tin rule chưa chạy (clock skew) → chạy lại.
  3. Hoặc rule có nhiều action group cùng group trigger.

Fix: sync NTP cho cluster, hoặc disable Kibana node thừa nếu không cần HA.

Ca 4: Email digest mất alert

Setup throttle: 1h cho email. Trong giờ X có 10 alert active, expect digest list 10 alert. Thực tế email chỉ show 1 alert đầu tiên.

Lý do: Kibana action context chỉ chứa snapshot của alert hiện tại (cái mới nhất), không phải tích luỹ. Để có digest thật, dùng webhook → backend tự gom → gửi digest qua email.

Phần 9: Best practices tổng kết

PracticeCách làm
Set notifyWhen: onActionGroupChange mặc địnhTránh notification mỗi check
Throttle theo channel (Slack 10m, Email 1h)Phù hợp tần suất kênh
dedupKey ổn định cho PagerDutyĐảm bảo merge incident
Group alert ở rule levelTránh 30 alert song song
Severity routingCritical -> page, Low -> digest
Audit trigger count + ack rate hàng thángLoại rule không value
for N consecutive checksTránh flapping
Maintenance window cho planned changeBypass alert đúng lúc

Cheatsheet

SettingDefaultRecommended
notifyWhenonActionGroupChangeonActionGroupChange
throttle rule levelnonenone (dùng action level)
Slack action throttleonActionGroupChange10m
PagerDuty action throttleonActionGroupChange1m
Email action throttleonActionGroupChange1h
Schedule1m1m (5m cho rule chậm)
Time window5m2 * ingest_lag + schedule
Consecutive checks12-3 cho rule flappy

Lời kết

Alert fatigue không phải vấn đề kỹ thuật, mà là vấn đề thiết kế. Kibana có đủ tool: notifyWhen, throttle, dedupKey, severity routing. Cái thiếu thường là disciplined audit mỗi tháng để loại rule noise. Một channel #alerts có 50 message/tuần với 80% được đọc đáng giá hơn một channel 5000 message với 5% được đọc.

Đây là bài cuối Part 3 của series Kibana từ A đến Z. Tiếp theo là Part 4 về Security & Access Control: cách multi-team share cùng một cluster mà không giẫm chân nhau, cấu hình spaces, RBAC, audit log cho SOC2/ISO. Nếu bạn đang có channel alert đầy noise và muốn audit, chia sẻ con số ack rate hiện tại; mình có thể giúp suggest từng rule cụ thể nên tune ra sao.