Harbor

Tổng hợp lượt click quảng cáo

KhóStream processingIdempotencyĐối soát batchOLAPGiống Google Ads, Facebook Ads Manager, hệ thống đo click của ad network

Hiểu đề

Tổng hợp lượt click quảng cáo là gì? Người xem bấm vào quảng cáo, hệ thống ghi nhận click rồi chuyển họ tới trang của nhà quảng cáo. Nhà quảng cáo xem số click theo phút, giờ, ngày và trả tiền theo số click hợp lệ, nên số liệu vừa phải nhanh vừa phải đúng tới mức tính tiền.

Yêu cầu chức năng

  1. Người xem bấm quảng cáo được chuyển tới trang đích, mỗi click được ghi nhận
  2. Nhà quảng cáo xem số click theo quảng cáo hoặc chiến dịch, chọn khoảng thời gian và độ chi tiết phút/giờ/ngày
  3. Số liệu tạm tính hiện trong vòng 1 phút, số đã chốt để tính tiền có mỗi ngày
  4. Click trùng và click gian lận bị loại khỏi số tính tiền

Yêu cầu phi chức năng

  • Redirect p95 < 50 ms, sẵn sàng 99,99% (lỗi redirect là mất tiền của cả hai bên)
  • Không mất click đã redirect; số tính tiền sau đối soát lệch < 0,01%
  • Dashboard trễ ≤ 1 phút, truy vấn 30 ngày theo một nhà quảng cáo p95 < 1 s
  • Chịu ≈ 58.000 click/s lúc đỉnh (Black Friday, chung kết bóng đá)

Ngoài phạm vi: Đấu giá và chọn quảng cáo để hiển thị (ad serving); Xuất hóa đơn và thu tiền nhà quảng cáo; Theo dõi chuyển đổi (mua hàng sau click); Mô hình ML phát hiện gian lận chi tiết (chỉ nói ở mức luật).

Thực thể và API

Ad
Quảng cáo: ad_id, campaign_id, advertiser_id, URL trang đích, trạng thái.
Impression
Một lần hiển thị do ad server tạo: impression_id, ad_id, thiết bị, thời điểm. Link click mang token ký HMAC chứa impression_id.
ClickEvent
Một click: click_id (= impression_id), ad_id, thời điểm, IP băm, user agent, cờ hợp lệ và lý do loại.
AdClickMinute
Số tổng hợp: ad_id, phút, clicks, valid_clicks, nguồn (stream tạm tính hoặc batch đã chốt).
ReconciliationReport
Kết quả đối soát mỗi ngày: ngày, ad_id, số stream, số batch, chênh lệch.
MethodEndpointGhi chú
GET/c/{token}Click vào quảng cáo. Xác minh chữ ký token, trả 302 Location tới trang đích, ghi sự kiện bất đồng bộ.
GET/v1/advertisers/{id}/clicks?adIds=&from=&to=&granularity=minute|hour|dayChuỗi thời gian số click. Kèm cờ tạm tính hay đã chốt cho từng khoảng.
GET/v1/advertisers/{id}/campaigns/{cid}/summary?from=&to=Tổng click và click hợp lệ theo chiến dịch.
GET/v1/advertisers/{id}/reports/daily?date=Số đã chốt sau đối soát, dùng để tính tiền.

Thiết kế tổng thể

Đi lần lượt từng yêu cầu chức năng. Mục tiêu ở đây là chạy đúng, việc tối ưu để dành cho phần đào sâu.

1) Người xem bấm quảng cáo và được chuyển tới trang đích

Click đi qua redirect service của ta thay vì nhảy thẳng tới trang đích, nên không phụ thuộc JavaScript phía client hay trình chặn quảng cáo. Service chỉ xác minh token và ghi sự kiện, việc đếm làm phía sau.

  1. 1Ad server sinh link /c/{token}, token ký HMAC chứa impression_id, ad_id, thời điểm hiển thị
  2. 2Người xem bấm, redirect service kiểm tra chữ ký và hạn của token
  3. 3Service tra URL đích của ad_id trong cache cục bộ, trả 302
  4. 4Sự kiện click được gửi vào queue (Kafka ở mức lớn) với key ad_id; token sai thì vẫn redirect nhưng ghi cờ không hợp lệ

