Harbor

Nền tảng video

KhóUpload file lớnTranscodeStreaming qua CDNChi phí băng thôngGiống YouTube, Vimeo, FPT Play

Hiểu đề

Nền tảng video là gì? Nền tảng cho người dùng tải video lên và cho hàng triệu người khác xem trên web, điện thoại, TV. Video được chuyển thành nhiều độ phân giải và phát qua CDN, chất lượng tự đổi theo tốc độ mạng của người xem.

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

  1. Người dùng có thể tải lên video lớn (đến vài GB) và tiếp tục khi mạng bị ngắt
  2. Hệ thống xử lý video thành nhiều độ phân giải kèm ảnh thumbnail
  3. Người dùng có thể xem video mượt, chất lượng tự điều chỉnh theo mạng
  4. Người dùng có thể xem thông tin video và số lượt xem

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

  • Video bắt đầu phát p95 < 2 s, tỉ lệ khựng (rebuffer) < 1% thời gian xem
  • Video 10 phút sẵn sàng để xem trong < 10 phút sau khi upload xong
  • Không mất video đã upload: độ bền lưu trữ 99,999999999% (11 số 9)
  • Trang xem video sẵn sàng 99,95%; số lượt xem được trễ vài phút
  • Chi phí băng thông là khoản lớn nhất, phải tối ưu từng MB phát ra

Ngoài phạm vi: Livestream (xem bài Bình luận livestream cho phần real-time); Gợi ý video, tìm kiếm, bình luận, quảng cáo; Kiểm duyệt nội dung và bản quyền (Content ID); DRM cho nội dung trả phí.

Thực thể và API

Video
Metadata: video_id, owner, tiêu đề, mô tả, thời lượng, trạng thái UPLOADING/PROCESSING/READY/FAILED, đường dẫn manifest.
UploadSession
Phiên upload nhiều phần: upload_id của object storage, kích thước phần, danh sách phần đã xong, hạn hết hiệu lực.
Rendition
Một mức trong ladder của video: độ phân giải, bitrate, codec, prefix các segment trên storage.
TranscodeJob
Một bước trong DAG xử lý: loại (split, encode, package, thumbnail), segment, rendition, trạng thái, số lần thử.
Channel
Kênh của người dùng: tên, avatar, số người đăng ký.
ViewEvent
Sự kiện xem: video_id, viewer/session, vị trí phát, thời điểm. Gom lại thành bộ đếm lượt xem.
MethodEndpointGhi chú
POST/v1/uploadsTạo Video + UploadSession, trả uploadId và presigned URL cho từng phần 8 MB.
PUT{presigned-url}?partNumber=nClient đẩy thẳng từng phần lên object storage, không đi qua API.
GET/v1/uploads/{id}Danh sách phần đã nhận, dùng để upload tiếp sau khi mất mạng.
POST/v1/uploads/{id}/completeGhép các phần, chuyển Video sang PROCESSING và khởi động pipeline transcode.
GET/v1/videos/{id}Metadata, số lượt xem và URL master manifest (HLS/DASH) trên CDN có ký.
POST/v1/videos/{id}/viewsHeartbeat khi đang xem (30 s/lần), dùng để đếm view và thời gian xem.

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 dùng có thể tải lên video lớn và tiếp tục khi mất mạng

File gốc vài trăm MB đến vài GB không nên đi qua API server. API chỉ cấp presigned URL, client chia file thành phần 8 MB và đẩy thẳng lên object storage bằng multipart upload.

  1. 1Client gọi POST /v1/uploads với kích thước file, nhận uploadId và URL cho từng phần
  2. 2Client PUT song song 3-4 phần một lúc lên S3/GCS, phần lỗi thì gửi lại riêng phần đó
  3. 3Mất mạng: client gọi GET /v1/uploads/{id} để biết phần nào đã có và gửi tiếp
  4. 4Xong hết thì gọi /complete, storage ghép thành một object raw/{videoId}

Nguồn: Grokking Modern System Design · ch.26 Design YouTube ↗ · bài "Design of YouTube" · mục "High-level design" (upload, blob storage) ; Grokking Modern System Design · ch.20 Blob Store ↗ · bài "Design of a Blob Store" · mục "Write a blob" (chia blob thành chunk) ; Chưa có nguồn trong thư viện: presigned URL và multipart upload của S3/GCS không có trong sách

