Hệ thống Dữ liệu Phái sinh/Log-based broker — Kafka, offset, và log chỉ append
10/21
Bài 10 / 21~13 phútXử lý stream (stream processing)Miễn phí lượt xem

Log-based broker — Kafka, offset, và log chỉ append

Kafka lưu mỗi sự kiện vào log chỉ-append có offset cố định, không xoá sau khi đọc — nhiều consumer đọc độc lập ở tốc độ riêng. Khác broker truyền thống.

TL;DR: Message broker truyền thống (RabbitMQ/ActiveMQ) xoá message sau khi consumer đọc — một message chỉ đến tay đúng một nhóm. Kafka thì ngược: mỗi message được ghi vào log chỉ-append có địa chỉ cố định gọi là offset và giữ lại trong retention window (mặc định 168 giờ). Nhờ đó nhiều nhóm consumer hoàn toàn độc lập có thể đọc cùng topic ở tốc độ riêng. Điểm then chốt: offset là con trỏ của consumer group chứ không phải trạng thái broker — consumer tự giữ offset của mình, có thể tua lại (replay) bất cứ lúc nào trong window. Đây là lý do Kafka xử lý được 7 nghìn tỷ message mỗi ngày tại LinkedIn năm 2019.

Bạn đang xây hệ thống e-commerce và cần ba việc xảy ra mỗi khi có đơn hàng mới: (1) team analytics tổng hợp doanh thu theo giờ, (2) team billing ghi hoá đơn, (3) team audit giữ nhật ký bất biến cho bộ phận pháp lý. Cả ba cần đọc cùng một luồng sự kiện nhưng ở tốc độ và thời điểm khác nhau — audit đôi khi cần replay lại tuần trước để kiểm tra.

Với broker truyền thống, bạn phải dựng ba queue riêng và fan-out message vào cả ba — mỗi message bị sao chép ba lần, và khi audit cần replay tuần trước thì không thể: message đã bị xoá. Kafka giải quyết cả hai vấn đề bằng một thay đổi mang tính kiến trúc: thay queue (xoá sau đọc) bằng log (giữ lại).

Bài này giải thích cách log-based broker hoạt động, vì sao nó khác broker truyền thống khi nhiều team cần đọc cùng luồng sự kiện, và pitfall phổ biến khi mới dùng Kafka.

Bạn đã thấy cơ chế offset và append-only log của Kafka ở track Thuật toán ứng dụng. Bài này không dạy lại cơ chế đó — mà đặt nó trong ngữ cảnh so sánh với broker truyền thống, giải thích vì sao thiết kế "giữ log" thắng "xoá sau đọc" khi hệ thống có nhiều consumer group.

1. Analogy — thùng thư cá nhân vs bảng thông báo chung

Hình dung khu chung cư có hai cách thông báo cho cư dân.

Cách một — thùng thư cá nhân (broker truyền thống): ban quản lý in thông báo, bỏ vào từng thùng thư. Anh A lấy thư ra thì thùng trống — chị B không đọc được thư đó. Muốn cả hai nhận, phải in hai bản. Một khi đã lấy thư, bạn không thể lấy lại "bản cũ từ tuần trước".

Cách hai — bảng thông báo chung (log-based broker): ban quản lý ghim thông báo theo thứ tự lên bảng ở hành lang. Anh A đọc đến tờ số 5, chị B đọc đến số 3. Bảng không gỡ thông báo khi ai đó đọc — chúng ở đó theo thứ tự trong vòng 7 ngày. Mỗi người tự nhớ mình đọc đến đâu. Muốn đọc lại tờ số 2 từ hôm qua, chỉ cần quay lại số 2 trên bảng.

Thùng thư cá nhânBảng thông báo chungKhái niệm kỹ thuật
Mỗi hộ có thùng riêngMột bảng, mọi người đọc chungQueue riêng vs Shared log
Lấy thư rồi — hộ khác không đọc đượcTờ vẫn đó sau khi ai đó đọcXoá sau đọc vs Giữ lại
Muốn hai hộ nhận → in hai bảnHai người đọc cùng một tờFan-out copy vs Read-only offset
Không lấy lại thư cũQuay lại đọc từ tờ cũ đượcKhông replay vs Replay trong window
Thùng hết thư = rỗngBảng gỡ tờ cũ sau 7 ngàyDequeue vs Retention window
💡 Cách nhớ

