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) và 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ông400 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.