✍️ Khoa📅 19/04/2026☕ 17 phút đọc

Kafka Internals & Operations — Hiểu Sâu Để Vận Hành Đúng

Kafka không chỉ là "message queue nhanh". Đằng sau performance của nó là một loạt quyết định thiết kế cực kỳ thú vị: log-structured storage, zero-copy I/O, partition-level ordering, và consumer group protocol. Hiểu những thứ này giúp bạn không chỉ dùng Kafka mà còn debug được khi Kafka misbehavetune đúng khi cần throughput cao.


1. Log-Structured Storage — Tại Sao Kafka Nhanh

Sequential I/O là chìa khóa

Hầu hết message broker truyền thống (ActiveMQ, RabbitMQ với persistence) dùng random I/O: mỗi message được ghi vào một vị trí ngẫu nhiên trên disk, giống như bạn vừa ghi chỗ này vừa xóa chỗ kia trong một cuốn sổ. HDD chịu không nổi cái này (seek time), SSD thì đỡ hơn nhưng vẫn tốn.

Kafka làm ngược lại hoàn toàn: append-only log. Mọi message đều được ghi tuần tự vào cuối file. Disk I/O pattern này gần như tương đương với memory I/O về throughput — HDD sequential write có thể đạt 100-200 MB/s, random write chỉ 1-2 MB/s. Đây là lý do chính tại sao Kafka có thể handle millions of messages/second trên hardware thông thường.

Partition Log (append-only):
+----------+----------+----------+----------+
| offset 0 | offset 1 | offset 2 | offset 3 | ---> append tiếp
+----------+----------+----------+----------+
     ^           ^
  oldest       newest
 (có thể    (active
 đã delete)  segment)

Mỗi partition là một directory chứa các segment files:

/kafka-data/my-topic-0/
  00000000000000000000.log       # segment 1
  00000000000000000000.index     # offset -> file position index
  00000000000000000000.timeindex # timestamp -> offset index
  00000000000000012345.log       # segment 2 (active)
  00000000000000012345.index

Zero-Copy với sendfile()

Khi consumer request data, thay vì copy data từ disk → kernel buffer → user space → socket buffer, Kafka dùng Linux sendfile() syscall để copy thẳng từ page cache → socket buffer mà không đi qua user space:

Traditional path (4 copies):
Disk --> Kernel Page Cache --> User Space Buffer --> Kernel Socket Buffer --> NIC

Zero-copy path (2 copies):
Disk --> Kernel Page Cache --> Kernel Socket Buffer --> NIC
                    (sendfile() skips user space)

Đây gọi là zero-copy — không phải zero copy thật sự (vẫn có 2 copies) mà là zero copy qua user space. Kết quả: CPU overhead giảm drastically, throughput tăng 2-4x so với truyền thống.

Page Cache — Đừng Chiếm Hết RAM Cho JVM

Kafka tận dụng OS page cache thay vì tự quản lý memory cache. Khi bạn đọc một segment file, OS cache nó trong page cache. Consumer đọc lần sau không cần xuống disk.

Hệ quả quan trọng cho ops:

  • Kafka broker JVM heap nên để nhỏ (6-8 GB max), phần còn lại để OS dùng làm page cache
  • Producer mới thường đọc lại ngay message vừa ghi → hit page cache 100% → latency cực thấp
  • Consumer đọc historical data (lag cao) → page cache miss → I/O spike

Nếu bạn thấy consumer lag đột nhiên tăng cao sau khi consumer restart, đó thường là do page cache đã bị evict và consumer phải đọc từ disk lạnh.


2. Partition & Replication Internals

Leader Election Per Partition

Đây là điểm nhiều người nhầm: Kafka không bầu leader per broker, mà per partition. Một broker có thể là leader cho partition 0 của topic A, nhưng là follower cho partition 1 của cùng topic đó.

Broker 1          Broker 2          Broker 3
+-----------+     +-----------+     +-----------+
| topic-A   |     | topic-A   |     | topic-A   |
| part-0 [L]|     | part-0 [F]|     | part-0 [F]|
| part-1 [F]|     | part-1 [L]|     | part-1 [F]|
| part-2 [F]|     | part-2 [F]|     | part-2 [L]|
+-----------+     +-----------+     +-----------+

[L] = Leader, [F] = Follower