Queue = thùng thư (xoá khi lấy, mỗi người cần bản riêng). Log = bảng thông báo (giữ lại, mọi người đọc cùng bản, tự nhớ vị trí đọc).

2. Broker truyền thống — queue, AMQP, và xoá sau đọc

Message broker truyền thống (RabbitMQ, ActiveMQ, IBM MQ) được xây trên mô hình queue: producer gửi message vào queue, broker giao cho consumer, và xoá message ngay khi consumer ACK (xác nhận đã nhận).

# Broker truyen thong: queue model
Producer --> [Queue: orders] --> Consumer Group A
                                 (ACK) --> broker xoa message khoi queue

# Neu Consumer Group B cung can doc cung event:
Producer --> [Queue: orders-for-analytics] --> Consumer Group A (analytics)
Producer --> [Queue: orders-for-billing]   --> Consumer Group B (billing)
# Can fan-out: message sao chep N lan cho N consumer group

Giao thức AMQP (Advanced Message Queuing Protocol) là nền của mô hình này: producer publish tới exchange, exchange route sang một hoặc nhiều queue theo routing key, consumer subscribe vào queue. Toàn bộ protocol được thiết kế cho point-to-point hoặc pub/sub với fan-out tường minh.

Điểm mạnh: routing linh hoạt (direct, topic, fanout, headers exchange), message priority, dead-letter queue, độ trễ p99 thấp ở tải nhẹ.

Điểm yếu căn bản khi cần nhiều consumer group đọc cùng luồng sự kiện:

  • Phải fan-out tường minh: 3 team đọc cùng event → 3 queue riêng, message sao chép 3 lần, producer (hoặc fan-out middleware) phải biết về tất cả downstream.
  • Không replay: message đã ACK thì biến mất — không tua lại được để kiểm tra lịch sử.
  • Không audit: queue có thể drain hết và không có cách dựng lại trạng thái đã qua.

3. Vì sao nhiều team cần đọc cùng một luồng sự kiện?

Trong hệ thống hiện đại, một sự kiện nghiệp vụ thường cần được xử lý bởi nhiều hệ thống độc lập ở tốc độ hoàn toàn khác nhau:

Team / hệ thốngEvent quan tâmLàm gì với eventTốc độ chấp nhận được
Analyticsorder.placedTổng hợp doanh thu theo giờVài giây lag OK
Billingorder.placedTạo hoá đơn, ghi sổGần real-time
Audit/Legalorder.placedLưu nhật ký bất biến 7 nămBatch, cần replay
Recommendationorder.placedCập nhật model gợi ýAsync, vài phút OK
Notificationorder.placedGửi email xác nhậnGần real-time

Với queue truyền thống, bạn phải dựng 5 queue và fan-out — khi thêm hệ thống thứ 6, bạn phải sửa cấu hình fan-out. Audit team đặt thêm yêu cầu: cần replay lại event từ tuần trước để xác minh báo cáo thuế — điều không thể với queue truyền thống vì message đã xoá.

Với log-based broker: mỗi team tự tạo consumer group của mình và đọc cùng topic ở offset riêng. Producer không cần biết downstream. Hệ thống thứ 6 tự tạo consumer group mới, replay từ đầu nếu muốn, hoàn toàn không ảnh hưởng các group khác.

4. Log-based broker — Kafka và cấu trúc append-only log

Kafka lưu mỗi message vào một log phân vùng (partitioned log). Ba khái niệm cốt lõi:

Topic là kênh sự kiện có tên (ví dụ orders, payments). Producer ghi vào topic; consumer đọc từ topic.

Partition là một phần của topic — một file log tuần tự trên đĩa. Kafka chia topic thành nhiều partition để song song hoá: nhiều producer ghi đồng thời, nhiều consumer trong cùng consumer group đọc song song (mỗi consumer giữ một tập partition không trùng).

Offset là số thứ tự của mỗi message trong một partition — tăng đơn điệu và không bao giờ thay đổi sau khi ghi. Đây là địa chỉ cố định của mỗi sự kiện trong log.

# Topic "orders", Partition 0 — append-only
offset:   [ 0 ]   [ 1 ]   [ 2 ]   [ 3 ]   [ 4 ]  -->
event:    [ord A] [ord B] [ord C] [ord D] [ord E] --> chi them vao cuoi

