Thực hành viết và kiểm thử truy vấn Snuba trong hệ thống dữ liệu Sentry

Snuba là thành phần cốt lõi trong kiến trúc giám sát của Sentry, đóng vai trò như một lớp truy vấn thời gian thực trên nền tảng ClickHouse và Kafka. Việc hiểu cách xây dựng, gửi và kiểm tra các truy vấn Snuba là kỹ năng thiết yếu khi phát triển hoặc mở rộng chức năng phân tích sự kiện trong hệ thống.

Khám phá mô hình dữ liệu Snuba

Trước khi viết truy vấn, cần xác định rõ bộ dữ liệu (dataset), thực thể (entity)lược đồ (schema) tương ứng. Mỗi dataset đại diện cho một tập hợp logic các sự kiện — ví dụ: errors, discover, hay transactions.

Danh sách các entity khả dụng có thể được liệt kê bằng lệnh CLI:

snuba entities list

Kết quả mẫu:

Declared Entities:
errors
discover
transactions
sessions
metrics_counters
...

Để xem chi tiết cấu trúc của một entity cụ thể — bao gồm danh sách cột, kiểu dữ liệu và các quan hệ với entity khác — dùng lệnh:

snuba entities describe errors

Đầu ra sẽ hiển thị:

Entity errors
    Entity schema
    --------------------------------
    event_id UUID
    project_id UInt64
    timestamp DateTime64(3, 'UTC')
    platform String
    environment Nullable(String)
    tags.key Array(String)
    tags.value Array(String)
    ...

    Relationships
    --------------------------------
        events
        --------------------------------
        Destination: discover
        Type: LEFT
            Join keys
            --------------------------------
            project_id = LEFT.project_id
            event_id = LEFT.event_id

Các thông tin này giúp đảm bảo truy vấn tham chiếu đúng trường và tuân thủ ràng buộc liên kết.

Xây dựng truy vấn Snuba với SnQL và SDK

Ngôn ngữ truy vấn chính thức của Snuba là SnQL — cú pháp gần giống SQL nhưng được thiết kế đặc biệt để tối ưu hóa với ClickHouse và hỗ trợ các khái niệm như phân vùng thời gian, mẫu dữ liệu (sampling), và điều kiện nhất quán (consistency).

Thay vì viết SnQL dạng chuỗi thuần túy, nên sử dụng snuba-sdk để xây dựng truy vấn theo cách lập trình, đảm bảo tính an toàn về kiểu và dễ bảo trì:

from snuba_sdk import Query, Entity, Column, Function, Condition, Op, Limit, Offset, Granularity
from datetime import datetime, timedelta

now = datetime.utcnow()
one_hour_ago = now - timedelta(hours=1)

query = Query(
    dataset="errors",
    match=Entity("errors"),
    select=[
        Column("project_id"),
        Function("count", [], "event_count"),
        Function("toStartOfHour", [Column("timestamp")], "hour_bucket")
    ],
    groupby=[Column("project_id"), Column("hour_bucket")],
    where=[
        Condition(Column("timestamp"), Op.GTE, one_hour_ago),
        Condition(Column("project_id"), Op.IN, [42, 105, 201])
    ],
    limit=Limit(50),
    offset=Offset(0),
    granularity=Granularity(3600)
)

SDK tự động xử lý việc chuyển đổi sang SnQL, thêm tiền tố `_snuba_` cho các cột, và chèn các điều kiện mặc định như deleted = 0 hoặc project_id IN (...) nếu cần.

Gửi truy vấn từ ứng dụng Sentry

Trong mã nguồn Sentry, truy vấn Snuba thường được gửi qua lớp SnubaQueryExecutor hoặc hàm tiện ích raw_query() trong module sentry.utils.snuba. Đây là cách được khuyến nghị vì nó tích hợp sẵn:

  • Bộ nhớ đệm (cache) dựa trên hash truy vấn
  • Cơ chế retry tự động khi gặp lỗi mạng hoặc timeout
  • Hỗ trợ gửi hàng loạt (batch) nhiều truy vấn cùng lúc
  • Thu thập metrics và timing chi tiết

Ví dụ gọi từ Python trong môi trường Sentry:

from sentry.utils.snuba import raw_query

result = raw_query(
    query,
    referrer="web.discover.transactions.chart",
    use_cache=True,
    # Thêm các tùy chọn nâng cao
    consistent=True,
    turbo=False,
    debug=True
)

Đối tượng trả về có cấu trúc chuẩn:

{
  "data": [
    {"project_id": 42, "event_count": 172, "hour_bucket": "2024-04-05T14:00:00"},
    {"project_id": 105, "event_count": 89, "hour_bucket": "2024-04-05T14:00:00"}
  ],
  "meta": [
    {"name": "project_id", "type": "UInt64"},
    {"name": "event_count", "type": "UInt64"},
    {"name": "hour_bucket", "type": "DateTime"}
  ],
  "timing": { ... },
  "stats": { ... }
}

Kiểm thử nhanh qua giao diện Web và curl

Khi chạy Snuba cục bộ (ví dụ: bằng docker-compose up snuba), bạn có thể truy cập giao diện thử nghiệm tại:

http://localhost:1218/errors/snql

Nhập SnQL dưới dạng JSON trong trường query:

{
  "query": "MATCH (errors) SELECT count() AS count BY project_id WHERE timestamp >= toDateTime('2024-04-05T12:00:00') AND project_id IN tuple(42, 105)",
  "dataset": "errors",
  "debug": true,
  "consistent": true
}

Tương đương với lệnh curl:

curl -X POST http://localhost:1218/errors/snql \
  -H "Content-Type: application/json" \
  -d '{
        "query": "MATCH (errors) SELECT count() AS count BY project_id WHERE timestamp >= toDateTime(\"2024-04-05T12:00:00\") AND project_id IN tuple(42, 105)",
        "debug": true
      }'

Phân tích phản hồi và xử lý lỗi

Mã trạng thái HTTP phổ biến:

  • 200 OK: Truy vấn thành công
  • 400 Bad Request: Lỗi cú pháp hoặc vi phạm ràng buộc (ví dụ: thiếu điều kiện thời gian)
  • 429 Too Many Requests: Vượt ngưỡng giới hạn tốc độ
  • 500 Internal Server Error: Lỗi hệ thống (ClickHouse timeout, kết nối thất bại, v.v.)

Một phản hồi thành công ở chế độ debug=true bao gồm khối sql — đây là câu lệnh ClickHouse thực tế đã được Snuba sinh ra — và khối stats chứa các chỉ số vận hành quan trọng như tên bảng (clickhouse_table), mức độ mẫu (sample), số dòng kết quả (result_rows), và query_id để trace log.

Lỗi xác thực trả về dạng:

{
  "error": {
    "type": "invalid_query",
    "message": "missing >= condition on column timestamp for entity errors"
  }
}

Lỗi ClickHouse sẽ có "type": "clickhouse" và thường kèm theo stack trace từ phía database, hữu ích để chẩn đoán hiệu năng hoặc cấu hình bảng.

Thẻ: snuba ClickHouse sentry snql python-sdk

Đăng vào ngày 9 tháng 7 lúc 17:26