Có một bản năng tốn kém khi thiết kế hệ song song: "cứ thêm nhiều làn cho chắc". Nhưng số làn hữu ích không do mong muốn quyết định — nó do nút thắt thật và số người thật sự chạy song song quyết định. Vượt qua điểm đó, mỗi làn thêm vào không cho thêm một chút thông lượng nào, mà vẫn phải trả tiền bảo trì đều đặn. Partition của Kafka là ví dụ sắc nét, và tôi sẽ đo cả cái nó mua được lẫn cái nó tốn.

"Đặt nhiều partition cho chắc" là lời khuyên phổ biến và tốn kém. Bài này đo cả hai vế: partition mua được gì, và tốn gì.

Phía ghi gần như không quan tâm

500.000 tin × 300 byte, một broker, thay đổi số partition:

Partition Thông lượng
1 802.568 tin/giây
2 932.835
4 967.117
8 907.441
16 931.098
32 905.797
64 833.333
128 941.619

Một partition đã đạt 83% mức đỉnh. Từ 4 trở lên, chênh lệch chỉ là nhiễu — 64 partition còn thấp hơn 4 partition.

Điều này ngược với hình dung thường gặp rằng partition là "làn đường" và thêm làn thì thông xe hơn. Với một broker, nút thắt là đĩa và mạng của chính máy đó, không phải số làn.

Vậy partition dùng để làm gì?

Phía đọc — nhưng cũng bão hoà

Đọc 2 triệu tin từ topic 8 partition, thay đổi số consumer trong nhóm:

Consumer Thông lượng Hệ số
1 1.863.933 tin/giây 1,00×
2 2.353.449 1,26×
4 3.164.007 1,70×
8 3.099.374 1,66×

Bốn consumer cho 1,70 lần, không phải 4 lần. Tám consumer không thêm gì.

Lại là nút thắt máy: một broker phục vụ tất cả, và nó chỉ đọc được chừng đó từ bộ đệm trang.

Trên cụm thật, partition trải trên nhiều broker và mỗi broker phục vụ phần của nó — đó mới là chỗ khả năng mở rộng đến từ. Con số ở đây đo trần của một máy, không đo trần của Kafka.

Nhưng luật cứng vẫn giữ nguyên và đó mới là điều đáng nhớ: số partition là trần tuyệt đối của số consumer hoạt động trong một nhóm. Topic 4 partition thì consumer thứ 5 ngồi không, dù cụm có bao nhiêu broker.

Cái giá

Bộ nhớ broker Khởi động lại
~5 partition 290 MB 2,0 giây
414 partition 682 MB 3 giây
2.414 partition 973 MB 9 giây

Tạo một topic 2.000 partition rỗng hoàn toàn:

  • 24 MB đĩa (12 KB mỗi partition — tệp chỉ mục thưa)
  • 2.590 tệp đang mở trên tiến trình broker
  • Bộ nhớ từ 290 MB lên 973 MB — gấp 3,4 lần, chỉ vì có thêm partition rỗng

Mỗi partition là một thư mục, ít nhất ba tệp mở, một mục trong siêu dữ liệu cụm, và một dòng trong mọi báo cáo trạng thái. Broker giữ chỉ mục của partition đang mở trong bộ nhớ, và đó là phần lớn con số 973 MB.

Việc tạo và xoá thì nhanh: tạo 2.000 partition mất chưa tới 2 giây, gọi lệnh xoá trả về sau 862 ms. Nhưng xoá là bất đồng bộ — thư mục được đánh dấu rồi mới dọn sau file.delete.delay.ms (mặc định 60 giây). Ngay sau lệnh xoá, ls vẫn thấy đủ 2.414 thư mục. Đừng dựa vào việc đếm thư mục để xác nhận đã xoá xong.

Ba hệ quả khác của partition thừa

Bầu lại leader lâu hơn. Khi một broker chết, controller phải bầu leader mới cho từng partition nó giữ. Vài nghìn partition thì việc đó tính bằng giây; vài chục nghìn thì tính bằng phút, và trong khoảng đó những partition chưa bầu xong đều không phục vụ được.

Cân bằng lại nhóm nặng hơn. Danh sách partition phải truyền qua lại giữa mọi thành viên trong mỗi lần cân bằng.

Nén và dọn hết hạn chạy trên nhiều tệp hơn. Nhiều partition rỗng vẫn tốn công quét định kỳ.

Không giảm được