# Kafka KHONG xoa sau khi consumer doc
# Offset 0 van o do du Consumer Group analytics da doc no 3 lan
# Xoa chi xay ra khi het retention window (mac dinh 168 gio)

Kafka không push message đến consumer — consumer tự poll (kéo) theo offset hiện tại. Khi poll xong batch, consumer tự commit offset mới lên Kafka. Broker không cần theo dõi ai đã đọc gì — nó chỉ lưu log và phục vụ poll.

5. Offset — con trỏ của consumer group, không phải trạng thái broker

Đây là điểm khác biệt kiến trúc cốt lõi giữa Kafka và broker truyền thống.

Ở broker truyền thống, broker theo dõi trạng thái từng message (đã giao, đang chờ ACK, đã xoá). Trạng thái "đã đọc đến đâu" nằm ở phía broker.

Ở Kafka, consumer group tự giữ offset của mình — lưu trong một topic nội bộ gọi là __consumer_offsets. Broker chỉ là nơi lưu log và phục vụ poll; nó không biết consumer nào đang ở offset nào ngoài những gì consumer tự báo về.

flowchart LR
    P["Producer"] -->|"append offset 4"| LOG["Topic: orders<br/>Partition 0<br/>[0][1][2][3][4]"]
    LOG -->|"poll from offset 3"| CGA["Group: analytics<br/>offset = 3"]
    LOG -->|"poll from offset 4"| CGB["Group: billing<br/>offset = 4"]
    LOG -->|"poll from offset 0 (replay)"| CGC["Group: audit<br/>offset = 0"]

Ba consumer group đọc cùng một partition ở ba offset khác nhau — hoàn toàn độc lập. Audit đang replay từ offset 0 (đầu log), analytics ở offset 3, billing đã đọc đến offset 4.

Hệ quả trực tiếp của "offset thuộc consumer group":

  • Consumer group mới có thể bắt đầu từ auto.offset.reset=earliest (replay toàn bộ log trong window) hoặc latest (chỉ đọc event mới từ lúc này).
  • Consumer gặp sự cố restart từ offset đã commit — không mất event (nếu commit đúng cách).
  • Tua lại (rewind): audit team muốn kiểm tra lại dữ liệu tuần trước, chỉ cần reset offset về timestamp tương ứng — miễn còn trong retention window 168 giờ.
  • Không block lẫn nhau: analytics bị chậm không làm billing chậm theo, vì mỗi group giữ offset riêng.

6. So sánh: broker truyền thống vs log-based — khi nào dùng gì?

Đây là trọng tâm của bài. Hai mô hình không thay thế nhau hoàn toàn — mỗi cái có điểm mạnh rõ ràng.

Tiêu chíBroker truyền thống (RabbitMQ/ActiveMQ)Log-based broker (Kafka)
Xoá message sau đọcCó — sau khi consumer ACKKhông — giữ trong retention window
Nhiều consumer groupFan-out tường minh (nhiều queue riêng)Mọi group đọc cùng log độc lập
Replay lịch sửKhông thểĐược — trong retention window (168h mặc định)
Trạng thái offsetBroker quản lýConsumer group tự giữ
Đảm bảo thứ tựPer-queue (toàn cục nếu 1 consumer)Per-partition (đảm bảo trong partition)
Throughput (3 broker, 1KB, repl-3)RabbitMQ classic ~38.000 msg/s (thấp hơn ~16×)Kafka ~605.000 msg/s
Độ trễ p99 tải nhẹTốt hơn KafkaCao hơn khi tải nhẹ
Routing linh hoạtCó — direct/topic/fanout/headers exchangeKhông có built-in routing
Phù hợp nhấtTask queue, RPC, routing phức tạp, message priorityNhiều consumer group, replay, event sourcing, audit log

Số liệu benchmark Confluent 2020 (3 broker, message 1 KB, replication factor 3): Kafka đạt 605.000 msg/s so với RabbitMQ classic 38.000 msg/s — Kafka cao hơn 16 lần về throughput. Lý do: Kafka không xoá sau đọc → không overhead bookkeeping per-message; sequential append-only I/O tối ưu hơn random access của queue; zero-copy (sendfile syscall) khi consumer poll.

Khi nào chọn RabbitMQ thay Kafka