Nguồn: Grokking Modern System Design · ch.34 TinyURL ↗ · mục "Requirements" (redirect phải sẵn sàng cao vì lỗi redirect là lỗi người dùng thấy) ; Grokking Modern System Design · ch.17 Distributed Messaging Queue ↗ · bài "Design of a Distributed Messaging Queue" (producer ghi vào queue) ; Chưa có nguồn trong thư viện: token ký HMAC là kỹ thuật bảo mật cơ bản, thư viện không bàn

2) Số click được tổng hợp gần real-time

Tầng stream đọc sự kiện, loại trùng theo click_id, áp luật gian lận cơ bản, rồi đếm theo cửa sổ 1 phút cho mỗi ad_id dựa trên thời điểm click (event time).

  1. 1Flink đọc topic clicks, key theo click_id để loại trùng trong keyed state 24 h
  2. 2Áp luật: quá nhiều click từ một IP hoặc thiết bị, click ngay sau hiển thị dưới 100 ms, user agent bot
  3. 3Key lại theo ad_id, đếm theo cửa sổ tumbling 1 phút với watermark
  4. 4Ghi (ad_id, phút, clicks, valid_clicks) vào kho OLAP, ghi đè được nếu cửa sổ tính lại

Nguồn: Microservices Patterns ↗ (.zip) · ch.3 · §3.3.6 Handling duplicate messages (tr. 95–96) (track messages and discard duplicates theo message id) ; Chưa có nguồn trong thư viện: thư viện không có sách về Flink/Spark/ClickHouse/Kafka; chỉ nêu khái niệm cơ bản (event time, watermark, cửa sổ tumbling)

3) Nhà quảng cáo xem số liệu theo thời gian

Truy vấn luôn đọc bảng đã tổng hợp sẵn, không bao giờ đọc click thô. Kho OLAP giữ bảng phút và các bảng giờ, ngày gộp sẵn, phân vùng theo ngày và sắp theo advertiser_id.

  1. 1Dashboard gọi /v1/advertisers/{id}/clicks với khoảng thời gian
  2. 2Query API chọn bảng theo độ chi tiết: ≤ 2 ngày dùng bảng phút, ≤ 90 ngày dùng bảng giờ, dài hơn dùng bảng ngày
  3. 3Kết quả cache 30 s trong Redis theo (advertiser, truy vấn)
  4. 4Khoảng đã đối soát lấy số batch, khoảng hôm nay lấy số stream và gắn nhãn tạm tính

Nguồn: Grokking Modern System Design · ch.21 Distributed Search ↗ · mục "Indexing" (dữ liệu tổng hợp sẵn phục vụ truy vấn nhanh) ; Grokking Modern System Design · ch.16 Distributed Cache ↗ · mục "Writing policies" ; Chưa có nguồn trong thư viện: kho OLAP và chọn bảng theo độ chi tiết là thiết kế của khoá học

4) Số tính tiền được chốt mỗi ngày

Click thô được lưu nguyên vào data lake. Mỗi đêm job batch tính lại toàn bộ ngày hôm trước với luật loại trùng và gian lận đầy đủ, so với số stream và thay thế phân vùng ngày đó.

  1. 1Kafka Connect đổ topic clicks vào S3 dạng Parquet theo giờ
  2. 2Spark đọc cả ngày, loại trùng theo click_id, áp luật gian lận cả ngày
  3. 3So từng ad_id với số stream, ghi ReconciliationReport, cảnh báo nếu lệch > 0,1%
  4. 4Ghi đè phân vùng ngày trong kho OLAP bằng số batch, đánh dấu đã chốt

Nguồn: Grokking Modern System Design · ch.21 Distributed Search ↗ · mục "Scaling Search and Indexing" (MapReduce xử lý hàng loạt trên cụm phân tán) ; Chưa có nguồn trong thư viện: thư viện không có sách về Flink/Spark/ClickHouse/Kafka; chỉ nêu khái niệm cơ bản (Spark, Parquet, đối soát)

