Thiết kế pipeline event analytics (Kafka + ClickHouse)
Cách xây pipeline event analytics trong .NET: SDK ingest, streaming Kafka, evolution schema, ClickHouse cho OLAP, và trade-off lambda architecture.
Mục lục
Pipeline event analytics là nơi mọi ý tưởng queue, stream, lưu trữ trong series gộp thành một kiến trúc. App emit event "click nút"; dashboard hiển thị DAU một giờ sau. Giữa hai điểm đó là Kafka, evolution schema, materialised view, và trade-off chi phí quyết định bạn tiêu bao nhiêu cho data. Chương này thiết kế phiên bản .NET.
Khi nào pipeline analytics chuyên có giá trị?
Ba tín hiệu.
Volume vượt công cụ vận hành. Application Insights hay Mixpanel đắt sau 100M event/tháng. Pipeline self-host trả lại ở scale.
Nhiều consumer downstream. Dashboard, data warehouse, ranking autocomplete, phát hiện gian lận, ML feature store - tất cả đều muốn cùng event stream. Kafka tập trung tách producer khỏi consumer.
Replay quan trọng. Bug fix trong job analytics cần tính lại data tháng trước. Stream giữ 7 ngày event làm cái đó nhẹ; queue drop message khi consume thì không.
Nếu dưới 1M event/ngày và một dashboard, Application Insights hay PostHog Cloud rẻ hơn xây.
Số nào nên ngân sách?
Event / ngày 500M
Avg size event 1 KB (sau compress: 200 byte)
Storage / năm (raw) 500M * 365 * 1 KB = 180 TB
Storage / năm (compressed) 36 TB
Storage / năm (ClickHouse) ~10 TB sau columnar + dictionary
Tốc độ ingest đỉnh 500M / 100K * 5 = 25K event/giây
Broker Kafka cần 3 (replication factor 3)
Node ClickHouse cần 3-6 VM nhỏ
Tỉ lệ compress (5x raw, 18x columnar) là cái khiến cái này khả chi. Không có nó, cùng workload cần phần cứng 10x.
Kiến trúc trông thế nào?
flowchart LR
Apps[SDK Mobile / Web] -->|HTTP batch| Ingest[ASP.NET Core Ingest]
Ingest -->|partition theo user| K[(Kafka)]
K --> RT[Aggregator realtime<br/>Flink / .NET worker]
K --> Loader[Loader batch]
Loader --> CH[(ClickHouse)]
RT --> CH
Apps2[Dashboard] --> CH
K --> Lake[(Data lake / S3)]
Năm tier. Ingest nhận event batch từ client, validate schema, publish Kafka. Aggregator realtime tính rollup 5 phút. Loader batch ghi event raw vào ClickHouse. Data lake giữ bản bất biến để replay. Dashboard query ClickHouse.
Cấu hình .NET 10 cho ingest?
public record AnalyticsEvent(
string EventName, Guid UserId, DateTimeOffset Timestamp,
Dictionary<string, object> Properties, int SchemaVersion);
app.MapPost("/v1/events", async (AnalyticsEvent[] batch, IKafkaProducer producer,
IValidator validator) =>
{
if (batch.Length > 1000) return Results.BadRequest("Batch too large.");
foreach (var ev in batch)
{
if (!validator.Validate(ev)) continue; // log và skip
await producer.ProduceAsync("events.raw",
key: ev.UserId.ToString(),
value: JsonSerializer.Serialize(ev));
}
return Results.Accepted();
})
.RequireRateLimiting("ingest")
.AllowAnonymous(); // SDK dùng API key, không user auth
// Producer setup với batching cho throughput
public class KafkaProducer
{
private readonly IProducer<string, string> _producer;
public KafkaProducer(IConfiguration config)
{
var conf = new ProducerConfig
{
BootstrapServers = config["Kafka:Brokers"],
CompressionType = CompressionType.Zstd,
LingerMs = 50, // batch tới 50 ms
BatchSize = 100_000,
Acks = Acks.All
};
_producer = new ProducerBuilder<string, string>(conf).Build();
}
public Task ProduceAsync(string topic, string key, string value)
=> _producer.ProduceAsync(topic, new() { Key = key, Value = value });
}
Ba chi tiết. Partition key user ID giữ event của một user theo thứ tự trên một partition Kafka - hữu ích cho funnel analysis. Compress Zstd tốt hơn gzip 2-3x. Ack=All đảm bảo write bền tới mọi replica trước khi ack producer.
Aggregator realtime chạy ra sao?
flowchart LR
K[(Kafka events.raw)] --> Worker[.NET worker<br/>cửa sổ 5 phút]
Worker --> RT[(events.rollup_5min)]
RT --> CH[(ClickHouse<br/>materialised view)]
Worker --> CH
CH --> Dashboard[Grafana]
Worker nền .NET consume từ Kafka, tích event trong cửa sổ 5 phút, tính counter theo user/event-type, và flush aggregate sang bảng ClickHouse. Materialised view ClickHouse tính lại aggregate giờ và ngày tăng dần. Dashboard query bảng rollup, không phải event raw.
Đường scale-out hỗ trợ?
- Ingest: stateless, scale replica ASP.NET Core.
- Kafka: thêm broker, tăng số partition cho song song.
- Aggregator: song song qua consumer group Kafka; một worker mỗi partition.
- ClickHouse: shard theo date; query optimiser lo phân phối theo shard.
- Schema registry: Confluent Schema Registry hoặc bảng Postgres tự xây; bắt buộc khi có nhiều app producer.
Cho >10M event/giây, bạn cần cluster Kafka chuyên mỗi region cộng replication; scale đó hiếm và xứng team data.
Tạo failure mode nào?
- Mất event khi producer crash - buffer in-memory chưa flush. Phòng: SDK persist xuống disk local trước in-memory; Acks=All trên producer Kafka đảm bảo write bền trước khi client thấy success.
- Lag aggregator - rollup realtime trễ vài phút. Phòng: alert trên lag consumer Kafka; scale worker.
- Phá schema - producer ship hình event mới; consumer crash. Phòng: schema registry với rule tương thích ngược; reject thay đổi không tương thích lúc PR.
- Dashboard chậm - query ClickHouse không partition. Phòng: pre-aggregate qua materialised view; quota cho query dài.
Khi nào pipeline chuyên là quá liều?
Dưới ~5M event/ngày, công cụ analytics host thắng về chi phí và ops. Mixpanel, Amplitude, PostHog Cloud, Application Insights đều cho phần lớn cùng dashboard không tốn chi phí kỹ thuật data. Xây pipeline chuyên ở volume mà hoá đơn biện minh team.
Đi tiếp đâu từ đây?
Bạn đã hoàn tất case study. Tiếp: cách trả lời phỏng vấn system design
- chương meta biến chín case study thành khung phỏng vấn lặp được. Sau đó, kết luận khép series với năm bài học còn lại sau mọi chương.
Câu hỏi thường gặp
Sao Kafka thay vì queue thường?
ClickHouse, BigQuery, hay Snowflake?
Xử đổi schema không phá pipeline ra sao?
schema_version; (2) consumer chấp nhận field không biết; (3) xoá chờ một chu kỳ giữ đầy đủ. Dùng Protobuf hay Avro cho typing chặt nếu team lớn; JSON ổn cho team nhỏ phối hợp qua review PR.