Chọn RabbitMQ khi: (1) cần routing linh hoạt (message priority, dead-letter queue, delay exchange), (2) chỉ một consumer group quan tâm mỗi event, (3) độ trễ p99 thấp ở tải nhỏ quan trọng hơn throughput. Chọn Kafka khi: nhiều team đọc cùng luồng sự kiện, cần replay/audit, hoặc throughput trên 100.000 msg/s.

7. Pitfall tổng hợp

Nhầm 1: "Đọc xong là mất" — chạy consumer mới với auto.offset.reset=latest

Người mới dùng Kafka hay tưởng Kafka xoá message như RabbitMQ, nên không để ý config reset offset khi deploy consumer group mới.

# SAI: start consumer group moi khong set auto.offset.reset
auto.offset.reset=latest   # gia tri mac dinh khi group chua co offset

# Ket qua: group bat dau tu message MOI NHAT luc deploy
# Toan bo du lieu cu trong window retention bi bo qua
# Giong nhu "xoa" du lieu theo kieu broker truyen thong

# DUNG: replay tu dau trong retention window
auto.offset.reset=earliest
# Hoac: reset offset ve mot timestamp cu cu the
# kafka-consumer-groups.sh --reset-offsets --to-datetime 2026-07-01T00:00:00.000

✅ Kafka giữ dữ liệu trong window retention (168 giờ mặc định). Dữ liệu không mất khi consumer đọc — chỉ mất khi quá retention hoặc bạn tường minh reset offset về latest.

Nhầm 2: Partition key sai làm mất thứ tự trong entity

Kafka chỉ đảm bảo thứ tự trong một partition. Nếu không set partition key, Kafka phân phối message round-robin — event của cùng một entity (ví dụ cùng orderId) có thể nằm ở partition khác nhau.

# SAI: khong set partition key
Producer.send(topic="orders", key=null, value=orderEvent)
# orderId=42 co the o partition 0, 1, hoac 2 -> thu tu khong bao dam

# DUNG: dung entity ID lam partition key
Producer.send(topic="orders", key=orderId.toString(), value=orderEvent)
# orderId=42 luon vao cung 1 partition (Kafka hash key -> partition)
# Thu tu event trong entity do duoc bao dam

✅ Luôn dùng entity ID (orderId, userId) làm partition key khi thứ tự trong entity đó quan trọng.

Nhầm 3: Bật enable.idempotence=true tưởng đã exactly-once end-to-end

enable.idempotence=true (mặc định từ Kafka 3.0) chỉ đảm bảo idempotent producer trong một session — producer không ghi trùng khi retry trong cùng kết nối. Nếu consumer reprocess và ghi vào DB downstream, vẫn có thể double-count.

Exactly-once end-to-end cần thêm Kafka transaction (từ Kafka 0.11.0): atomic write nhiều partition cộng với commit offset trong cùng một transaction. Đây là chủ đề của bài 04.

8. 📚 Deep Dive

📚 Deep Dive — Kafka Design và The Log

Spec / reference chính thức:

  • Apache Kafka Design (v4.3) — trang thiết kế chính thức: log-as-storage, partition, offset, pull-based consumer, consumer group, delivery semantics. Đọc mục "Log Compaction" để hiểu tại sao append-only log không phình mãi mà vẫn giữ snapshot trạng thái mới nhất mỗi key.
  • Kafka: a Distributed Messaging System for Log Processing — Kreps, Narkhede, Rao (NetDB 2011) — paper gốc từ LinkedIn. Xem mục "Consumer": thiết kế consumer-driven offset (consumer tự giữ offset) được giải thích ở đây, kèm benchmark so với ActiveMQ về throughput (producer ~50 MB/s trên 1 broker, 6 đĩa RAID 10, năm 2011).
  • The Log — Jay Kreps (LinkedIn Engineering, 2013) — bài viết nền quan trọng nhất. Kreps lập luận "log" là abstraction thống nhất cho DB replication, stream processing, và distributed coordination. Năm 2013 LinkedIn dùng Kafka xử lý 60 tỷ message/ngày, tiền thân của 7 nghìn tỷ năm 2019.

Ghi chú: đọc theo thứ tự — paper 2011 cho cơ chế, The Log 2013 cho triết lý thiết kế, Kafka Design (living doc) cho implementation hiện đại. Nếu chỉ đọc một: The Log là lựa chọn tốt nhất để nắm "vì sao log thắng queue".