--alter --partitions chỉ đi lên. Kafka không có cách giảm số partition — vì giảm nghĩa là phải quyết định dữ liệu trong partition bị bỏ đi đâu, và không có câu trả lời đúng.

Muốn giảm thì phải tạo topic mới và chép dữ liệu sang, rồi đổi mọi producer và consumer. Đó là một dự án, không phải một lệnh.

Và tăng cũng không miễn phí: nó làm hỏng phân bố khoá của dữ liệu cũ, vì hash(key) % n đổi khi n đổi. Phần sau đo con số cụ thể.

Cách chọn

Công thức thực dụng:

số partition = số consumer cao nhất bạn nghĩ sẽ cần  ×  2

Nhân đôi để có chỗ tăng consumer mà không phải sửa topic. Không nhân 10.

Vài điểm neo từ phép đo trên:

  • Dưới 10 consumer: 8–16 partition. Thừa đủ.
  • Vài chục consumer: 32–64 partition.
  • Trên 100 partition cho một topic: cần lý do cụ thể, thường là một nhóm consumer thật sự lớn.
  • Tổng partition trên một broker: vài nghìn thì bình thường, và con số 973 MB ở trên cho thấy vì sao vài chục nghìn thì không.

Và nếu bạn dùng khoá để giữ thứ tự, còn một ràng buộc nữa quan trọng hơn tất cả những cái trên: số khoá phân biệt phải lớn hơn số partition rất nhiều, nếu không partition sẽ rỗng bất kể bạn đặt bao nhiêu. Phần 3 đã đo, và phần sau đo tiếp.

Cùng một điểm ngọt "song song vượt nút thắt là thuế đứng", đổi ngăn xếp

Chuyện partition thứ năm không thêm thông lượng nhưng vẫn tốn bộ nhớ không phải đặc sản Kafka. Nó là một luật chung về song song: số đơn vị hữu ích do nút thắt thật chặn, phần vượt qua chỉ tính chi phí.

Trong thế giới Java, đây đúng là cái bẫy tăng số luồng trong thread pool: quá số lõi CPU (hoặc quá năng lực của một nút thắt hạ nguồn, ví dụ connection pool tới CSDL) thì luồng thêm vào chỉ tốn stack và chi phí chuyển ngữ cảnh, không thêm một đơn vị việc nào — điểm ngọt do định luật Little và Amdahl đặt, không phải "nhiều là an toàn". Một connection pool đặt lớn hơn số truy vấn CSDL chạy song song nổi cũng vậy: kết nối thừa xếp hàng ở CSDL, tốn bộ nhớ, không nhanh thêm.

Trong thế giới Go, đặt GOMAXPROCS vượt số lõi, hay bung quá nhiều goroutine trên một tài nguyên có trần, cho đúng kết cục đó.

Mẫu số chung: mức song song có một điểm ngọt do nút thắt thật (hoặc lượng việc thật) quyết định — vượt qua nó, mỗi đơn vị thêm vào cho không thêm thông lượng nhưng vẫn gánh một chi phí cố định thường trực (bộ nhớ, tệp mở, chuyển ngữ cảnh, siêu dữ liệu), kể cả khi rỗng; nên bản năng "thêm cho chắc" là một khoản thuế đứng, và tệ hơn khi nó một chiều — với partition, tăng thì được mà giảm thì không. Nên câu hỏi đúng khi chọn số đơn vị song song không phải "đặt nhiều cho chắc" mà là "số này có vượt quá nút thắt thật hoặc số người tiêu thụ thật của tôi không — vì phần vượt chỉ tính chi phí, và nếu con số này một chiều thì đặt thừa là thừa mãi". Công thức "số consumer cao nhất × 2" ở trên chính là cách chốt điểm ngọt đó có chừa dư địa mà không phóng tay.

Muốn đếm partition trên broker của bạn và đối chiếu với bộ nhớ, chạy thử:

kafka-topics.sh --bootstrap-server kf:9092 --describe \
  | grep -c "^	Topic:"

docker stats --no-stream kf

Rồi xem có bao nhiêu partition thật sự đang được đọc:

kafka-consumer-groups.sh --bootstrap-server kf:9092 --all-groups --describe 2>/dev/null \
  | awk 'NR>1 && $2!="" {print $2"-"$3}' | sort -u | wc -l

Chênh lệch giữa hai con số là số partition bạn đang trả tiền mà không dùng.

Phần sau đo khoá và thứ tự: cái gì Kafka bảo đảm, cái gì không, và 45% khoá đổi chỗ khi bạn tăng partition.