Hạ tầng theo quy mô

Cùng một sản phẩm, ba mức người dùng. Chọn từng mức để xem sơ đồ, phép tính và cấu hình server thay đổi thế nào.

Người dùng
50 nhà quảng cáo, 100.000 click/ngày
Tải đỉnh
≈ 12 click/s đỉnh
Chi phí ước lượng
≈ 40–100 USD/tháng (ước lượng)

Một ad network nhỏ. Một service vừa redirect vừa phục vụ dashboard, ghi click vào Postgres với khóa chính click_id, cron mỗi phút gộp thành bảng phút.

Người dùngNgười xem QCNgười dùngNhà quảng cáoBên ngoàiTrang đíchDịch vụRedirect + APIDatabasePostgreSQLWorkerCron gộp phútGET /c/{token}theo 302dashboardINSERT clickgộp theo phút

Ước lượng

  • Click: 100.000 / 86.400 ≈ 1,2 click/s, đỉnh ×10 ≈ 12 click/s
  • API: ceil(12 / 600) = 1 → chạy 2 instance để dự phòng
  • Ghi: 12 INSERT/s, PRIMARY KEY click_id + ON CONFLICT DO NOTHING chặn trùng
  • Lưu thô: 100.000 × 300 B = 30 MB/ngày ≈ 11 GB/năm
  • Bảng phút: ≤ 100.000 hàng/ngày (không quá số click) × 50 B ≈ 5 MB/ngày
  • Dashboard: 50 × 50 truy vấn = 2.500 truy vấn/ngày, đọc bảng phút

Nguồn con số: Grokking Modern System Design · ch.5 Back-of-the-envelope Calculations ↗ · mục "Requests estimation", "Storage requirements" ; Quy tắc ước lượng của khoá học · /hoc/chon-server (60% CPU, 600 req/s mỗi instance, Redis ×1,3, năng lực Kafka/Flink là giả định của khoá học, cần load test)

Bảng hạ tầng

Thành phầnChọnSố lượngCấu hìnhVì sao
Redirect + APINode.js (NestJS) trên Cloud Run22 vCPU · 4 GB RAM12 req/s rất nhỏ, chạy 2 để redirect không chết khi một instance lỗi.
DatabasePostgreSQL managed12 vCPU · 8 GB · 50 GB SSD11 GB/năm thô; ghi đồng bộ trước khi redirect vẫn nhanh ở tải này.
Gộp số liệuCron (Cloud Scheduler) mỗi phút1Chạy trong APIINSERT ... SELECT gộp phút vừa xong vào bảng click_minute, chạy lại được vì ghi đè theo (ad_id, phút).
DashboardSPA tĩnh trên CDN1Cloudflare Pages / S3 + CloudFrontGần như miễn phí, gọi API để lấy số.
Thay đổi so với mức trước
  • Điểm xuất phát: một service, một Postgres
  • Chặn trùng bằng khóa chính click_id, gộp số bằng cron
  • Chưa cần queue hay stream processing
Nút thắt tiếp theo

Khi click lên hàng trăm mỗi giây, ghi đồng bộ vào Postgres làm redirect chậm theo DB, và cron gộp phút tranh tài nguyên với dashboard.

Cấu hình và chi phí là ước lượng để học và trình bày trong phỏng vấn. Xem cách tính ở bài Chọn server.

So sánh nhanh ba mức