9. Liên hệ các bài khác

  • Bài 01 — Tư duy stream: đặt nền mô hình event/producer/consumer và input unbounded — log-based broker là hạ tầng vận chuyển dòng sự kiện vô tận đó.
  • Bài 03 — Windowing: windowing cần event-time chính xác. Kafka log giữ timestamp của producer (event-time), không phải lúc consumer đọc (processing-time) — phân biệt này trở nên quan trọng khi reprocess lịch sử.
  • Bài 04 — Stream join và exactly-once: exactly-once end-to-end cần Kafka transaction — ghi kết quả cộng với commit offset trong cùng một atomic transaction (Kafka 0.11.0 trở đi).
  • Module 1 — Batch (bài 01 — Tư duy batch): kỷ luật "input bất biến, output phái sinh" của batch tương đương với "log chỉ-append, consumer đọc offset riêng" của Kafka — hai biểu hiện của cùng triết lý bất biến.

10. Tóm tắt

  • Broker truyền thống (RabbitMQ/ActiveMQ): xoá message sau ACK, nhiều consumer group cần fan-out sang queue riêng, không replay được. Routing linh hoạt; độ trễ p99 thấp ở tải nhẹ.
  • Log-based broker (Kafka): mỗi message ghi vào log chỉ-append với offset cố định, giữ trong retention window (168 giờ mặc định). Nhiều consumer group đọc cùng log độc lập — không cần fan-out.
  • Offset là con trỏ của consumer group, không phải trạng thái broker. Consumer tự giữ offset, có thể replay bất cứ lúc nào trong window retention.
  • Throughput: Kafka 605.000 msg/s vs RabbitMQ classic 38.000 msg/s (cùng điều kiện, Confluent 2020) — Kafka cao hơn 16 lần. Lý do: sequential append-only I/O + không overhead bookkeeping per-message.
  • Pitfall chính: (1) auto.offset.reset=latest bỏ qua dữ liệu cũ khi deploy consumer group mới, (2) partition key null làm mất thứ tự trong entity, (3) nhầm idempotent producer với exactly-once end-to-end.
  • Chọn Kafka khi: nhiều team đọc cùng luồng sự kiện, cần replay/audit, throughput cao. Chọn RabbitMQ khi: routing phức tạp, message priority, một consumer group duy nhất.

11. Tự kiểm tra

Tự kiểm tra
Q1
Vì sao broker truyền thống (queue) cần fan-out sang nhiều queue khi có nhiều consumer group, trong khi Kafka không cần?

Broker truyền thống xoá message sau khi consumer ACK — message chỉ tồn tại để giao cho một consumer (hoặc consumer group). Nếu nhiều team cần nhận cùng event, broker phải sao chép message vào nhiều queue riêng (fan-out tường minh) để mỗi team có bản của mình.

Kafka không xoá sau đọc — message nằm trong log với offset cố định trong suốt retention window. Mỗi consumer group tự giữ offset riêng và poll từ đó; broker không quan tâm ai đã đọc gì. Nên tất cả group đọc cùng log ở vị trí riêng — không cần fan-out, producer cũng không cần biết về downstream.

Q2
Offset là trạng thái của ai — broker hay consumer group? Nêu hai hệ quả cụ thể của câu trả lời đó.

Offset là trạng thái của consumer group, lưu trong topic nội bộ __consumer_offsets. Broker chỉ lưu log và phục vụ poll; nó không theo dõi consumer nào đang ở đâu.

Hai hệ quả trực tiếp:

  • Replay: consumer group có thể reset offset về vị trí cũ hơn (hoặc về offset 0) bất cứ lúc nào trong retention window — tái xử lý dữ liệu lịch sử mà không cần broker làm gì thêm.
  • Độc lập giữa các group: analytics bị chậm (lag lớn) không ảnh hưởng billing, vì mỗi group giữ offset riêng và broker không đợi group nào trước khi phục vụ group khác.
Q3
Benchmark cho thấy Kafka đạt 605.000 msg/s còn RabbitMQ classic đạt 38.000 msg/s cùng điều kiện — kém 16 lần. Giải thích vì sao log-based broker có throughput cao hơn về mặt cơ chế I/O.

Kafka dùng sequential append-only I/O: mỗi message ghi nối tiếp vào cuối file log. Sequential write rất nhanh (OS page cache + sequential disk access, không cần random seek). Không overhead xoá hay cập nhật trạng thái per-message.

