blog

Tự dựng dashboard giám sát uptime cho 30 website khách hàng

Khách gọi báo web sập trước khi tôi biết. Cách theo dõi uptime, tốc độ phản hồi và hạn SSL của 30 site mà không sinh ra một núi cảnh báo rác.

Next.jsNode.jsTailwind CSS
Dashboard giám sát uptime và hiệu năng website khách hàng

Server sập lúc hai giờ sáng, chứng chỉ SSL hết hạn vào đúng cuối tuần, trang chậm dần sau mỗi lần khách tự cài thêm plugin. Người biết cuối cùng thường là tôi, sau khi khách gọi điện.

Dashboard này ra đời để đảo ngược thứ tự đó. Công nghệ không có gì mới; phần đáng nói là vài quyết định nhỏ khiến nó dùng được hằng ngày thay vì bị tắt thông báo sau một tuần.

Ba chỉ số cần theo dõi: uptime, TTFB, hạn SSL

Cám dỗ khi làm công cụ giám sát là đo mọi thứ đo được. Tôi giới hạn còn ba chỉ số, vì đó là ba thứ khiến khách gọi điện:

Site còn sống không. Kiểm tra HTTP status mỗi phút.

Trang có chậm đi không. Đo TTFB, lưu chuỗi số liệu theo thời gian để nhìn ra xu hướng. Một site chậm dần đều trong ba tuần nguy hiểm hơn một site sập mười phút, vì không ai để ý.

SSL còn hạn bao lâu. Cảnh báo trước 14 ngày. Loại sự cố này phòng được hoàn toàn, nhưng không có nhắc thì năm nào cũng dính một lần.

Phần backend là một worker Node.js chạy cron, ghi vào SQLite. Quy mô vài chục site với một điểm dữ liệu mỗi phút thì SQLite thoải mái, không cần dựng Postgres. Dashboard là Next.js, biểu đồ render phía client.

Chống báo động giả: ngưỡng ba lần fail liên tiếp

Tuần đầu tiên, mạng phía máy giám sát chập chờn khoảng ba mươi giây. Hệ quả: mười mấy site "sập" cùng lúc, Telegram nổ liên tục, rồi tất cả tự "hồi phục". Không site nào có vấn đề thật.

Vài lần như vậy là người ta tắt thông báo, và lần sập thật trôi qua trong im lặng.

Quy tắc sau khi sửa: chỉ báo động khi fail liên tiếp N lần, và luôn báo khi phục hồi.

js
// worker/check.js — chống cảnh báo giả do lỗi mạng thoáng qua
const FAIL_THRESHOLD = 3

async function checkSite(site) {
  const started = Date.now()
  try {
    const res = await fetch(site.url, { signal: AbortSignal.timeout(10_000) })
    await recordMetric(site.id, { status: res.status, ttfb: Date.now() - started })

    // Chỉ báo phục hồi nếu trước đó đã thật sự báo động
    if (site.failCount >= FAIL_THRESHOLD) notify(`✅ ${site.name} đã phục hồi`)
    site.failCount = 0
  } catch {
    site.failCount += 1
    // Dùng === thay vì >= : chỉ bắn đúng một lần khi vượt ngưỡng
    if (site.failCount === FAIL_THRESHOLD) {
      notify(`🔴 ${site.name} không phản hồi (${FAIL_THRESHOLD} lần liên tiếp)`)
    }
  }
}

Chi tiết dễ bỏ sót nằm ở dấu ===. Viết >= thì mỗi lượt kiểm tra tiếp theo trong lúc site vẫn đang sập lại bắn thêm một tin nhắn: site sập nửa tiếng thành ba mươi thông báo. Với ===, một sự cố chỉ sinh đúng một cảnh báo lúc bắt đầu và một lúc kết thúc.

Ba lần liên tiếp với chu kỳ một phút nghĩa là phát hiện sự cố sau khoảng ba phút. Đổi lại là gần như không còn báo động giả, đánh đổi đáng giá với loại website này.

Downsample dữ liệu trước khi vẽ biểu đồ

Sau vài tuần chạy, mỗi site tích luỹ hàng chục nghìn điểm đo. Chọn khoảng "30 ngày" là trang đứng hình vài giây.

Tôi không đổi thư viện biểu đồ. Không màn hình nào hiển thị nổi 43.000 điểm trên một biểu đồ rộng 600px, và mắt người cũng không đọc được. Cách sửa là gộp dữ liệu trước khi vẽ, ngay từ phía API:

jsx
// Tối đa ~200 điểm cho mỗi biểu đồ, gộp trung bình theo bucket thời gian
const points = useMemo(
  () => downsample(metrics, { maxPoints: 200, by: 'avg' }),
  [metrics]
)

return <ResponseChart data={points} className="h-40 w-full" />

Biểu đồ nhìn y hệt, trang phản hồi tức thì. Một lưu ý khi gộp: trung bình sẽ san phẳng các đỉnh nhọn. Cần nhìn thấy spike thì giữ thêm giá trị max của mỗi bucket và vẽ chồng lên.

Thiết kế dashboard quanh đúng một câu hỏi

Dashboard chỉ có giá trị nếu nó là tab đầu tiên tôi mở mỗi sáng. Mọi quyết định thiết kế vì vậy phục vụ đúng một câu hỏi: hôm nay có site nào cần đụng tay không?

Nên trang chủ không có biểu đồ đẹp. Nó là một danh sách xếp theo mức độ nghiêm trọng: site đang có vấn đề nằm trên cùng, site khoẻ thu lại thành một dòng. Biểu đồ chi tiết nằm ở trang con, dành cho lúc cần điều tra.

Công cụ nội bộ hay chết vì được thiết kế như sản phẩm thương mại: nhiều màn hình, nhiều tuỳ chọn, nhiều thứ để cấu hình. Cái dùng được lâu thường trả lời đúng một câu hỏi và trả lời thật nhanh.

Phần xử lý các site bị dashboard này bắt là chậm nằm ở bài tối ưu PageSpeed cho WordPress. Cần người theo dõi hộ sau bàn giao thì xem dịch vụ quản lý website.

Bài viết liên quan

tất cả bài viết →