Thành phầnMức 1Mức 2Mức 3
Người dùng50 nhà quảng cáo, 100.000 click/ngày5.000 nhà quảng cáo, 10 triệu click/ngày100.000 nhà quảng cáo, 1 tỷ click/ngày
Chi phí≈ 40–100 USD/tháng (ước lượng)≈ 800–2.000 USD/tháng (ước lượng)≈ 30.000–70.000 USD/tháng (ước lượng)
Redirect + API
Node.js (NestJS) trên Cloud Run
2 × 2 vCPU · 4 GB RAM
Chưa cầnChưa cần
Database
PostgreSQL managed
1 × 2 vCPU · 8 GB · 50 GB SSD
PostgreSQL managed
1 primary + 1 read replica × 4 vCPU · 16 GB · 200 GB SSD
Chưa cần
Gộp số liệu
Cron (Cloud Scheduler) mỗi phút
1 × Chạy trong API
Chưa cầnChưa cần
Dashboard
SPA tĩnh trên CDN
1 × Cloudflare Pages / S3 + CloudFront
Chưa cầnChưa cần
Redirect serviceChưa cần
Node.js, container autoscale
2–4 (autoscale) × 2 vCPU · 4 GB RAM
Node.js trên Kubernetes, 3 region
97 pod (HPA) × 2 vCPU · 4 GB mỗi pod, đĩa đệm cục bộ
StreamChưa cần
Amazon Kinesis Data Streams
2 shard × Key theo ad_id, giữ 24 h
Chưa cần
AggregatorChưa cần
Node.js consumer (KCL)
2 × 1 vCPU · 2 GB RAM
Chưa cần
Redis dedupChưa cần
ElastiCache
1 primary + 1 replica × 2 vCPU · 4 GB
Chưa cần
Lưu thôChưa cần
Kinesis Firehose → S3 (Parquet)
1 × 10.000.000 × 500 B = 5 GB/ngày
Chưa cần
Đối soátChưa cần
Athena / Spark job hàng đêm
1 job × Quét 5 GB mỗi đêm
Chưa cần
Global LBChưa cầnChưa cần
Anycast LB (CloudFront / Cloud Load Balancing)
1 × Định tuyến tới region gần nhất
KafkaChưa cầnChưa cần
MSK / Confluent
6 broker × Topic clicks 32 partition, RF 3, 4 TB mỗi broker
FlinkChưa cầnChưa cần
Flink trên Kubernetes, RocksDB, checkpoint lên S3
8 TaskManager × 4 vCPU · 16 GB · 100 GB SSD mỗi TM
OLAPChưa cầnChưa cần
ClickHouse (ReplicatedReplacingMergeTree)
3 shard × 2 replica × 8 vCPU · 32 GB · 2 TB SSD mỗi node
Query APIChưa cầnChưa cần
Node.js trên Kubernetes
3 pod × 2 vCPU · 4 GB mỗi pod
RedisChưa cầnChưa cần
ElastiCache
1 primary + 1 replica × 2 vCPU · 8 GB
Data lake + đối soátChưa cầnChưa cần
Kafka Connect → S3 Parquet, Spark trên EMR
1 cụm theo lịch × ≈ 100 GB Parquet/ngày (500 GB thô nén ~5 lần)

Đào sâu

Mỗi câu hỏi đi từ cách đơn giản đến cách người phỏng vấn mong đợi ở cấp senior.

1) Ghi nhận click thế nào để không mất click mà redirect vẫn nhanh?

Cách chưa tốt
JavaScript phía trình duyệt gửi một request ghi click riêng

Trình duyệt chuyển trang ngay nên request ghi click hay bị hủy giữa chừng, trình chặn quảng cáo chặn luôn. Kẻ gian cũng gọi thẳng API này để tạo click giả.

Đánh đổi: Redirect nhanh nhất nhưng mất vài phần trăm click và không kiểm soát được nguồn.

⚠ Chưa có nguồn trong thư viện: hành vi trình duyệt hủy request khi chuyển trang và trình chặn quảng cáo không có trong sách

Cách tốt
Redirect service ghi click vào DB rồi mới trả 302

Mọi click đi qua server của ta và được ghi chắc chắn trước khi chuyển hướng.

Đánh đổi: Độ trễ redirect phụ thuộc DB, và DB thành nút thắt khi click lên hàng chục nghìn mỗi giây.

Nguồn: Grokking Modern System Design · ch.34 TinyURL ↗ · mục "Requirements" (độ trễ redirect) ; Grokking Modern System Design · ch.5 Back-of-the-envelope Calculations ↗ · mục "Standard numbers to remember" (QPS một DB điển hình ≈ 1.000)

Cách tốt nhất
Redirect service ghi vào Kafka với acks=all, có bộ đệm đĩa cục bộ, token ký HMAC

