Apache Kafka 4.0 KRaft 2026 - Loại bỏ ZooKeeper, KIP-848 Next-Gen Consumer Group và Tiered Storage cho Event Streaming Production

Posted on: 4/16/2026 3:11:01 PM

1. Cột mốc lớn nhất của Kafka kể từ ngày ra đời

Apache Kafka 4.0 (released cuối năm 2025) là phiên bản đánh dấu nhiều thay đổi cấu trúc nhất kể từ khi LinkedIn open-source dự án này năm 2011. Lần đầu tiên trong lịch sử, một broker Kafka khởi động không cần ZooKeeper — gói tarball thậm chí không còn binary zookeeper-server-start.sh. Cùng lúc, KRaft trở thành mode mặc định và duy nhất, KIP-848 đẩy giao thức consumer group sang server-side rebalance, KIP-932 bổ sung Share Group để Kafka có thể đóng vai trò một queue đúng nghĩa, và Tiered Storage GA cho phép một cluster nhỏ giữ hàng petabyte log mà không cần thêm broker.

Bài viết này không phải tutorial khởi động Kafka, mà là phân tích kiến trúc các thay đổi lõi của 4.0 và cách chúng định hình lại pattern triển khai event streaming production năm 2026: từ replication metadata Raft, batch fetch của consumer mới, đến cách Tiered Storage tách hot/cold để cluster co lại 60–80% chi phí mà vẫn giữ throughput. Cuối bài là phần migration thực tế từ ZooKeeper-mode 3.x sang KRaft 4.0 và pattern tích hợp với hệ thống .NET 10 / Vue.js qua Confluent.Kafka 2.x.

0Số process ZooKeeper trong cluster Kafka 4.0
~10xTốc độ controller failover so với ZK mode
2M+Số partition mỗi cluster KRaft hỗ trợ tốt
-65%Chi phí storage trung bình khi bật Tiered Storage

Kafka 4.0 trong một câu

Kafka 4.0 hoàn tất hành trình "ZooKeeper-less" bắt đầu từ KIP-500, biến controller thành một quorum Raft viết hoàn toàn bằng Java, và song song mở khóa ba khả năng cho cluster: scale tới hàng triệu partition, phục vụ workload kiểu queue qua Share Group, và đẩy log cũ ra object storage để giữ broker disk gọn gàng.

2. Vì sao ZooKeeper phải ra đi

Suốt 14 năm, kiến trúc Kafka kinh điển có hai cụm: cluster Kafka brokers chứa data, và cluster ZooKeeper (3 hoặc 5 node) chứa metadata — danh sách topic, partition assignment, ACL, controller election. Mô hình này từng hợp lý khi metadata còn nhỏ, nhưng đã lộ bốn yếu điểm chí mạng khi cluster lớn lên:

  • Hai stack vận hành. Team SRE phải biết cả Kafka lẫn ZooKeeper, tune JVM khác nhau, monitor riêng, backup riêng, upgrade lệch pha. Thực tế nhiều outage Kafka gốc rễ ở ZK chứ không phải broker.
  • Controller cold-start chậm. Khi controller hiện tại chết, controller mới phải load full metadata từ ZooKeeper qua một loạt API call sequential. Một cluster 200k partition mất 30–60 giây để controller mới sẵn sàng — trong thời gian đó produce/consume rate tụt mạnh.
  • Trần partition. ZK lưu mỗi partition như một znode, và watch event được fan-out theo cách không scale tuyến tính. Trên thực tế ai chạm mốc 200k partition đều biết "ZK đang giãy".
  • Snapshot/replay kém. Metadata trong ZK không có khái niệm log — chỉ có state. Mọi thay đổi đều full-write. Restore từ backup chậm và rủi ro inconsistency.

KIP-500 (đề xuất từ năm 2019) chốt định hướng: viết lại metadata thành một topic Kafka đặc biệt tên __cluster_metadata, replicate qua Raft, và dùng chính các broker (hoặc một nhóm node controller riêng) làm quorum. Sau gần 6 năm hoàn thiện, 4.0 hợp nhất tất cả: KRaft là mặc định, ZK code path bị xóa hoàn toàn, gọn gàng từ codebase đến binary distribution.

3. Kiến trúc KRaft: Raft consensus thay ZooKeeper