Benefit: load phân bổ đều giữa các brokers. Tất cả producer/consumer đều giao tiếp với leader của từng partition, không phải một single leader cho cả topic.

ISR — In-Sync Replicas

ISR là tập hợp các replicas đang theo kịp leader (không lag quá replica.lag.time.max.ms, mặc định 30 giây).

Khi nào replica bị kick khỏi ISR:

  • Follower không fetch từ leader trong replica.lag.time.max.ms ms
  • Follower fetch nhưng lag quá nhiều message (trước đây có replica.lag.max.messages, giờ đã removed)
  • Follower bị network partition hoặc crash
Normal state (ISR = {0,1,2}):
Leader (0) ----replicate---> Follower (1)
           ----replicate---> Follower (2)
ISR: [0, 1, 2]

Follower 2 bị slow:
Leader (0) ----replicate---> Follower (1)   [in ISR]
           ----replicate---> Follower (2)   [lagging...]
After lag.time.max.ms:
ISR: [0, 1]  <-- Follower 2 kicked out

Hậu quả khi replica bị kick khỏi ISR:

  • Nếu broker chứa leader chết, chỉ các replicas trong ISR mới được bầu làm leader mới
  • Nếu ISR chỉ còn 1 (leader), và leader chết → nếu unclean.leader.election.enable=false thì partition unavailable cho đến khi broker cũ recover

acks — Trade-off Thực Tế

acks=0: Fire and forget
Producer ------> Broker (ghi xuống buffer, không confirm)
         <------ Không có response
Throughput: MAX | Durability: NONE | Use case: metrics, logs không quan trọng

acks=1: Leader acknowledge
Producer ------> Leader (ghi vào leader log)
         <------ ACK (không đợi followers)
Throughput: HIGH | Durability: MEDIUM | Risk: leader chết trước khi replicate
Use case: non-critical events

acks=all (hoặc acks=-1): All ISR acknowledge
Producer ------> Leader ------> Follower 1
                        ------> Follower 2
         <------ ACK (sau khi tất cả ISR confirm)
Throughput: LOWER | Durability: HIGH | Use case: payment, orders

Config thực tế cho critical data:

# Producer config
acks=all
retries=2147483647          # Retry "forever" (Integer.MAX_VALUE)
max.in.flight.requests.per.connection=5   # Với idempotent=true, an toàn tăng lên 5
enable.idempotence=true

min.insync.replicas — Tại Sao Cần Set

min.insync.replicas là số ISR replicas tối thiểu phải có để producer với acks=all được phép ghi. Nếu ISR ít hơn số này, producer nhận NotEnoughReplicasException.

Công thức: ISR_count >= min.insync.replicas để ghi được, và replication_factor >= min.insync.replicas + 1 để chịu được ít nhất 1 broker failure.

Ví dụ với replication.factor=3:

min.insync.replicas=2:
- Normal (ISR=3): OK, chịu được 1 broker failure
- 1 broker down (ISR=2): OK, vẫn đủ ISR
- 2 broker down (ISR=1): FAIL, producer nhận exception
--> Tolerate: 1 failure

min.insync.replicas=1 (default):
- Ngay cả khi chỉ còn leader trong ISR, vẫn ghi được
- Durability thực sự = acks=1 (chỉ leader có data)
--> Đừng dùng với acks=all nếu bạn quan tâm đến durability

Topic-level config override:

# Khi tạo topic hoặc alter topic config
min.insync.replicas=2

3. Log Compaction vs Log Retention

Kafka có hai cơ chế xử lý data cũ:

Retention by Time/Size (Delete policy)

# Retention theo thời gian (mặc định 7 ngày)
log.retention.hours=168
log.retention.ms=604800000

# Retention theo size (per partition)
log.retention.bytes=107374182400   # 100 GB per partition

# Kích thước mỗi segment file
log.segment.bytes=1073741824       # 1 GB

Khi segment đủ cũ hoặc partition vượt size limit, Kafka xóa toàn bộ segment (không xóa từng message). Đây là lý do bạn không bao giờ nên set log.retention.bytes quá nhỏ trên partition có segment lớn.

Log Compaction — Kafka Như Một Key-Value Store

Log compaction giữ lại message mới nhất cho mỗi key, xóa các message cũ hơn có cùng key. Use cases:

  • Changelog topics (Kafka Streams internal state): giữ latest state per key
  • CDC (Change Data Capture): latest row state per primary key
  • __consumer_offsets topic: Kafka tự dùng compaction ở đây