Service xác minh token, gửi sự kiện tới Kafka (thường 2-5 ms) rồi trả 302. Nếu Kafka chậm quá 20 ms, sự kiện được ghi vào file đệm trên đĩa của pod và một tiến trình nền gửi lại sau, nên redirect không bao giờ chờ lâu. Token chứa impression_id làm click_id cố định, nên request lặp hay gửi lại đều mang cùng một id.

Đánh đổi: Phải xử lý đĩa đệm khi pod bị xóa (drain trước khi tắt) và chấp nhận at-least-once, tức trùng sẽ được loại ở bước sau.

Nguồn: Grokking Modern System Design · ch.17 Distributed Messaging Queue ↗ · bài "Design of a Distributed Messaging Queue" ; Microservices Patterns ↗ (.zip) · ch.3 · §3.3.6 Handling duplicate messages (tr. 95–96) (broker giao tối thiểu một lần, nên cần chống trùng) ; Chưa có nguồn trong thư viện: Kafka acks=all và bộ đệm đĩa cục bộ

2) Tổng hợp gần real-time thế nào, nhất là với click đến muộn?

Cách chưa tốt
Lưu click thô, dashboard chạy COUNT ... GROUP BY khi mở

Mỗi lần mở dashboard quét hàng triệu tới hàng tỷ dòng. Hàng nghìn nhà quảng cáo mở cùng lúc thì DB quá tải.

Đánh đổi: Linh hoạt mọi chiều, nhưng chậm và đắt ở quy mô thật.

Nguồn: Grokking Modern System Design · ch.5 Back-of-the-envelope Calculations ↗ · mục "Standard numbers to remember" (QPS một DB điển hình) ; Chưa có nguồn trong thư viện: chi phí quét COUNT/GROUP BY là ước lượng của khoá học

Cách tốt
Batch nhỏ mỗi 5 phút gộp click thô thành bảng phút

Cron hoặc Spark gộp phần dữ liệu mới vào bảng tổng hợp, dashboard đọc bảng này.

Đánh đổi: Đơn giản và đúng, nhưng trễ 5-10 phút, còn job chạy chồng nhau khi dữ liệu tăng.

Nguồn: Grokking Modern System Design · ch.21 Distributed Search ↗ · mục "Scaling Search and Indexing" (MapReduce, xử lý hàng loạt)

Cách tốt nhất
Flink cửa sổ tumbling 1 phút theo event time, watermark và allowed lateness

Đếm theo thời điểm click thật nên click từ điện thoại mất mạng gửi lại muộn vẫn vào đúng phút. Watermark chậm 30 s, cửa sổ vẫn nhận thêm trong 2 phút và mỗi lần nhận thêm thì phát lại kết quả đầy đủ của phút đó. Kho OLAP giữ bản mới nhất theo (ad_id, phút), nên ghi lại là an toàn. Click muộn hơn 2 phút đi vào side output và được batch đêm xử lý. Checkpoint gắn offset Kafka với state nên khởi động lại không đếm hai lần.

Đánh đổi: Cần vận hành Flink và chọn độ trễ watermark: ngắn thì số tạm tính thiếu, dài thì dashboard chậm.

Nguồn: Chưa có nguồn trong thư viện: thư viện không có sách về Flink/Spark/ClickHouse/Kafka; chỉ nêu khái niệm cơ bản (event time, watermark, allowed lateness, side output) ; Microservices Patterns ↗ (.zip) · ch.3 · §3.3.6 Handling duplicate messages (tr. 95–96) (xử lý lại an toàn khi ghi idempotent)

3) Loại click trùng và click gian lận thế nào?

Cách chưa tốt
Đếm mọi request tới /c/{token}

Người dùng bấm đúp, trình duyệt tải lại, consumer xử lý lại sau sự cố đều thành click mới. Bot bấm liên tục cũng được tính.

Đánh đổi: Nhà quảng cáo trả tiền cho click không có thật và mất niềm tin.