Broker truyền thống phải làm thêm: (1) xoá hoặc đánh dấu message sau ACK — random write để cập nhật trạng thái queue; (2) theo dõi per-message state (đang chờ ACK từ ai, đã giao chưa) — overhead bộ nhớ và I/O. Mỗi message cần nhiều I/O operation hơn so với "chỉ append vào cuối log" của Kafka.

Ngoài ra, Kafka dùng zero-copy (sendfile syscall) để chuyển byte từ page cache trực tiếp sang network socket mà không copy qua user space — giảm CPU đáng kể khi consumer poll lượng lớn.

Q4
Một team deploy consumer group mới cho topic `orders` và chạy với cấu hình mặc định `auto.offset.reset=latest`. Họ phát hiện consumer không nhận được event nào từ tuần trước. Giải thích nguyên nhân và cách sửa.

auto.offset.reset=latest có hiệu lực khi consumer group chưa có offset nào được commit (group mới). Với latest, group bắt đầu đọc từ offset mới nhất tại thời điểm start — tức là chỉ nhận event xuất hiện sau khi group được tạo. Toàn bộ event từ tuần trước (dù vẫn còn trong retention window 168 giờ) bị bỏ qua.

Cách sửa: (1) set auto.offset.reset=earliest trước khi group đọc lần đầu — group sẽ replay từ offset 0 của partition; hoặc (2) tường minh reset offset của group về timestamp cụ thể bằng kafka-consumer-groups.sh --reset-offsets --to-datetime 2026-07-01T00:00:00.000. Lưu ý: cần làm trước khi consumer bắt đầu chạy, vì sau khi có offset commit thì auto.offset.reset không còn hiệu lực.

Q5
Retention window của Kafka mặc định là 168 giờ (7 ngày). Sau khi hết retention, message bị xoá. Điều này có mâu thuẫn với tính 'không xoá sau khi đọc' của Kafka không? Giải thích sự khác biệt.

Không mâu thuẫn — hai cơ chế xoá này khác nhau về ai kích hoạtkhi nào.

  • Broker truyền thống: xoá ngay khi consumer ACK — message biến mất ngay sau khi đọc, bất kể có bao nhiêu giây đã trôi qua, và bất kể ai khác cần đọc không.
  • Kafka retention: xoá dựa trên thời gian (hoặc kích thước log) theo policy cấu hình — message tồn tại đủ 168 giờ dù 10 consumer group đã đọc, sau đó broker mới xoá để tiết kiệm đĩa.

Trong window 168 giờ: mọi consumer group có thể đọc, replay, reset offset bất kỳ lúc nào. Sau window: message xoá theo batch bởi log retention — đây là trade-off storage vs replay, hoàn toàn tách biệt với trạng thái consumer.

Q6
Bạn cần thiết kế hệ thống nhận event `order.placed` và fan-out cho 5 team downstream (analytics, billing, audit, recommendation, notification). Khi nào nên chọn Kafka thay RabbitMQ? Nêu một lý do cụ thể từ yêu cầu ở trên.

Nên chọn Kafka chủ yếu vì yêu cầu của team audit: cần replay lại event từ tuần trước để xác minh báo cáo — điều không thể với RabbitMQ vì message đã ACK thì biến mất.

Các lý do bổ sung: (1) producer decoupled khỏi downstream — 5 team tự tạo consumer group, thêm team thứ 6 không cần sửa producer; (2) 5 team xử lý ở tốc độ khác nhau — Kafka cho phép mỗi group lag độc lập mà không block lẫn nhau; (3) throughput — nếu mỗi đơn hàng sinh nhiều event con (clicked, added-to-cart, paid), tổng throughput có thể vượt 100.000 msg/s.

Chỉ nên chọn RabbitMQ nếu: cần routing phức tạp per-message (ví dụ notification chỉ gửi với đơn trên 500K VND), hoặc cần dead-letter queue và message priority — đây là tính năng RabbitMQ có sẵn mà Kafka không có built-in.

Bài tiếp theo: Windowing — tumbling, sliding, session và event-time

Bài này có giúp bạn hiểu bản chất không?

Hỏi đáp về bài này

Chưa có câu hỏi

Đặt câu hỏi

Có gì chưa rõ trong bài? Đặt câu hỏi đầu tiên — câu trả lời từ cộng đồng giúp bạn (và người sau).

Đặt câu hỏi đầu tiên