Before compaction (cleanup.policy=compact):
offset: 0    key=user-1  value={"name":"Alice"}
offset: 1    key=user-2  value={"name":"Bob"}
offset: 2    key=user-1  value={"name":"Alice Smith"}   <-- cùng key
offset: 3    key=user-3  value={"name":"Charlie"}
offset: 4    key=user-2  value={"name":"Robert"}        <-- cùng key

After compaction:
offset: 2    key=user-1  value={"name":"Alice Smith"}   <-- latest
offset: 3    key=user-3  value={"name":"Charlie"}
offset: 4    key=user-2  value={"name":"Robert"}        <-- latest

Tombstone message: gửi message với key nhưng value=null để "xóa" key khỏi compacted log. Kafka giữ tombstone trong delete.retention.ms trước khi xóa hẳn, để consumer kịp nhận "deletion event".

Compaction Gotchas — Không Phải Real-Time

Log compaction không xảy ra ngay lập tức. Cleaner thread chạy background:

log.cleaner.min.cleanable.ratio=0.5   # Compact khi 50% log là "dirty" (có duplicate keys)
log.cleaner.min.compaction.lag.ms=0   # Thời gian tối thiểu message tồn tại trước khi compact
log.cleaner.max.compaction.lag.ms=    # Default: không giới hạn (có thể lag dài)

Implication quan trọng: Nếu bạn dùng compacted topic như một key-value store và cần đọc "latest state" ngay sau khi write, bạn có thể đọc phải stale data nếu cleaner chưa chạy. Đây là gotcha kinh điển trong Kafka Streams.

Kết hợp cả hai:

# Compaction + Delete (giữ latest mỗi key, nhưng xóa sau 7 ngày)
cleanup.policy=compact,delete
log.retention.ms=604800000

4. Consumer Group Protocol

Consumer Group Rebalance — Khi Nào Trigger

Rebalance là quá trình Kafka phân phối lại partitions giữa các consumers trong một group. Trigger khi:

  1. Consumer mới join group
  2. Consumer rời group (crash, shutdown, hoặc quá 30s không poll)
  3. Topic thêm partition mới
  4. Consumer subscribe thêm topic mới
Before rebalance (3 consumers, 6 partitions):
Consumer-A: [part-0, part-1]
Consumer-B: [part-2, part-3]
Consumer-C: [part-4, part-5]

Consumer-C crashes → Rebalance:

STOP-THE-WORLD (Eager rebalance):
1. Tất cả consumers revoke tất cả partitions
2. Re-join group
3. Phân phối lại:
   Consumer-A: [part-0, part-1, part-4]
   Consumer-B: [part-2, part-3, part-5]

Trong thời gian này: không consumer nào xử lý → LAG SPIKE

Trong production, lag spike trong rebalance có thể kéo dài 30-60 giây nếu không tune đúng.

Eager vs Cooperative Rebalance

Eager (default trước Kafka 2.4): Stop-the-world. Tất cả consumers drop partitions, re-join, nhận lại partitions mới. Gây downtime.

Cooperative Incremental (từ Kafka 2.4+): Chỉ revoke các partitions cần di chuyển, không phải tất cả. Consumers tiếp tục xử lý partitions không bị ảnh hưởng.

// Enable Cooperative Rebalance
props.put(ConsumerConfig.PARTITION_ASSIGNMENT_STRATEGY_CONFIG,
    CooperativeStickyAssignor.class.getName());
Cooperative rebalance khi Consumer-C crash:
Round 1: Broker thông báo partitions [part-4, part-5] cần di chuyển
         Consumer-A và B revoke ONLY part-4, part-5
         Consumer-A, B vẫn xử lý [part-0,1] và [part-2,3]
Round 2: Phân phối part-4 và part-5 lại
         Consumer-A: [part-0, part-1, part-4]
         Consumer-B: [part-2, part-3, part-5]

max.poll.interval.ms vs session.timeout.ms

Đây là hai config hay bị nhầm lẫn nhất:

session.timeout.ms (default: 45000ms):
- Broker-side timeout
- Nếu broker không nhận heartbeat từ consumer trong khoảng thời gian này
  → Broker coi consumer chết → trigger rebalance