Nguồn: Microservices Patterns ↗ (.zip) · ch.3 · §3.3.6 Handling duplicate messages (tr. 95–96) (duplicate message do client crash trước khi ack, nên một sự kiện có thể xử lý nhiều lần)

Cách tốt
Redis SET click_id NX EX 86400 trước khi đếm

Chỉ click lần đầu thấy click_id mới được đếm. Đây là cách theo dõi id đã xử lý để bỏ bản trùng trong Microservices Patterns §3.3.6. Lưu kèm sequence number của bản ghi, để khi consumer chạy lại cùng đoạn sau sự cố thì không bị coi là trùng.

Đánh đổi: Hiệu quả ở mức vừa; 1 tỷ click/ngày cần ≈ 183 GB RAM, và click_id do client tự đặt thì vẫn giả được.

Nguồn: Microservices Patterns ↗ (.zip) · ch.3 · §3.3.6 Handling duplicate messages (tr. 95–96) (mục "Tracking messages and discarding duplicates": lưu id đã xử lý) ; Redis in Action ↗ · ch.3 · §3.7.3 Expiring keys ; Grokking Modern System Design · ch.5 Back-of-the-envelope Calculations ↗ · mục "Storage requirements" (phép tính RAM)

Cách tốt nhất
click_id = impression_id đã ký, dedup trong Flink state, luật gian lận ở stream và batch

Click chỉ hợp lệ khi token có chữ ký đúng và còn hạn, nên mỗi lần hiển thị cho tối đa một click tính tiền và không ai tự tạo id được. Flink key theo click_id, state TTL 24 h trong RocksDB, cùng checkpoint với offset nên chính xác kể cả khi chạy lại. Luật nhanh (tần suất theo IP, thiết bị, click quá sớm) chạy trong stream; luật chậm và ML chạy ở batch đêm. Giữ cả clicks và valid_clicks để nhà quảng cáo thấy số bị loại.

Đánh đổi: State dedup lớn (≈ 64 GB ở 1 tỷ click/ngày) và luật gian lận có thể loại nhầm, nên phải có số liệu và kênh khiếu nại.

Nguồn: Microservices Patterns ↗ (.zip) · ch.3 · §3.3.6 Handling duplicate messages (tr. 95–96) (idempotent handler, tracking messages) ; Chưa có nguồn trong thư viện: thư viện không có sách về Flink/Spark/ClickHouse/Kafka; chỉ nêu khái niệm cơ bản (keyed state, checkpoint) ; Chưa có nguồn trong thư viện: luật gian lận và ML không có trong sách

4) Trả số liệu theo nhà quảng cáo nhanh và khớp với số tính tiền thế nào?

Cách chưa tốt
Chỉ tin số stream, ghi vào Postgres và truy vấn thẳng

Lỗi code ở job stream, mất một partition hay luật gian lận đổi đều làm sai số tiền mà không ai biết. Bảng phút hàng trăm triệu hàng mỗi ngày làm Postgres truy vấn chậm.

Đánh đổi: Một hệ thống duy nhất, nhưng không có cách kiểm chứng.

Nguồn: Microservices Patterns ↗ (.zip) · ch.3 · §3.3.6 Handling duplicate messages (tr. 95–96) (không có cách kiểm chứng khi chỉ có một đường xử lý thì lỗi âm thầm) ; Chưa có nguồn trong thư viện: ví dụ lỗi stream job là suy luận của khoá học

Cách tốt
Batch đêm tính lại từ dữ liệu thô và báo chênh lệch

Click thô lưu ở S3, Spark tính lại mỗi đêm và so với số stream. Kiến trúc lambda: stream cho nhanh, batch cho đúng.

Đánh đổi: Phát hiện được sai nhưng vẫn phải quyết định số nào hiển thị và hai bộ code dễ lệch logic.

Nguồn: Chưa có nguồn trong thư viện: kiến trúc lambda (stream cho nhanh, batch cho đúng) không có trong sách ; Grokking Modern System Design · ch.21 Distributed Search ↗ · mục "Scaling Search and Indexing" (batch bằng MapReduce)

Cách tốt nhất
Kho OLAP gộp sẵn theo nhiều độ chi tiết, batch ghi đè phân vùng đã chốt