2) Hệ thống xử lý video thành nhiều độ phân giải

Sự kiện upload xong kích hoạt một DAG job: kiểm tra file, cắt thành đoạn, encode song song từng đoạn ở từng mức, rồi đóng gói thành HLS/DASH. Mỗi job idempotent nên chạy lại được khi worker chết.

  1. 1Object storage phát sự kiện upload xong vào queue
  2. 2Orchestrator tạo DAG: probe → split → encode (đoạn × mức) → package → thumbnail
  3. 3Worker ffmpeg lấy job, encode đoạn 10 s ở một mức, ghi kết quả lên storage
  4. 4Đủ đoạn thì package sinh manifest .m3u8/.mpd, cập nhật Video sang READY

Nguồn: Grokking Modern System Design · ch.26 Design YouTube ↗ · bài "Design of YouTube" · mục "High-level design" (encoder/transcoder tạo nhiều độ phân giải) ; Grokking Modern System Design · ch.26 Design YouTube ↗ · bài "The Reality Is More Complicated" · mục "Encode" (chia video thành segment, encode từng segment) ; Grokking Modern System Design · ch.23 Distributed Task Scheduler ↗ · mục "Task idempotency" ; Chưa có nguồn trong thư viện: định dạng HLS/DASH và ffmpeg

3) Người dùng có thể xem video mượt theo tốc độ mạng

Video được lưu thành các segment 4 s ở nhiều bitrate. Player tải manifest, đo băng thông và độ đầy buffer, tự chọn mức phù hợp cho từng segment. Mọi segment đi qua CDN vì nội dung không bao giờ đổi.

  1. 1Client gọi GET /v1/videos/{id}, nhận URL master manifest có chữ ký hết hạn
  2. 2Player tải master manifest từ CDN, chọn mức khởi đầu thấp để phát nhanh
  3. 3Player tải segment tiếp theo, tăng hoặc giảm mức theo băng thông đo được
  4. 4CDN miss thì lấy từ origin shield, rồi mới tới object storage

Nguồn: Grokking Modern System Design · ch.26 Design YouTube ↗ · bài "The Reality Is More Complicated" · mục "Adaptive streaming" (chọn chunk theo băng thông và buffer), mục "Deliver" ; Grokking Modern System Design · ch.11 Content Delivery Network (CDN) ↗ · mục "Multi-tier CDN architecture" ; Chưa có nguồn trong thư viện: độ dài segment 4 s và URL ký hết hạn

4) Người dùng có thể xem thông tin video và số lượt xem

Metadata đọc nhiều hơn ghi rất nhiều nên đi qua cache. Lượt xem được ghi thành sự kiện và cộng dồn bất đồng bộ, số hiển thị có thể trễ vài phút.

  1. 1API đọc metadata từ Redis, miss thì đọc DB replica rồi ghi lại cache
  2. 2Player gửi heartbeat /views mỗi 30 s
  3. 3Heartbeat vào bộ đếm (Redis ở mức vừa, Kafka + stream job ở mức lớn)
  4. 4Bộ đếm định kỳ ghi tổng vào DB, cache metadata được làm mới

Nguồn: Grokking Modern System Design · ch.26 Design YouTube ↗ · bài "Evaluation of YouTube's Design" · mục "Distributed cache", "Consistency" (chấp nhận nhất quán yếu cho số liệu không quan trọng) ; Grokking Modern System Design · ch.16 Distributed Cache ↗ · mục "Writing policies" ; Grokking Modern System Design · ch.24 Sharded Counters ↗ · mục "High-level solution sketch"

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
10.000 DAU, 100 video upload/ngày
Tải đỉnh
≈ 417 người xem đồng thời, ≈ 0,63 Gbps ra CDN
Chi phí ước lượng
≈ 1.000–2.000 USD/tháng (ước lượng, hơn 70% là băng thông CDN)

Nền tảng video nội bộ hoặc khóa học online. Upload thẳng lên object storage, 1-2 worker ffmpeg, phát HLS qua CDN thuê ngoài. API và Postgres gần như không chịu tải; tiền chủ yếu nằm ở băng thông.