- Heartbeat gửi bởi background thread riêng (không phải poll thread)
- Set quá thấp → spurious rebalances khi consumer bận xử lý
- Set quá cao → chậm phát hiện consumer thật sự chết

max.poll.interval.ms (default: 300000ms = 5 phút):
- Client-side timeout
- Nếu khoảng cách giữa 2 lần poll() vượt quá giá trị này
  → Consumer tự rời group → trigger rebalance
- Dùng để detect consumer bị stuck trong processing loop
- Tăng nếu message processing time dài (batch jobs, external API calls)
Timeline:
t=0: Consumer poll() → nhận 100 messages
t=0-t=50s: Consumer xử lý (slow processing)
t=45s: session.timeout expires → nhưng heartbeat thread vẫn chạy → KHÔNG rebalance
t=300s: max.poll.interval expires → consumer bị coi là dead → rebalance!

Tuning cho slow processors:

# Consumer config
max.poll.interval.ms=600000        # 10 phút nếu processing thực sự chậm
max.poll.records=50                # Reduce batch size để xử lý nhanh hơn
session.timeout.ms=30000
heartbeat.interval.ms=10000        # Phải < session.timeout.ms / 3

Consumer Lag Monitoring

Consumer Lag = Latest Offset (log-end-offset) - Committed Offset (consumer position)

Topic: orders, Partition 0:
Log end offset:      1000
Consumer committed:  950
Lag:                 50 messages

Tool monitoring:

# kafka-consumer-groups.sh
kafka-consumer-groups.sh --bootstrap-server localhost:9092 \
  --group my-consumer-group --describe

# Output:
# GROUP           TOPIC     PARTITION  CURRENT-OFFSET  LOG-END-OFFSET  LAG
# my-group        orders    0          950             1000            50
# my-group        orders    1          1200            1200            0

Prometheus metrics (JMX hoặc Kafka exporter):

# kafka_consumer_group_lag{group="my-group", topic="orders", partition="0"} 50

Alert rule thực tế:

# Prometheus AlertManager rule
- alert: KafkaConsumerLagHigh
  expr: kafka_consumer_group_lag > 10000
  for: 5m
  labels:
    severity: warning
  annotations:
    summary: "Consumer group {{ $labels.group }} lag {{ $value }} on {{ $labels.topic }}"

