Ba khái niệm này thường được vẽ bằng hình hộp và mũi tên. Bài này mở thư mục dữ liệu ra xem, và tìm được một cái bẫy về key mà hình vẽ không bao giờ cho thấy.

Thư mục partition, phân bố theo key, và cách segment cắt khúc

Topic là thư mục

Tạo một topic 4 partition rồi nhìn vào đĩa:

/tmp/kafka-logs/t4-0/    t4-1/    t4-2/    t4-3/

Mỗi partition một thư mục. Bên trong, khi chưa có tin nào:

00000000000000000000.log            0 byte
00000000000000000000.index     10.485.760 byte
00000000000000000000.timeindex 10.485.756 byte
leader-epoch-checkpoint             8 byte
partition.metadata                 43 byte

Topic rỗng mà đã chiếm 20 MB tệp chỉ mục mỗi partition. Chúng được cấp phát trước và ánh xạ bộ nhớ; phần thân là lỗ thưa nên du báo 12 KB. Lát nữa sẽ thấy vì sao con số này không đáng lo.

Ghi 100.000 tin 200 byte vào, .log lên 5.207.886 byte cho một partition. Dữ liệu thật là 100.000 × 200 ÷ 4 = 5.000.000 byte, phần dôi ra khoảng 4% là phần đầu mỗi bản ghi và phần đầu mỗi lô.

Partition quyết định thứ tự — và chỉ trong phạm vi của nó

Kafka không có thứ tự toàn cục. Nó chỉ hứa: các tin cùng vào một partition thì ra theo đúng thứ tự vào.

Không có key thì producer rải đều. 100.000 tin vào 4 partition:

p0 24.804    p1 23.872    p2 28.080    p3 23.244

Không chia chằn chặn 25.000 vì producer dùng lối "gộp dính": nó dồn đầy một lô cho một partition rồi mới chuyển. Đủ đều để không thành vấn đề.

Cái bẫy: bốn key vào bốn partition

Muốn giữ thứ tự cho từng khách hàng thì gửi kèm key. Cùng key luôn vào cùng partition. Tôi thử 20.000 tin với 4 key K0 K1 K2 K3 vào topic 4 partition, chờ mỗi partition một key:

p0  5.000     p1  0     p2  0     p3  15.000

Hai partition rỗng hoàn toàn, một partition ôm ba phần tư.

Kafka chọn partition bằng murmur2(key) % số_partition. Bốn giá trị băm có thể chia dư ra cùng một số, và ở đây đúng là như vậy: K1, K2, K3 cùng rơi vào p3.

Phản xạ đầu tiên là thêm partition. Cùng 4 key đó vào topic 8 partition:

p0 5.000   p1 0   p2 0   p3 5.000   p4 0   p5 0   p6 0   p7 10.000

Năm partition rỗng, và một partition vẫn gấp đôi. Thêm partition không chữa được — nó chỉ đổi cách đụng.

Cái chữa được là số key phải lớn hơn số partition rất nhiều. Cùng phép đo, 1.000 key vào 4 partition:

p0 4.640   p1 4.880   p2 5.160   p3 5.320

Lệch cao nhất 7%. Đủ đều.

Bài học thực dụng: nếu key của bạn là region với 5 giá trị, hay tenant với 12 giá trị, đừng mong tải rải đều — dù bạn đặt bao nhiêu partition. Một consumer sẽ ngồi không trong khi consumer khác quá tải, và đồ thị độ trễ sẽ cho thấy điều đó chứ không ai báo lỗi cả.

Offset và segment

Offset là số thứ tự trong một partition, bắt đầu từ 0, tăng đơn điệu, không bao giờ dùng lại. Nó không phải id toàn cục: offset 4998 của partition 0 và offset 4998 của partition 3 là hai tin khác nhau.

Đọc thẳng từ một offset:

kafka-console-consumer.sh --bootstrap-server kf:9092 --topic tk \
  --partition 0 --offset 4998 --max-messages 2 \
  --property print.offset=true --property print.key=true
Offset:4998   K0   tin-19996
Offset:4999   K0   tin-20000

Trên đĩa, partition không phải một tệp khổng lồ mà là nhiều segment. Đặt segment.bytes=1MB rồi ghi 30.000 tin:

00000000000000000000.log   1.048.128     .index        504 byte
00000000000000004992.log   1.048.128     .index        504 byte
00000000000000009984.log   1.048.128     .index        504 byte
00000000000000014976.log   1.048.128     .index        504 byte
00000000000000019968.log   1.048.128     .index        504 byte
00000000000000024960.log   1.048.128     .index        504 byte
00000000000000029952.log      10.093     .index 10.485.760 byte   <- đang mở

Tên tệp chính là offset đầu tiên trong khúc đó. Muốn đọc từ offset 20.100, Kafka tìm nhị phân trên danh sách tên để chọn tệp 19968, rồi tra .index để nhảy đúng vị trí byte. Không quét tuần tự từ đầu.

Và đây là câu trả lời cho 20 MB chỉ mục ở đầu bài: chỉ khúc đang mở mới giữ kích thước cấp phát trước. Khi khúc đóng lại, Kafka cắt chỉ mục xuống đúng cỡ thật — 504 byte. Sáu khúc đã đóng cộng lại chưa tới 4 KB.

Segment còn là đơn vị xoá. Kafka không xoá từng tin; hết hạn lưu trữ thì nó xoá cả tệp segment. Đó là lý do retention.ms không bao giờ chính xác đến từng tin, và là lý do segment.bytes quá lớn khiến dữ liệu cũ nằm lâu hơn bạn nghĩ.

Đặt bao nhiêu partition

Ba ràng buộc, theo thứ tự quan trọng:

  1. Số partition là trần của song song đọc. Một partition chỉ được một consumer trong nhóm đọc. 4 partition thì consumer thứ 5 ngồi không.
  2. Tăng được, giảm không được. --alter --partitions chỉ đi lên. Và tăng partition làm hỏng phân bố key cũ: % n đổi thì tin cùng key sẽ sang partition khác, thứ tự trong quá khứ không còn ý nghĩa.
  3. Mỗi partition tốn tệp và bộ nhớ. Vài nghìn partition trên một broker là bình thường; vài chục nghìn thì thời gian khởi động và thời gian bầu lại leader tăng rõ.

Khởi điểm thực dụng: đặt gấp 2–3 lần số consumer bạn nghĩ sẽ cần lúc cao điểm. Thà thừa một chút còn hơn phải tăng sau.

Thử ba mươi giây

Tự kiểm cái bẫy key trên máy bạn:

kafka-topics.sh --bootstrap-server kf:9092 --create --topic tk --partitions 4 --replication-factor 1

for i in $(seq 1 20000); do echo "K$((i % 4)):tin-$i"; done | \
  kafka-console-producer.sh --bootstrap-server kf:9092 --topic tk \
  --property parse.key=true --property key.separator=:

kafka-get-offsets.sh --bootstrap-server kf:9092 --topic tk

Nếu có partition nào bằng 0, bạn vừa nhìn thấy lý do tại sao "chia đều theo key" là một giả định chứ không phải một bảo đảm.

Phần sau đo acks — và cho thấy acks=0 không nhanh hơn acks=1 chút nào.