Người dùngNgười xem/đăngCDNCDNLưu trữS3 / GCSDịch vụAPI Node.jsDatabasePostgreSQLWorkerWorker ffmpegHLS + APIPUT presigned/v1/*cache missmetadata, joblấy jobđọc raw, ghi HLS

Ước lượng

  • Xem: 10.000 DAU × 20 phút = 200.000 phút/ngày; bitrate TB 1,5 Mbps → 1,5 × 60 / 8 = 11,25 MB/phút
  • Băng thông: 200.000 × 11,25 MB = 2,25 TB/ngày × 30 ≈ 67,5 TB/tháng ra CDN
  • Đồng thời: 200.000 / 1.440 phút ≈ 139 người, đỉnh ×3 ≈ 417 × 1,5 Mbps ≈ 625 Mbps
  • Upload: 100 video × 10 phút = 1.000 phút/ngày; file gốc 8 Mbps = 60 MB/phút → 60 GB/ngày, giữ 30 ngày = 1,8 TB
  • Ladder 3 mức 720p 2,5 + 480p 1 + 360p 0,6 = 4,1 Mbps → 30,75 MB/phút × 1.000 ≈ 30,75 GB/ngày ≈ 11,2 TB/năm
  • Transcode ≈ 1× thời gian thực trên 4 vCPU: 1.000 / 1.440 ≈ 0,7 worker → 1-2 worker; API: 10.000 × 30 / 86.400 ≈ 3,5 req/s, đỉnh ×10 = 35 req/s → 2 instance

Nguồn con số: Grokking Modern System Design · ch.26 Design YouTube ↗ · bài "Requirements of YouTube's Design" · mục "Storage estimation", "Bandwidth estimation", "Number of servers estimation" ; Grokking Modern System Design · ch.5 Back-of-the-envelope Calculations ↗ · mục "Requests estimation", "Storage requirements", "Bandwidth requirements" ; Quy tắc ước lượng của khoá học · /hoc/chon-server (bitrate từng mức, transcode ≈ 1× thời gian thực, 600 req/s mỗi instance, Redis ×1,3 là giả định của khoá học)

Bảng hạ tầng

Thành phầnChọnSố lượngCấu hìnhVì sao
CDNBunny CDN / CloudFront1Cache segment HLS, TTL dài67,5 TB/tháng nếu đi từ origin sẽ đắt và chậm; segment không đổi nên hit rate rất cao.
APINode.js (NestJS) trên Cloud Run22 vCPU · 4 GB RAM35 req/s đỉnh chỉ cần 1 instance, chạy 2 để dự phòng; file video không đi qua API.
DatabasePostgreSQL managed12 vCPU · 8 GB · 50 GB SSDMetadata vài chục nghìn video là rất nhỏ; bảng job dùng luôn làm hàng đợi (pg-boss).
Object storageS3 / GCS1 bucket raw + 1 bucket HLS1,8 TB raw + 11,2 TB/năm HLSMultipart upload có sẵn, độ bền 11 số 9, không bao giờ lưu video trong DB.
Transcode workerffmpeg trong container (Cloud Run Jobs)1-24 vCPU · 8 GB1.000 phút video/ngày chỉ dùng khoảng 0,7 worker, scale về 0 khi không có job.
Đếm lượt xemUPDATE trực tiếp trong Postgres0 (dùng DB)Vài lượt/s10.000 × 4 view / 86.400 ≈ 0,5 lượt/s, chưa cần hệ thống riêng.
Thay đổi so với mức trước
  • Điểm xuất phát: upload thẳng lên storage, API chỉ ký URL
  • Ladder 3 mức tối đa 720p để giữ chi phí lưu trữ và băng thông thấp
  • Postgres làm luôn hàng đợi job transcode
Nút thắt tiếp theo

Khi upload tăng lên hàng nghìn video/ngày, encode tuần tự cả file trên 1-2 worker tạo hàng đợi dài nhiều giờ. Hóa đơn CDN cũng tăng tuyến tính theo phút xem.

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ùng10.000 DAU, 100 video upload/ngày1 triệu DAU, 2.000 video upload/ngày50 triệu DAU, 100.000 video upload/ngày
Chi phí≈ 1.000–2.000 USD/tháng (ước lượng, hơn 70% là băng thông CDN)≈ 40.000–80.000 USD/tháng (ước lượng, hơn 80% là băng thông CDN)Hàng triệu USD/tháng (ước lượng); băng thông vẫn là khoản lớn nhất dù đã có cache trong ISP
CDN
Bunny CDN / CloudFront
1 × Cache segment HLS, TTL dài
CloudFront / Akamai + origin shield
1 × 62,5 Gbps đỉnh, URL ký
Chưa cần
API
Node.js (NestJS) trên Cloud Run
2 × 2 vCPU · 4 GB RAM
Node.js (NestJS), container autoscale
3-6 (autoscale) × 2 vCPU · 4 GB RAM
Node.js trên Kubernetes, 3 region
87 pod (HPA) × 2 vCPU · 4 GB mỗi pod
Database
PostgreSQL managed
1 × 2 vCPU · 8 GB · 50 GB SSD
PostgreSQL managed (HA)
1 primary + 2 read replica × 4 vCPU · 16 GB · 200 GB SSD
MySQL + Vitess, shard theo video_id
4 shard × (1 primary + 2 replica) × 8 vCPU · 32 GB · 1 TB SSD
Object storage
S3 / GCS
1 bucket raw + 1 bucket HLS × 1,8 TB raw + 11,2 TB/năm HLS
S3 + lifecycle
2 bucket × 399 TB/năm HLS; raw 30 ngày ≈ 28,8 TB
Chưa cần
Transcode worker
ffmpeg trong container (Cloud Run Jobs)
1-2 × 4 vCPU · 8 GB
ffmpeg trên EC2 Spot / GKE spot
11-22 (autoscale) × 8 vCPU · 16 GB
ffmpeg (H.264 + AV1 cho video hot) trên spot
556-1.100 (autoscale) × 8 vCPU · 16 GB
Đếm lượt xem
UPDATE trực tiếp trong Postgres
0 (dùng DB) × Vài lượt/s
Chưa cầnChưa cần
RedisChưa cần
ElastiCache / Memorystore
1 primary + 1 replica × 2 vCPU · 8 GB
Chưa cần
OrchestratorChưa cần
AWS Step Functions / Temporal
1 × Managed
Temporal cluster
1 cluster (3 region) × History shard trên Cassandra/MySQL
QueueChưa cần
Amazon SQS
1 × Có dead-letter queue
Chưa cần
Edge cacheChưa cầnChưa cần
Cache server đặt trong ISP/IXP + CDN thuê cho vùng thiếu
≈ 150 máy cache × 40 Gbps mỗi máy, SSD cho video hot
Redis ClusterChưa cầnChưa cần
ElastiCache Redis Cluster
6 shard × (1 primary + 1 replica) × 2 vCPU · 8 GB mỗi node
Blob storageChưa cầnChưa cần
S3 / GCS nhiều lớp (Standard, IA, Archive)
Đa region × 20,6 PB/năm HLS + raw
KafkaChưa cầnChưa cần
MSK / Confluent
6 broker × Topic view-events 24 partition, RF 3
Stream jobChưa cầnChưa cần
Apache Flink
1 cluster, 12 task slot × 4 vCPU · 16 GB mỗi TaskManager

Đà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) Upload file 2 GB qua mạng di động chập chờn thế nào để không phải làm lại từ đầu?

Cách chưa tốt
Một request POST multipart/form-data lên API server

API nhận toàn bộ file rồi mới ghi lên storage. Mất mạng ở 90% là mất hết, và mỗi upload chiếm một kết nối, bộ nhớ, băng thông của API trong nhiều phút.

Đánh đổi: Dễ viết nhất, chỉ ổn cho file vài MB.

Nguồn: Grokking Modern System Design · ch.20 Blob Store ↗ · bài "Design of a Blob Store" · mục "Write a blob" (blob lớn chia thành chunk) ; Chưa có nguồn trong thư viện: hậu quả giữ kết nối nhiều phút trên API server là suy luận của khoá học

Cách tốt
Chia phần ở client, API nhận từng phần và ghép lại

Client cắt file 8 MB một phần, gửi kèm số thứ tự. API ghi từng phần ra đĩa tạm, lưu danh sách phần đã nhận để client hỏi và gửi tiếp.

Đánh đổi: Resume được nhưng dữ liệu vẫn chảy qua API, phải tự lo ghép file và dọn phần thừa.

Nguồn: Grokking Modern System Design · ch.20 Blob Store ↗ · mục "Write a blob" và bài "Design Considerations of a Blob Store" · mục "Blob metadata" (quản lý danh sách chunk)

Cách tốt nhất
Multipart upload trực tiếp lên object storage bằng presigned URL

API tạo multipart upload trên S3/GCS và ký URL cho từng phần. Client đẩy song song 3-4 phần thẳng lên storage, file 2 GB là 256 phần 8 MB. Trạng thái phần nào đã xong do storage giữ (ListParts), API chỉ lưu UploadSession. Thêm checksum từng phần và lifecycle rule tự hủy upload dở sau 7 ngày. Có thể kiểm tra hash file gốc để bỏ qua video trùng.

Đánh đổi: URL ký phải hết hạn ngắn và giới hạn kích thước; cần cấu hình CORS và cơ chế dọn upload bỏ dở.

Nguồn: Grokking Modern System Design · ch.20 Blob Store ↗ · mục "Write a blob" ; Grokking Modern System Design · ch.26 Design YouTube ↗ · bài "Evaluation of YouTube's Design" · mục "Duplicate videos" (chống video trùng) ; Chưa có nguồn trong thư viện: presigned URL, multipart upload, ListParts, lifecycle rule hủy upload dở

2) Pipeline transcode được tổ chức thế nào để video 1 giờ không phải chờ 1 giờ?

Cách chưa tốt
Một worker chạy ffmpeg cho cả file, lần lượt từng độ phân giải

Một máy encode toàn bộ video ở 1080p, rồi 720p, rồi 480p. Video 60 phút với 4 mức mất vài giờ, worker chết giữa chừng thì làm lại từ đầu.

Đánh đổi: Đơn giản, chấp nhận được khi chỉ có vài chục video ngắn mỗi ngày.

Nguồn: Grokking Modern System Design · ch.26 Design YouTube ↗ · bài "The Reality Is More Complicated" · mục "Encode" (một video phải encode ra nhiều định dạng) ; Chưa có nguồn trong thư viện: thời gian encode vài giờ là ước lượng của khoá học

Cách tốt
Queue job theo từng mức độ phân giải

Upload xong thì đẩy 4 job (1080p, 720p, 480p, 360p) vào queue, 4 worker chạy song song. Mức thấp xong trước nên video có thể phát sớm ở 360p.

Đánh đổi: Nhanh hơn 4 lần nhưng mức 1080p vẫn phải encode cả file trên một máy.

Nguồn: Grokking Modern System Design · ch.17 Distributed Messaging Queue ↗ · bài "System Design: The Distributed Messaging Queue" (phân tách producer/consumer) ; Grokking Modern System Design · ch.23 Distributed Task Scheduler ↗ · mục "Queueing"

Cách tốt nhất
DAG job: cắt đoạn theo keyframe, encode song song đoạn × mức, rồi package

Orchestrator (Temporal, Step Functions, hay scheduler như Grokking ch.23) dựng DAG: probe, cắt video thành đoạn 10 s tại keyframe, mỗi cặp (đoạn, mức) là một job, sau đó ghép và đóng gói HLS/DASH, song song với thumbnail và audio. Video 10 phút có 60 đoạn × 4 mức = 240 job, chạy trên hàng trăm worker spot nên xong trong vài phút. Ladder chọn theo nội dung (per-title): video tĩnh dùng bitrate thấp hơn, giảm lưu trữ và băng thông. Job idempotent theo khóa (video, đoạn, mức) nên worker spot bị thu hồi chỉ chạy lại job đó.

Đánh đổi: Thêm orchestrator phải vận hành; cắt đoạn phải đúng keyframe và đồng bộ audio, nếu không sẽ có vết nối khi phát.

Nguồn: Grokking Modern System Design · ch.26 Design YouTube ↗ · bài "The Reality Is More Complicated" · mục "Encode" (encode theo từng segment/shot, segment ít chuyển động nén mạnh hơn) ; Grokking Modern System Design · ch.23 Distributed Task Scheduler ↗ · mục "Task idempotency", "Resource capacity optimization", "Execution cap" ; Chưa có nguồn trong thư viện: Temporal/Step Functions, cắt tại keyframe

3) Phát video mượt cho người xem ở mạng 3G lẫn cáp quang, với chi phí băng thông chấp nhận được?

Cách chưa tốt
Phát file MP4 duy nhất từ server của mình

Người dùng tải một file 1080p qua HTTP range request từ origin. Mạng yếu thì khựng liên tục, mọi byte đều đi từ data center nên băng thông origin bùng nổ.

Đánh đổi: Không cần xử lý gì thêm nhưng trải nghiệm kém và tốn kém nhất.

Nguồn: Grokking Modern System Design · ch.26 Design YouTube ↗ · bài "The Reality Is More Complicated" · mục "Deploy" (phát thẳng từ data center làm nặng origin) ; Grokking Modern System Design · ch.11 Content Delivery Network (CDN) ↗ · mục "Problem statement"

Cách tốt
Nhiều file MP4 theo độ phân giải, đặt sau CDN, người dùng tự chọn

Có bản 360p, 720p, 1080p trên CDN. Người dùng chọn chất lượng bằng tay. CDN gánh phần lớn lưu lượng.

Đánh đổi: Không tự thích nghi khi mạng đổi giữa chừng; đổi chất lượng phải tải lại từ đầu file.

Nguồn: Grokking Modern System Design · ch.26 Design YouTube ↗ · bài "Design of YouTube" · mục "High-level design" (video phổ biến đẩy lên CDN) ; Grokking Modern System Design · ch.11 Content Delivery Network (CDN) ↗ · mục "Content caching strategies in CDN"

Cách tốt nhất
HLS/DASH với segment 4 s, ABR ở player, CDN nhiều tầng

Mỗi mức là chuỗi segment 4 s, manifest liệt kê các mức. Player (hls.js, ExoPlayer) chọn mức cho từng segment theo băng thông và buffer, bắt đầu thấp để phát nhanh. Segment không bao giờ đổi nên cache TTL dài ở CDN, có origin shield gom miss. Ở quy mô rất lớn, như Grokking ch.26 mô tả, video phổ biến được đẩy sẵn vào cache đặt trong ISP/IXP, video ít xem phục vụ từ colocation site hoặc data center gốc. URL được ký và hết hạn để chặn link lậu.

Đánh đổi: Lưu nhiều bản của một video tốn storage; segment ngắn thì đổi mức nhanh nhưng nhiều request hơn.

Nguồn: Grokking Modern System Design · ch.26 Design YouTube ↗ · bài "The Reality Is More Complicated" · mục "Adaptive streaming", "Deploy" (đặt chunk video phổ biến ở ISP/IXP), "Deliver" (video ít xem phục vụ từ colocation site hoặc data center) ; Grokking Modern System Design · ch.11 Content Delivery Network (CDN) ↗ · mục "Multi-tier CDN architecture", "Time-to-live (TTL)" ; Chưa có nguồn trong thư viện: hls.js/ExoPlayer, URL ký

4) Đếm lượt xem cho hàng trăm triệu lượt mỗi ngày, và dùng dữ liệu đó để giảm chi phí lưu trữ thế nào?

Cách chưa tốt
UPDATE videos SET views = views + 1 mỗi lượt xem, giữ mọi bản mãi ở storage chuẩn

Video viral nhận hàng nghìn lượt/s, tất cả tranh khóa trên cùng một hàng. Đồng thời mọi video, kể cả video 0 lượt xem trong 2 năm, vẫn nằm đủ 5 mức ở lớp lưu trữ đắt nhất.

Đánh đổi: Đúng với vài nghìn lượt/ngày; ở quy mô lớn thì DB nghẽn và hóa đơn storage tăng tuyến tính mãi.

Nguồn: Grokking Modern System Design · ch.24 Sharded Counters ↗ · mục "Problem statement" (một bộ đếm bị ghi dồn dập gây tranh chấp khóa) ; Grokking Modern System Design · ch.26 Design YouTube ↗ · bài "The Reality Is More Complicated" · mục "Deploy" (Flash servers cho video phổ biến, storage servers cho video ít xem)

Cách tốt
Redis INCR làm bộ đệm, flush định kỳ; lifecycle storage theo tuổi file

Mỗi heartbeat là một INCR trên Redis, job 30 s một lần cộng dồn vào DB. Storage có rule: file gốc sau 30 ngày chuyển sang lớp archive, segment sau 1 năm sang lớp truy cập ít.

Đánh đổi: Không chống được view ảo, mất Redis là mất vài chục giây số đếm; theo tuổi thì video cũ nhưng vẫn hot cũng bị chuyển lớp.

Nguồn: Redis in Action ↗ · ch.5 · §5.2.1 Storing counters in Redis ; Chưa có nguồn trong thư viện: lifecycle rule theo tuổi file của object storage

Cách tốt nhất
Sự kiện xem qua Kafka, stream job gom và lọc, popularity điều khiển tầng lưu trữ

Heartbeat vào Kafka partition theo video_id. Job Flink gom theo cửa sổ 1 phút, bỏ trùng theo session, lọc bot, rồi ghi tổng vào DB; video cực hot có thể dùng sharded counter (Grokking ch.24). Cùng dữ liệu này tính độ phổ biến: video hot được đẩy trước lên CDN/ISP cache, video ít xem chuyển sang lớp lưu trữ rẻ, video gần như không ai xem chỉ giữ 2-3 mức thấp và encode lại mức cao khi cần.

Đánh đổi: Số lượt xem hiển thị trễ 1-2 phút và hệ thống phức tạp hơn; encode lại theo yêu cầu làm lượt xem đầu tiên ở mức cao chậm hơn.

Nguồn: Grokking Modern System Design · ch.24 Sharded Counters ↗ · mục "Detailed design", "Burst of writes requests" ; Grokking Modern System Design · ch.18 Pub-sub ↗ · bài "Design of a Pub-sub System" (topic chia partition) ; Grokking Modern System Design · ch.26 Design YouTube ↗ · bài "The Reality Is More Complicated" · mục "Deploy" (độ phổ biến quyết định nơi đặt nội dung) ; Chưa có nguồn trong thư viện: Kafka, Flink và cửa sổ 1 phút (thư viện không có sách stream processing)

Trong phỏng vấn

Nên nói
  • “File video không bao giờ đi qua API server: client upload nhiều phần thẳng lên object storage bằng presigned URL.”
  • “Transcode là DAG job: cắt đoạn theo keyframe, encode song song đoạn × mức, mỗi job idempotent để chạy trên máy spot.”
  • “Tôi phát bằng HLS/DASH với segment vài giây để player tự đổi chất lượng, và segment bất biến nên cache rất tốt ở CDN.”
  • “Ở quy mô lớn băng thông là chi phí số một, nên tôi dùng độ phổ biến để quyết định video nào nằm ở edge cache và lớp lưu trữ nào.”
Bẫy hay gặp
  • Cho file vài GB đi qua API hoặc lưu video trong database.
  • Encode cả video trên một máy, làm lại từ đầu khi máy chết.
  • Quên tính băng thông phát ra, chỉ tính dung lượng lưu trữ.
  • Cộng lượt xem bằng UPDATE trực tiếp trên một hàng cho video viral.

Đọc thêm

Xử lý file lớn
Cách upload, lưu và phân phối file lớn mà không để chúng đi qua app server: presigned URL, multipart và resumable upload, chia chunk và dedup theo hash, CDN, signed URL khi tải về, pipeline hậu xử lý bằng sự kiện, tách metadata khỏi blob store.
Tác vụ chạy lâu
Cách xử lý việc mất vài giây tới vài giờ mà không giữ request: nhận job, trả 202 kèm job id, worker xử lý, báo kết quả qua polling, webhook hoặc SSE. Kèm hàng đợi, retry có backoff và jitter, DLQ, idempotency, heartbeat, lập lịch, workflow engine và cách tính số worker.
Caching
Đặt cache ở đâu (client, CDN, app, Redis), các chiến lược đọc ghi cache-aside, write-through, write-back, write-around, eviction LRU/LFU/TTL, invalidation, chống stampede và hot key, cách tính RAM Redis và toán hit ratio.
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.
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.
Scale đọc
Phục vụ lượng đọc gấp hàng trăm lần lượng ghi: index, read replica và replication lag, cache nhiều tầng, CDN, read model denormalized theo CQRS, cùng cách xử lý cache stampede và hot key.

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