Case study Nâng cao 5 phút đọc

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
  1. Khi nào pipeline analytics chuyên có giá trị?
  2. Số nào nên ngân sách?
  3. Kiến trúc trông thế nào?
  4. Cấu hình .NET 10 cho ingest?
  5. Aggregator realtime chạy ra sao?
  6. Đường scale-out hỗ trợ?
  7. Tạo failure mode nào?
  8. Khi nào pipeline chuyên là quá liều?
  9. Đi tiếp đâu từ đây?

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ợ?

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?

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

Câu hỏi thường gặp

Sao Kafka thay vì queue thường?
Ba lý do: throughput (>1M event/giây trên phần cứng vừa), replay bền (chạy lại aggregation sau bug fix), và semantic nhiều consumer (cùng stream nuôi dashboard, data warehouse, updater autocomplete). Queue thường từ chương 6 là cho task queue; analytics là use case Kafka kinh điển.
ClickHouse, BigQuery, hay Snowflake?
ClickHouse self-host rẻ nhất ở scale và cho query OLAP dưới giây; tốt khi có team data riêng. BigQuery và Snowflake là managed - chi phí cao hơn nhưng không gánh vận hành. Cho shop .NET không có team data riêng, BigQuery qua tích hợp GCP thường là điểm khởi đầu đúng. ClickHouse là nâng cấp khi hoá đơn đáng sợ.
Xử đổi schema không phá pipeline ra sao?
Ba quy tắc: (1) mọi event có field 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.
Lambda architecture - còn đáng?
Ít hơn trước. Lambda gốc có 'speed layer' nhanh (realtime xấp xỉ) cộng 'batch layer' chậm (đúng, có độ trễ). Engine streaming hiện đại (Flink, materialised view ClickHouse) gộp cả hai thành một. Dùng lambda chỉ khi công nghệ batch và stream khác vendor và bạn cần audit trail của batch run.