Burrow (LinkedIn's open-source lag evaluator) tốt hơn threshold-based lag alerting vì nó evaluate lag trend (đang tăng hay đang giảm) thay vì chỉ compare với threshold tĩnh.


5. Producer Tuning Thực Chiến

batch.size và linger.ms — Throughput vs Latency

Producer batch messages trước khi gửi. Hai config điều khiển batching:

batch.size=65536           # Max bytes per batch (default 16KB, tăng lên 64KB hoặc 128KB cho throughput)
linger.ms=10               # Đợi tối đa 10ms để fill batch trước khi gửi
linger.ms=0 (default):
Producer → gửi ngay khi có message → nhiều request nhỏ → throughput thấp, latency thấp

linger.ms=10:
t=0ms: message 1 vào buffer
t=3ms: message 2 vào buffer
t=7ms: message 3 vào buffer
t=10ms: batch full (hoặc timeout) → gửi cùng 1 request → throughput cao hơn

Trade-off:

  • linger.ms=0: tốt cho latency-sensitive (payment processing, real-time recommendations)
  • linger.ms=5-20: tốt cho throughput (event streaming, logs, metrics)
  • batch.size lớn hơn giảm overhead per request, nhưng tốn memory buffer

Compression

compression.type=lz4      # Recommended cho most cases
# Options: none, gzip, snappy, lz4, zstd

So sánh thực tế:

none:    Baseline. Không tốn CPU, dùng nhiều bandwidth + disk.
gzip:    Compress tốt nhất (~70-80%), CPU cao, latency cao.
         Dùng khi bandwidth là bottleneck (cross-DC replication).
snappy:  Compress tốt (~50-60%), CPU thấp, được Google thiết kế cho speed.
lz4:     Compress vừa (~50%), CPU cực thấp, speed cao nhất.
         --> KHUYẾN NGHỊ cho hầu hết use cases
zstd:    Compress tốt (~70%), CPU vừa, Kafka 2.1+.
         --> Tốt khi muốn balance compression ratio và speed

Compression xảy ra ở producer, decompress ở consumer. Broker lưu data đã compressed (không decompress). Một gotcha: nếu broker cần reassign batches (acks=all, recompressing), có thể ảnh hưởng CPU.

Idempotent Producer — Exactly-Once At Producer Level

Without idempotence:

Producer → Broker (message sent, network timeout)
Producer → Broker (retry) → Broker nhận duplicate!

Với enable.idempotence=true:

  • Mỗi producer có Producer ID (PID) unique, assigned bởi broker
  • Mỗi message có Sequence Number tăng dần per partition
  • Broker deduplicate dựa trên (PID, partition, sequence number)
  • Đảm bảo exactly-once delivery within a producer session
enable.idempotence=true
acks=all                              # Bắt buộc với idempotence
retries=2147483647
max.in.flight.requests.per.connection=5  # Tối đa 5 in-flight, an toàn với idempotence

Lưu ý: Idempotence chỉ bảo vệ trong một producer session. Nếu producer restart, PID mới được assign. Cho cross-session exactly-once, cần Kafka Transactions.


6. KRaft vs ZooKeeper

ZooKeeper là Bottleneck Như Thế Nào

ZooKeeper lưu cluster metadata (broker registration, topic configs, partition assignments, ISR lists). Mọi thay đổi metadata đều phải qua ZooKeeper:

Với ZooKeeper (pre-KRaft):
                    +-------------+
                    |  ZooKeeper  |  <-- single point of coordination
                    |  Ensemble   |
                    +-------------+
                   /      |       \
           Broker1      Broker2    Broker3
           (Controller)

Vấn đề:

  • Metadata scalability: ZooKeeper không thể handle cluster với hàng triệu partitions efficiently (Kafka 2.x bottleneck ở ~200k partitions)
  • Controller failover chậm: Controller mới phải load toàn bộ metadata từ ZooKeeper → có thể mất 30-60 giây
  • Operational complexity: 2 distributed systems để vận hành (Kafka + ZooKeeper)
  • Split-brain risk: ZooKeeper partition có thể gây inconsistency

KRaft (Kafka Raft) — GA từ Kafka 3.3

KRaft loại bỏ ZooKeeper hoàn toàn. Metadata được lưu trong một internal Kafka topic (__cluster_metadata), quản lý bởi Quorum Controller dùng Raft consensus:

KRaft architecture:
+----------+  +----------+  +----------+
| Broker1  |  | Broker2  |  | Broker3  |
| (Ctrlr)  |  | (Ctrlr)  |  |          |
+----------+  +----------+  +----------+
    |               |              |
    +----Raft Consensus Quorum------+
         (cho __cluster_metadata)

Benefits:

  • Controller failover < 1 giây (metadata đã replicated via Raft)
  • Support hàng triệu partitions (tested với 10 triệu partitions)
  • Chỉ cần vận hành 1 distributed system

Migration từ ZK → KRaft:

# Kafka 3.x: Migration mode available
# 1. Upgrade brokers lên Kafka 3.x
# 2. Chạy migration tool để copy metadata từ ZK sang KRaft
# 3. Enable KRaft mode trên controllers
# 4. Remove ZooKeeper

# Với deployment mới (Kafka 3.3+): dùng KRaft từ đầu
process.roles=broker,controller  # Hoặc tách riêng broker và controller nodes
node.id=1
controller.quorum.voters=1@controller1:9093,2@controller2:9093,3@controller3:9093

Kết luận thực tế: Nếu bạn deploy Kafka mới từ 2024 trở đi, dùng KRaft. Không cần học ZooKeeper internals nữa — nó đang được deprecate.


7. Operational Concerns

JVM Heap Sizing

Broker memory = JVM Heap + OS Page Cache + OS overhead

Rule of thumb:
- JVM Heap: 6-8 GB (đủ cho GC, metadata, connections)
- Phần còn lại: Page Cache (càng nhiều càng tốt)

Ví dụ server 64 GB RAM:
- JVM Heap: 6 GB (-Xmx6g -Xms6g)
- Page Cache available: ~56 GB
- GC: G1GC (mặc định Kafka), không dùng CMS

Đừng set JVM heap lớn hơn 8 GB:
- Larger heap → longer GC pauses → heartbeat miss → rebalance
- Page cache ít hơn → nhiều disk I/O hơn → throughput thấp
# Kafka start script
export KAFKA_HEAP_OPTS="-Xmx6g -Xms6g"
export KAFKA_JVM_PERFORMANCE_OPTS="-server -XX:+UseG1GC \
  -XX:MaxGCPauseMillis=20 -XX:InitiatingHeapOccupancyPercent=35"

Disk Throughput là Bottleneck Chính

Kafka throughput gần như hoàn toàn phụ thuộc vào disk:

  • Dùng multiple disks (JBOD — Just a Bunch Of Disks), mỗi disk handle một số partitions
  • Không dùng RAID (Kafka tự xử lý replication), RAID-5 đặc biệt tệ (write amplification)
  • NVMe SSD >> SATA SSD >> HDD cho production Kafka
# Nhiều disk paths cho JBOD
log.dirs=/data1/kafka,/data2/kafka,/data3/kafka

Kafka sẽ round-robin phân phối partitions giữa các disks. Mỗi disk độc lập → aggregate throughput tăng tuyến tính.

Partitioning Strategy — Bao Nhiêu Partitions Là Đủ?

Rule of thumb từ Confluent:

Partitions per topic = max(target throughput / consumer throughput,
                           target throughput / producer throughput)

Ví dụ:
- Target: 1 GB/s
- Consumer throughput: 50 MB/s per instance
- Partitions cần: 1000/50 = 20 partitions

Giới hạn thực tế:
- Mỗi partition = 1 open file handle trên broker (file descriptor)
- Mỗi partition = 1 thread trên producer (khi gửi)
- Rebalance time tỷ lệ thuận với số partitions
- Đừng over-partition: 1000 partitions/broker là reasonable max

Over-partitioning là anti-pattern phổ biến. Bắt đầu ít, tăng sau — Kafka hỗ trợ tăng partitions nhưng không giảm.

# Tăng partitions (không giảm được)
kafka-topics.sh --alter --topic my-topic \
  --partitions 20 \
  --bootstrap-server localhost:9092

Replication Flow — ASCII Diagram

Producer write flow với acks=all, ISR=[leader, f1, f2]:

Producer
   |
   | ProduceRequest (acks=all)
   v
Leader Broker
   |
   |-- append to local log
   |
   |-- sends to Follower 1 ------> Follower 1
   |                               |-- append to local log
   |                               |-- FetchResponse (ack)
   |                               v
   |<----- f1 acked --------------
   |
   |-- sends to Follower 2 ------> Follower 2
   |                               |-- append to local log
   |                               |-- FetchResponse (ack)
   |                               v
   |<----- f2 acked --------------
   |
   v
ProduceResponse (success) -------> Producer

Nếu f2 không ack trong replica.lag.time.max.ms:
→ f2 bị kick khỏi ISR
→ ISR = [leader, f1]
→ ProduceResponse thành công khi chỉ f1 ack (nếu min.insync.replicas=2)

Mental Model — Checklist Trước Khi Production

Durability checklist:

  • replication.factor >= 3 cho critical topics
  • min.insync.replicas = replication.factor - 1 (thường = 2)
  • acks=all ở producer cho critical data
  • enable.idempotence=true ở producer
  • unclean.leader.election.enable=false (default Kafka 3.x)

Performance checklist:

  • JVM heap <= 8 GB, phần còn lại cho page cache
  • Multiple disks (JBOD), không RAID
  • compression.type=lz4 hoặc zstd ở producer
  • Tune batch.sizelinger.ms theo use case (throughput vs latency)
  • max.poll.records phù hợp với processing time

Consumer checklist:

  • Dùng CooperativeStickyAssignor để tránh stop-the-world rebalance
  • max.poll.interval.ms đủ lớn cho processing time thực tế
  • Consumer lag monitoring với alerting
  • Idempotent consumer logic (xử lý at-least-once delivery)

Ops checklist:

  • KRaft cho deployment mới (không cần ZooKeeper)
  • Số partitions: đủ dùng, không over-partition
  • Log retention đặt phù hợp với storage capacity
  • Monitoring: broker disk usage, network I/O, ISR shrink events, unclean elections

Red flags cần điều tra ngay:

  • ISR shrink events tăng → network issue hoặc slow brokers
  • Consumer lag tăng liên tục → consumer bị stuck hoặc producer throughput tăng đột biến
  • Frequent rebalances → consumer crash loop hoặc max.poll.interval.ms quá thấp
  • Disk usage > 80% → tăng retention delete hoặc thêm disk

Tài Liệu Tham Khảo