ClickHouse (hoặc Druid, Pinot) giữ bảng phút, materialized view gộp lên giờ và ngày, sắp xếp theo (advertiser_id, ad_id, thời gian) nên truy vấn của một nhà quảng cáo chỉ đọc một vùng nhỏ. Hôm nay hiển thị số stream gắn nhãn tạm tính. Sau đối soát, batch ghi đè phân vùng ngày và đánh dấu đã chốt; hóa đơn chỉ dùng số đã chốt. Dùng chung thư viện luật cho Flink và Spark, và giữ Kafka 7 ngày để replay khi sửa lỗi.

Đánh đổi: Số hôm qua có thể đổi nhẹ sau khi chốt, phải giải thích cho nhà quảng cáo; vận hành thêm một kho OLAP.

Nguồn: Chưa có nguồn trong thư viện: ClickHouse/Druid/Pinot, materialized view và ghi đè phân vùng không có trong sách ; Grokking Modern System Design · ch.9 Databases ↗ · mục "Columnar database" (lưu theo cột phù hợp truy vấn tổng hợp)

Trong phỏng vấn

Nên nói
  • “Click đi qua redirect service của tôi, service chỉ xác minh token và đẩy sự kiện vào Kafka rồi trả 302.”
  • “click_id là impression_id đã ký HMAC, nên dedup theo click_id vừa chống trùng vừa chống tạo click giả.”
  • “Flink đếm theo cửa sổ 1 phút trên event time, có watermark và allowed lateness, ghi đè idempotent theo (ad_id, phút).”
  • “Số stream là tạm tính; batch đêm trên dữ liệu thô là số tính tiền và ghi đè phân vùng ngày.”
Bẫy hay gặp
  • Đếm click bằng request JavaScript phía client.
  • Dùng processing time nên click đến muộn rơi vào sai phút.
  • Dedup bằng Redis mà không tính tới consumer chạy lại cùng đoạn sau sự cố.
  • Dashboard truy vấn thẳng bảng click thô.

Đọc thêm

Apache Flink
Flink là engine xử lý stream có trạng thái, đọc event từ Kafka, gom theo cửa sổ thời gian và cho kết quả chính xác kể cả khi event đến muộn hoặc máy chết. Bài này giải thích event time, watermark, các loại window, state và checkpoint, exactly-once với Kafka, và so sánh với Kafka Streams, Spark.
Kafka
Kafka là log phân tán bền vững: topic chia partition, consumer đọc theo offset và có thể đọc lại. Bài này đi qua thứ tự theo partition, consumer group, replication với ISR và acks, giới hạn của exactly-once, cách chọn số partition và so sánh với SQS, Pub/Sub, RabbitMQ.
Scale ghi
Khi lượng ghi vượt sức một database: gom batch, ghi bất đồng bộ qua hàng đợi, chọn storage tối ưu cho ghi, chia dữ liệu theo key, xử lý partition nóng bằng salting, sharded counter và tổng hợp trước. Kèm con số cho từng quy mô.
Time-series database
Time-series database lưu dữ liệu đo theo thời gian như metric server, giá cổ phiếu, vị trí GPS. Bài này giải thích đặc điểm của loại dữ liệu này, cách nén, downsampling và retention, so sánh TimescaleDB, InfluxDB, Prometheus, ClickHouse, và bẫy cardinality.
Redis
Redis là kho key-value trong RAM với nhiều cấu trúc dữ liệu: dùng làm cache, bộ đếm, leaderboard, hàng đợi, GEO và pub/sub. Bài này đi qua mô hình dữ liệu, persistence, replication, Cluster, Lua và cách tính RAM.
Cấu trúc dữ liệu cho big data
Bloom filter, Count-Min Sketch, HyperLogLog, Top-K bằng heap và Space-Saving, skip list, LSM tree: mỗi cấu trúc giải bài toán gì, tốn bao nhiêu RAM, sai số bao nhiêu, và bài thiết kế nào cần tới.

Sách trong thư viện của bạn. Số chương có thể lệch nhẹ so với bản PDF.