KRaft viết tắt của Kafka Raft. Cốt lõi là một quorum 3 hoặc 5 node controller dùng giao thức Raft (biến thể đã được Kafka tinh chỉnh) để thống nhất metadata. Ba node controller bầu một leader; mọi thay đổi metadata (tạo topic, đổi config, di chuyển replica) ghi vào leader rồi replicate sang follower; khi đa số ack, change được commit và phát ra cho toàn bộ broker đăng ký theo cơ chế tail metadata log.

flowchart TB
    subgraph CONTROLLER["Controller Quorum (Raft)"]
        L["Controller Leader"]
        F1["Controller Follower 1"]
        F2["Controller Follower 2"]
        L -. "replicate __cluster_metadata" .-> F1
        L -. "replicate __cluster_metadata" .-> F2
    end
    subgraph DATA["Data Plane (Brokers)"]
        B1["Broker 1
topic partitions"] B2["Broker 2
topic partitions"] B3["Broker 3
topic partitions"] end L -- "metadata changes (push)" --> B1 L -- "metadata changes (push)" --> B2 L -- "metadata changes (push)" --> B3 P(["Producer"]) --> B1 C(["Consumer"]) --> B2

Hình 1: Controller quorum Raft tách khỏi data plane, broker tail metadata log thay vì đọc ZK

3.1. Hai role và pattern triển khai thực tế

Một process Kafka 4.0 có thể đóng một hoặc cả hai vai trò qua tham số process.roles:

  • controller — chỉ tham gia quorum metadata, không phục vụ produce/consume.
  • broker — chỉ làm data plane, đọc metadata từ controller quorum.
  • controller,broker — chế độ combined, một node làm cả hai.

Combined mode tiện cho dev/staging vì chỉ cần 3 node là chạy được. Production khuyến nghị tách hai role: 3 controller node nhỏ (4 vCPU, 16GB RAM, SSD nhanh nhưng dung lượng nhỏ) và N broker node lớn theo workload. Lý do: controller bị disk-fsync sensitive vì mọi metadata change phải fsync trước khi ack; trộn chung với broker (vốn có throughput log dữ liệu khổng lồ) sẽ làm metadata commit lag, kéo cả cluster chậm theo.

# config controller-only
process.roles=controller
node.id=1
controller.quorum.voters=1@ctrl-1:9093,2@ctrl-2:9093,3@ctrl-3:9093
listeners=CONTROLLER://:9093
controller.listener.names=CONTROLLER
log.dirs=/var/lib/kafka/metadata
metadata.log.max.record.bytes.between.snapshots=20971520
# config broker-only
process.roles=broker
node.id=11
controller.quorum.voters=1@ctrl-1:9093,2@ctrl-2:9093,3@ctrl-3:9093
listeners=PLAINTEXT://:9092,INTERNAL://:9094
advertised.listeners=PLAINTEXT://broker-11.anhtu.dev:9092
log.dirs=/data/kafka-logs
num.io.threads=16
num.network.threads=8

3.2. Vì sao failover nhanh hơn 10 lần

Trong ZK mode, controller mới phải tải lại toàn bộ metadata mỗi lần. Trong KRaft, mọi broker đã đang giữ một bản replay của metadata log trong RAM (cập nhật liên tục từ leader). Khi leader chết, một follower được bầu lên trong vòng vài trăm millisecond — không cần load lại gì. Cluster 500k partition chứng kiến controller failover từ 60 giây xuống 5 giây.

Snapshot metadata: cơ chế chống log vô hạn

__cluster_metadata là một topic compacted nhưng vẫn có thể phình to. KRaft dùng cơ chế snapshot: định kỳ ghi state hiện tại ra file .checkpoint, sau đó truncate log trước snapshot. Khi một broker mới join, nó tải snapshot mới nhất rồi replay log delta — giống cách etcd hay LMDB hoạt động. Điều này giúp metadata recovery của broker mới chỉ tốn vài giây dù cluster có hàng triệu partition.

4. KIP-848: Next-Gen Consumer Group Protocol

Giao thức consumer group cũ có một khuyết điểm cố hữu: rebalance là stop-the-world. Khi một consumer rời nhóm hay một partition mới được thêm vào topic, toàn bộ consumer trong group đều dừng nhận message, trả lại partition, đợi group coordinator chạy assignment, rồi nhận partition mới. Với group có 200 consumer và 2.000 partition, một rebalance có thể kéo dài 15–30 giây. Trong các pipeline realtime, đây là khoảng "đứng hình" không chấp nhận được.

KIP-848 (release stable trong 4.0) viết lại giao thức theo bốn nguyên tắc:

  1. Server-side assignment. Group coordinator (chạy trên broker) tự tính assignment cho mọi consumer dựa trên metadata, thay vì chuyển job đó cho một consumer leader trong group. Loại bỏ vai trò "group leader" gây bottleneck.
  2. Incremental rebalance. Coordinator tính delta thay vì recompute toàn bộ. Thêm 1 consumer chỉ làm di chuyển 1/N partition, các consumer khác tiếp tục commit bình thường.
  3. Heartbeat tách join. Heartbeat trở thành kênh riêng để báo cáo state, không gộp với JoinGroup nặng nề như trước. Coordinator phát hiện consumer chết nhanh hơn.
  4. Member epoch. Mỗi consumer giữ một số memberEpoch tăng dần. Coordinator bỏ qua heartbeat lỗi thời, chống race condition khi consumer tạm disconnect rồi quay lại.
sequenceDiagram
    participant C1 as Consumer 1
    participant C2 as Consumer 2 (new)
    participant GC as Group Coordinator
    Note over GC: Old protocol - stop the world
    C1->>GC: JoinGroup
    C2->>GC: JoinGroup
    GC->>C1: SyncGroup wait
    GC->>C2: SyncGroup wait
    GC->>C1: Reassign all partitions
    GC->>C2: Reassign all partitions
    Note over GC: KIP-848 - incremental
    C2->>GC: Heartbeat (memberEpoch=0)
    GC->>GC: Compute delta
    GC->>C2: Assign 50/200 partitions
    GC->>C1: Revoke 50 partitions only
    Note right of C1: Other 150 partitions keep flowing

Hình 2: So sánh rebalance cũ và KIP-848 — incremental, không stop-the-world

Hệ quả production: pipeline có 500 consumer scale up/down mỗi vài phút (autoscaling theo lag) không còn nhìn thấy spike lag mỗi lần rebalance. Lưu ý quan trọng: client cũ vẫn dùng được nhờ broker hỗ trợ song song hai giao thức, nhưng để hưởng KIP-848 phải dùng client phiên bản hỗ trợ — tương đương librdkafka 2.4+, kafka-clients 3.7+, Confluent.Kafka 2.5+.

5. Tiered Storage: tách hot và cold cho cluster TB-PB

Vấn đề kinh tế lớn nhất của Kafka cluster lớn không phải CPU mà là storage. Một topic giữ 14 ngày retention, throughput 200MB/s, nhân ba với replication factor 3 — bạn cần ~700TB SSD chỉ cho topic đó. SSD trên cloud (gp3, premium SSD) đắt khoảng 0.08–0.15 USD/GB/tháng. Trong khi 95% truy vấn lại chỉ chạm vào dữ liệu vài giờ gần nhất.

Tiered Storage (KIP-405, GA trong 3.6, ổn định và mặc định-an-toàn trong 4.0) tách topic data thành hai tier:

  • Local tier trên broker disk — chứa segment "hot" mới nhất (vài giờ tới một ngày). Mọi produce ghi xuống đây. Read từ tail consumer cũng đọc trực tiếp.
  • Remote tier trên object storage (S3, GCS, Azure Blob) — chứa segment "cold" đã được upload và xóa khỏi local. Read từ consumer rewind sẽ tự động fetch từ remote tier qua một RemoteLogManager plugin.
flowchart LR
    P(["Producer"]) --> B["Broker disk
local tier"] B -. "upload segments
after threshold" .-> S3[("Object Storage
S3 / GCS / Blob
remote tier")] B -- "delete after upload
local retention" --> X(("trash")) C1(["Tail consumer
realtime"]) --> B C2(["Replay consumer
old data"]) --> B B -. "transparent fetch" .-> S3

Hình 3: Producer luôn ghi local; segment cũ được tier-up sang object storage; consumer rewind đọc xuyên suốt qua broker

5.1. Cấu hình thực tế

# server-level
remote.log.storage.system.enable=true
remote.log.storage.manager.class.name=org.apache.kafka.server.log.remote.storage.RemoteLogStorageManager
remote.log.metadata.manager.class.name=org.apache.kafka.server.log.remote.metadata.storage.TopicBasedRemoteLogMetadataManager
rsm.config.s3.bucket.name=anhtu-kafka-tier
rsm.config.s3.region=ap-southeast-1

# topic-level
kafka-topics.sh --create --topic events.user.activity \
  --partitions 64 --replication-factor 3 \
  --config remote.storage.enable=true \
  --config local.retention.ms=21600000 \
  --config retention.ms=1209600000

Trong ví dụ trên: dữ liệu 6 giờ đầu sống trên SSD broker (local.retention.ms=21600000), sau đó được upload sang S3 và giữ 14 ngày trên S3. Một consumer mới khởi động và rewind từ đầu sẽ đọc 14 ngày dữ liệu, broker tự fetch segment từ S3 mà ứng dụng không thay đổi gì. Một số kết quả thực tế khi chuyển topic từ all-local sang tiered:

Thông sốAll-local SSDTiered Storage (6h local + S3)
Disk SSD/broker4TB500GB
Số broker để giữ 14 ngày124
Chi phí storage / tháng~5.800 USD~1.900 USD
Tail latency p99 (consumer realtime)8ms8ms (không đổi)
Replay latency segment cũ5ms120ms (cold fetch S3)
Recovery thời gian add broker mới40 phút4 phút

Cảnh báo khi bật Tiered Storage

Tier-up không miễn phí: mỗi GB upload và download S3 đều tính tiền egress nội vùng (~0.01 USD/GB) và request cost (PUT/GET). Nếu workload chủ yếu là replay liên tục dữ liệu cũ (analytics, ML training), Tiered Storage có thể đắt hơn all-local. Hãy đo tỉ lệ "cold read" trước khi bật cho mọi topic. Một số topic ngắn hạn (ví dụ retention 24 giờ) thì không cần tier vì local retention đã trùm hết.

6. KIP-932 - Share Group: Kafka làm queue đúng nghĩa

Một câu hỏi cũ: "Kafka có dùng làm message queue như RabbitMQ được không?". Câu trả lời cũ: "có thể, nhưng gượng ép" — vì model partition của Kafka buộc một consumer trong group độc quyền một partition. Không có cách nào để 5 consumer cùng cạnh tranh nhận message từ một partition kiểu work-stealing. Đây là lý do nhiều team phải duy trì song song RabbitMQ/SQS bên cạnh Kafka chỉ để có "competing consumers".

KIP-932 (preview trong 3.7, GA trong 4.0) thêm khái niệm Share Group: cho phép nhiều consumer chia sẻ cùng một partition với mỗi message được gán cho đúng một consumer xử lý, có ack/nack/redeliver như queue truyền thống. Cơ chế dựa trên một topic state nội bộ ghi lại trạng thái mỗi message được giao cho ai.

flowchart LR
    subgraph TOPIC["topic: jobs.email"]
        P0["partition 0"]
        P1["partition 1"]
    end
    subgraph CG["Consumer Group cũ"]
        CG1["worker A
= partition 0"] CG2["worker B
= partition 1"] CG3["worker C
= idle (3 worker > 2 partition)"] end subgraph SG["Share Group mới"] SG1["worker X"] SG2["worker Y"] SG3["worker Z"] end P0 --> CG1 P1 --> CG2 P0 -. "msg theo lượt" .-> SG1 P0 -. "msg theo lượt" .-> SG2 P0 -. "msg theo lượt" .-> SG3 P1 -. "msg theo lượt" .-> SG1 P1 -. "msg theo lượt" .-> SG2 P1 -. "msg theo lượt" .-> SG3

Hình 4: Consumer Group phân partition, Share Group cho phép nhiều consumer cạnh tranh trên cùng partition

API client đơn giản:

var consumer = new ShareConsumerBuilder<string, string>(config)
    .Build();
consumer.Subscribe("jobs.email");
while (!ct.IsCancellationRequested)
{
    var record = consumer.Poll(TimeSpan.FromSeconds(1));
    try
    {
        await emailService.SendAsync(record.Value);
        consumer.Acknowledge(record);   // commit per message
    }
    catch (TransientException)
    {
        consumer.Release(record);       // redeliver
    }
    catch (PoisonException)
    {
        consumer.Reject(record);        // dead-letter
    }
}

Hệ quả kiến trúc: nhiều use case trước đây phải dựng RabbitMQ song song giờ có thể consolidate vào Kafka. Đặc biệt phù hợp cho job queue nơi mỗi job có thời gian xử lý không đồng đều, autoscale theo backlog, không cần ordering trong cùng partition.

7. Migration thực tế từ ZooKeeper-mode 3.x sang KRaft 4.0

Đây là phần khiến nhiều team chần chừ nhất. Apache Kafka cung cấp một quy trình migration online không downtime, nhưng cần thực hiện đúng thứ tự. Bốn pha thực tế:

Pha 1 — Pre-flight
Audit toàn bộ client. Mọi client phải ở phiên bản hỗ trợ metadata.version tối thiểu 3.3 (KIP-866). Disable mọi feature đã deprecated (ACL stored on disk legacy, log.message.format.version cũ). Backup ZooKeeper data và snapshot Kafka log. Bench throughput baseline để so sánh sau migration.
Pha 2 — Provision controller quorum mới
Cài 3 node controller-only mới chạy Kafka 4.0 với process.roles=controllerzookeeper.metadata.migration.enable=true. Chúng kết nối vào ZK hiện tại với role migration controller. Lúc này ZK vẫn là source of truth, controller mới chỉ đang sync metadata vào KRaft log riêng để chuẩn bị take-over.
Pha 3 — Take-over và rolling broker
Khi metadata đã sync xong (theo dõi qua metric MigrationStateChange), chỉ định controller mới làm active. Sau đó rolling restart từng broker với zookeeper.metadata.migration.enable=true và trỏ controller.quorum.voters sang quorum mới. Mỗi broker khi restart sẽ chuyển sang đọc metadata từ KRaft thay vì ZK. Trong giai đoạn này cluster ở dual-mode: hoạt động bình thường, có thể rollback bằng cách disable migration flag và quay lại trỏ ZK.
Pha 4 — Cắt đứt ZK
Khi mọi broker đã chạy ổn 1–2 tuần ở dual-mode, gỡ flag migration, đổi config sang KRaft thuần. Lúc này không thể rollback. Tắt cluster ZooKeeper (giữ snapshot 30 ngày phòng audit). Cập nhật mọi tooling (admin script, CI) sang dùng --bootstrap-server thay vì --zookeeper. Migration hoàn tất.

Sai lầm phổ biến khi migrate

(1) Quên audit client cũ: một service Spring Boot 2.x dùng kafka-clients 2.4 sẽ không nói chuyện được với cluster KRaft sau khi metadata.version được upgrade — và bạn chỉ phát hiện khi service đó restart vì lý do khác. (2) Đặt controller quorum chung node với broker cũ: combined mode trong production cluster lớn gây metadata commit lag. (3) Bỏ qua snapshot ZK trước Pha 4: khi có sự cố audit pháp lý 6 tháng sau, không có cách reconstruct ACL cũ.

8. Operational patterns 2026: monitoring, sizing, partition strategy

8.1. Metric tối thiểu cần theo dõi

Sau khi chuyển sang KRaft, danh sách metric quan trọng thay đổi. Ngoài bộ cũ (UnderReplicatedPartitions, RequestQueueSize, NetworkProcessorAvgIdlePercent), thêm:

  • kafka.controller:type=KafkaController,name=ActiveControllerCount — phải bằng 1 trên đúng 1 node trong quorum.
  • kafka.server:type=BrokerTopicMetrics,name=RemoteFetchRequestsPerSec — tỉ lệ request đi vào tier remote, dùng để judge cost S3.
  • kafka.controller:type=KafkaController,name=MetadataSnapshotLagBytes — controller follower trễ bao nhiêu so với leader; lag tăng dần là dấu hiệu disk/network controller node có vấn đề.
  • kafka.share:type=ShareGroupCoordinatorMetrics,name=ShareGroupAcknowledgementLatency — latency ack trong share group, dấu hiệu hot partition cần resize.

8.2. Sizing cluster KRaft

WorkloadController nodeBroker nodePartition / cluster
Small (≤50MB/s)3 × (4 vCPU, 8GB, 50GB SSD)3 × (8 vCPU, 32GB, 1TB)≤ 5.000
Medium (≤500MB/s)3 × (8 vCPU, 16GB, 100GB SSD)6 × (16 vCPU, 64GB, 4TB + Tiered)≤ 100.000
Large (≤5GB/s)5 × (16 vCPU, 32GB, 200GB SSD)20+ × (32 vCPU, 128GB, 4TB + Tiered)≤ 1.000.000
XL (multi-region, multi-tenant)5 × (32 vCPU, 64GB, 500GB SSD)50+ × (32 vCPU, 256GB, 4TB + Tiered)2.000.000+

8.3. Partition strategy hậu KRaft

Trước đây, "tránh nhiều partition" là khẩu hiệu, vì ZK rên la khi quá 200k. Với KRaft, trần thực tế nâng lên hàng triệu, nhưng vẫn không nên tạo bừa. Một heuristic ổn định cho 2026:

  • Số partition ≈ (peak throughput của topic / throughput tối đa một consumer xử lý). Ví dụ topic peak 100MB/s, consumer xử lý 5MB/s ⇒ 20 partition.
  • Tránh partition < 1MB/s — overhead metadata không bõ.
  • Thiết kế cho compaction: nếu topic là changelog dùng key compacted, key cardinality nên gấp 100× số partition để phân bố đều.
  • Multi-tenant: một topic per tenant thường tệ. Dùng shared topic + key = tenant_id khi tenant nhỏ; tách topic riêng chỉ cho tenant top 1% theo throughput.

9. So sánh với Pulsar, Redpanda, WarpStream

Năm 2026 không chỉ có Kafka. Vài alternative đáng cân nhắc tùy use case:

Tiêu chíKafka 4.0Apache Pulsar 3.xRedpanda 24.xWarpStream
Metadata layerKRaft (built-in)BookKeeper + ZK / etcdRaft built-inS3 only (zero local disk)
Ngôn ngữ brokerJava (JVM)Java (JVM)C++ (Seastar)Go
Tier storageCó (KIP-405)Có (built-in)Có (Tiered Storage)Native (chỉ có cold)
Geo-replicationMirrorMaker 2 / Kafka GeoBuilt-inCross-cluster + Iceberg sinkMulti-region native
Wire compatibility với Kafka clientNativeQua adaptor (KoP)100%100%
Phù hợp khiCần ecosystem rộng nhất, tự hostMulti-tenant nặng, geo built-inLatency cực thấp, ít nodeCloud-only, tối ưu chi phí

Kết luận thực tế: nếu đã có Kafka cluster cũ, 4.0 là path nâng cấp tự nhiên — không có alternative nào đáng để thay đổi toàn bộ stack chỉ vì hứa hẹn chi phí. Nếu đang green-field hoàn toàn cloud-native và muốn đẩy mọi thứ ra S3, WarpStream đáng thử. Redpanda hợp với edge/IoT cần latency p99 < 1ms.

10. Tích hợp với .NET 10 và Vue.js qua Confluent.Kafka 2.x

Stack production điển hình 2026: backend .NET 10 microservice produce/consume qua Confluent.Kafka 2.5+, frontend Vue 3.6 dashboard dùng SignalR / SSE để stream lag chart, admin tool gọi Kafka Admin API để autoscale partition theo lag.

10.1. Producer idempotent + transactional

public sealed class OrderEventPublisher
{
    private readonly IProducer<string, byte[]> _producer;

    public OrderEventPublisher(IConfiguration cfg)
    {
        var producerConfig = new ProducerConfig
        {
            BootstrapServers = cfg["Kafka:Bootstrap"],
            EnableIdempotence = true,
            Acks = Acks.All,
            CompressionType = CompressionType.Zstd,
            LingerMs = 5,
            BatchSize = 32 * 1024,
            TransactionalId = $"orders-{Environment.MachineName}",
        };
        _producer = new ProducerBuilder<string, byte[]>(producerConfig).Build();
        _producer.InitTransactions(TimeSpan.FromSeconds(10));
    }

    public async Task PublishAsync(OrderCreated evt, CancellationToken ct)
    {
        _producer.BeginTransaction();
        try
        {
            var msg = new Message<string, byte[]>
            {
                Key = evt.OrderId.ToString(),
                Value = ProtobufSerializer.Serialize(evt),
                Headers = new Headers
                {
                    { "trace-id", Encoding.UTF8.GetBytes(Activity.Current?.TraceId.ToString() ?? "") },
                    { "schema-version", Encoding.UTF8.GetBytes("v3") },
                }
            };
            await _producer.ProduceAsync("events.order.created", msg, ct);
            _producer.CommitTransaction();
        }
        catch
        {
            _producer.AbortTransaction();
            throw;
        }
    }
}

10.2. Worker dùng Share Group cho job queue

public sealed class EmailWorker : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        var cfg = new ShareConsumerConfig
        {
            BootstrapServers = "kafka:9092",
            GroupId = "email-workers",
            EnableAutoCommit = false,
        };
        using var consumer = new ShareConsumerBuilder<string, byte[]>(cfg).Build();
        consumer.Subscribe("jobs.email.send");

        while (!stoppingToken.IsCancellationRequested)
        {
            var rec = consumer.Consume(TimeSpan.FromSeconds(1));
            if (rec == null) continue;

            try
            {
                await _email.SendAsync(EmailJob.Parser.ParseFrom(rec.Message.Value));
                consumer.Acknowledge(rec);
            }
            catch (SmtpTransientException)
            {
                consumer.Release(rec); // try again
            }
            catch (SmtpInvalidAddressException)
            {
                consumer.Reject(rec);  // dead-letter
            }
        }
    }
}

10.3. Vue 3.6 dashboard hiển thị consumer lag realtime

Backend expose endpoint SSE bơm lag từ Kafka Admin API qua mỗi 2 giây; Vue component dùng EventSource để render chart bằng vue-echarts.

<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue'
const lag = ref([])
let es
onMounted(() => {
  es = new EventSource('/api/kafka/lag-stream?group=email-workers')
  es.onmessage = e => {
    const d = JSON.parse(e.data)
    lag.value = [...lag.value.slice(-59), { t: d.ts, v: d.lag }]
  }
})
onBeforeUnmount(() => es?.close())
</script>

11. Bốn anti-pattern Kafka 4.0 cần tránh

  • Combined controller+broker trên cluster lớn. Tiện cho dev, độc cho production. Tách role ngay từ ngày đầu.
  • Bật Tiered Storage cho mọi topic mặc định. Topic ngắn hạn không cần tier, chỉ tăng cost S3. Bật có chọn lọc theo tỉ lệ cold-read.
  • Lạm dụng Share Group thay cho Consumer Group. Share Group thêm overhead state tracking; chỉ dùng khi thực sự cần competing consumers (job queue), không dùng cho stream processing thông thường.
  • Transactional producer với linger.ms=0. Transaction commit có cost cố định; không batch sẽ giết throughput. Tối thiểu linger.ms=5batch.size=32KB.

12. Kết luận và lộ trình áp dụng

Kafka 4.0 không phải bản update tính năng mới — nó là bản đại tu kiến trúc giải quyết những giới hạn tích lũy 14 năm. KRaft cho phép cluster co lại, controller failover gần tức thời, vận hành chỉ một stack. KIP-848 xóa bỏ stop-the-world rebalance đã ám ảnh consumer group từ ngày đầu. Tiered Storage cắt 60–80% chi phí cho cluster log-heavy. Share Group mở khóa pattern queue mà nhiều team trước đây phải mua RabbitMQ để giải.

Lộ trình thực tế cho một team đang ở Kafka 3.5+ với ZK:

  1. Quý hiện tại: audit client, upgrade lên 3.7 với metadata.version=3.7-IV4; quan sát 30 ngày.
  2. Quý sau: provision controller quorum mới, bật migration mode, monitor metric MigrationStateChange.
  3. 1 quý sau: rolling broker sang KRaft mode dual; chạy dual-mode 2 tuần.
  4. Cắt đứt ZK, đổi tooling; bật Tiered Storage cho 1–2 topic lớn nhất; benchmark cost.
  5. Rollout Share Group cho job queue; gỡ bỏ RabbitMQ song song nếu use case overlap.

Năm 2026, Kafka không còn là "chỉ là message broker"; nó là một data plane phổ quát kết nối microservices, AI agents, analytics pipeline và job worker. Hiểu kiến trúc 4.0 không chỉ giúp vận hành tốt mà còn giúp thiết kế đúng từ đầu cho 5–7 năm tới.

13. Nguồn tham khảo