🎮Bộ 2 · System Design ArenaTập 16 / 17

[SYSTEM DESIGN TẬP 16] CÁI BẪY CDN NGỐN $810 BĂNG THÔNG, KHO LẠNH 200TB LRU + SYMLINK 0-BYTE VÀ ĐƯỜNG CONG ZIPF KHI CFO SOI HÓA ĐƠN! 🧊📈

Cái bẫy CDN ngốn $810 băng thông ở Màn 25, kiến trúc 3 tầng R2 -> NVMe SSD -> Cold HDD Symlink 0-Byte bằng LRU relatime và đường cong Zipf ở Màn 26 ($140.25 ★★★)
🎨 Cái bẫy CDN ngốn $810 băng thông ở Màn 25, kiến trúc 3 tầng R2 -> NVMe SSD -> Cold HDD Symlink 0-Byte bằng LRU relatime và đường cong Zipf ở Màn 26 ($140.25 ★★★) 🔍 Phóng to

Sảnh đấu System Design Arena vừa dứt cơn bão Flash Sale ở Màn 13 thì chuông báo động của ban giám khảo lại vang rền.

Lần này, cánh cửa liên hoàn Màn 25 (object_storage_lifecycle — Ảnh cũ tốn tiền) và Màn 26 (cost_per_request_audit — CFO soi hóa đơn) đồng loạt mở toang. Không còn những đợt sóng thần traffic chục nghìn req/s đè bẹp CPU, hai màn chơi này đưa ra bài toán hóc hiểm nhất mà bất kỳ kỹ sư trưởng nào cũng từng toát mồ hôi khi ngồi đối diện Giám đốc Tài chính: Nghệ thuật tối ưu hóa chi phí (FinOps) và giải phẫu từng xu trên mỗi triệu request.

Thằng Đ kéo ghế ngồi sát lại màn hình bàn cờ, mắt sáng rực:

  • "Anh Kido! Màn 25 đề bài cho app chia sẻ ảnh đang ôm 200 TB trên Object Storage, hóa đơn ngốn tới $4,600/tháng. Trong đó 10 TB ảnh mới nhận 300 lượt xem/s, còn 190 TB ảnh cũ chỉ có 5 lượt xem/s nhưng ai mở vẫn phải thấy ngay. Dễ ợt, cứ cắm một cục CDN đằng trước để cache ảnh, ngân sách cho tận $2,420.00, lo gì thiếu tiền!"

Nói đoạn, tay nó thoăn thoắt kéo một hộp CDN Tier S ($20/tháng) đặt ngay ngắn giữa Users và Object Storage.


1. CÁI BẪY CDN NGỐN $810 BĂNG THÔNG VÀ NGHỊCH LÝ CLOUDFLARE R2 CHO 190TB ẢNH LẠNH 💸🧊

Tôi chưa kịp cản thì Đ đã đập tay cái bép vào nút ▷ Chạy.

Chỉ sau vài giây mô phỏng, bảng điểm đỏ rực hiện lên:

[FAILED] TỔNG CHI PHÍ: $2,665.42/tháng (VƯỢT TRẦN NGÂN SÁCH $2,420.00!)
Phí cố định CDN: $20.00
Phí băng thông CDN Egress: +$810.56/tháng! (Bẫy lưu lượng 305 req/s!)

Thằng Đ ôm đầu rên rỉ:

  • "Á á! Cục CDN trên bảng ghi có $20/tháng, sao lúc bấm chạy nó cắn thêm hơn $810 tiền băng thông của em thế này?!"

Tôi gõ cây bút lên bàn:

  • *"Mày tính thử xem: $300\text{ req/s} \times 600\text{ KB} \times 2.592.000\text{ giây} = \mathbf{466,56\text{ TB dữ liệu xuất xưởng}}! Mày nhìn lại thông số của cục Object Storage Tier S đi! Bản thân nó chịu được tới 5.000 req/s và độ trễ phản hồi chỉ vỏn vẹn 25 ms — quá thừa thãi so với SLA 300 ms và lưu lượng 305 req/s của đề bài! Người ta chỉ dùng CDN khi máy chủ gốc (origin) quá tải hoặc người dùng rải rác xuyên lục địa. Ở đây cục Object Storage đã nhanh như điện giật, mày cắm thêm CDN chẳng khác nào tự dựng trạm thu phí băng thông để cúng tiền cho nhà mạng! Xóa ngay cục CDN đi, nối thẳng Users vào Object Storage!"*
flowchart LR
    subgraph Trap["❌ Cái bẫy CDN ($2,665.42 - Thất bại)"]
        U1["Users (305 req/s)"] --> CDN["CDN Tier S<br/>(Cắn thêm +$810 Egress!)"] --> OBJ1["Object Storage"]
    end
    subgraph Optimal["✅ Kiến trúc tinh gọn 3 Sao ($2,103.82)"]
        U2["Users (305 req/s)"] -->|"file_read (trực tiếp 25ms < 300ms SLA)"| OBJ2["Object Storage<br/>(Lifecycle: Glacier IR)"]
    end
  • "Thế còn 190 TB ảnh cũ thì sao anh? Chuyển sang Deep Archive cho siêu rẻ nhé?" — Đ hỏi tiếp.
  • *"Nếu mày chọn Deep Archive, giá lưu trữ rẻ thật nhưng khi người dùng bấm xem ảnh kỷ niệm 3 năm trước, hệ thống phải mất 3 đến 5 tiếng để rã đông file từ băng từ (tape)! Khách đợi 3 tiếng thì SLA 300 ms của mày nát bét. Bí mật ở đây là Glacier Instant Retrieval (glacier_ir)!"*

Tôi chỉ cho Đ từng dòng chi phí khớp tuyệt đối với kỷ lục thế giới $2,103.82/tháng:

  1. Base Object Storage: $20.00
  2. 10 TB ảnh nóng (Standard $0.023/GB): $10.000 \times $0.023 = \mathbf{$230.00}$
  3. 190 TB ảnh lạnh (Glacier IR $0.004/GB): $190.000 \times $0.004 = \mathbf{$760.00}$ (giảm 83% tiền đĩa!)
  4. Phí GET requests (305 req/s): $\mathbf{$316.22}$
  5. Phí đọc dữ liệu lạnh Glacier IR (5 req/s × 600 KB): $\mathbf{$777.60}$ $$\text{Tổng cộng} = $20 + $230 + $760 + $316.22 + $777.60 = \mathbf{$2,103.82/\text{tháng}} \text{ (3 Sao rực rỡ!)}$$

Thằng Đ gật gù lia lịa, nhưng bỗng nhíu mày:

  • "Khoan đã anh Kido! Ngoài đời anh em mình hay dùng Cloudflare R2 vì nó quảng cáo miễn phí 100% băng thông tải ra ($0 Egress). Sao bài toán 200 TB này không thấy ai mang R2 ra giải?"

Tôi bật cười trước sự tinh ý của nó:

  • *"Đó chính là cái bẫy FinOps kinh điển mà nhiều kỹ sư trẻ mắc phải! Cloudflare R2 cực kỳ bá đạo cho 10 TB ảnh nóng vì nó không thu một xu tiền Egress. Nhưng với 190 TB ảnh cũ mốc meo cả tháng chỉ có vài người ngó, R2 Standard tính tiền thuê ổ cứng tới $0.015/GB = $2,850/tháng, R2 Infrequent cũng $0.010/GB = $1,900/tháng! Nó đắt gấp 3.75 lần so với con số $760/tháng của Glacier Instant Retrieval ($0.004/GB)! Nếu ôm 190 TB ảnh lạnh trên R2, công ty sẽ sạt nghiệp vì tiền thuê đĩa cứng mỗi tháng!"*

2. PHÁP SƯ TỰ DỰNG KHO 3 TẦNG $0/THÁNG: LRU RELATIME, NÉN LẠNH WEBP/AVIF VÀ SYMLINK 0-BYTE 🛠️🐧

Thằng Đ phấn khích:

  • "Thế nếu sau này mình tự làm dự án riêng hoặc xây hạ tầng cho công ty thương mại điện tử, không muốn cúng hàng nghìn đô cho AWS thì tự dựng kiểu gì hả anh?"

  • *"Nhìn vào cụm máy chủ nội bộ của chúng ta: Ta có Cloudflare R2 Free Tier (10 GB), 1 ổ NVMe SSD (/media/hot) và 1 ổ cơ HDD dung lượng lớn (/media/cold), kèm container imgproxy. Ta tạo ra một Thác nước lưu trữ 3 tầng (Storage Waterfall) chạy mượt như nhung!"*

flowchart TD
    Upload["📤 Upload ảnh sản phẩm mới"] --> Guard{"🛡️ R2 Budget Guard<br/>(Đang dùng < 8.5 GiB?)"}
    Guard -->|"Còn trống"| T1["☁️ TẦNG 1: Cloudflare R2 Free Tier<br/>(Ảnh nóng < 90 ngày · $0.00/tháng · $0 Egress)"]
    Guard -->|"Đầy > 8.5 GiB hoặc Hết slot"| T2["⚡ TẦNG 2: Local NVMe SSD (/media/hot)<br/>(Ảnh ấm · Đọc cực nhanh < 5ms)"]
    
    T1 -.->|"Sau 90 ngày (hoặc chạm ngưỡng 8.5 GiB)<br/>Rút về NVMe SSD để giữ R2 luôn $0"| T2
    T2 -.->|"Sau 365 ngày không ai xem (LRU atime):<br/>1. imgproxy nén sang @webp / @avif (giảm 80%)<br/>2. Cất xuống Cold HDD (/media/cold)<br/>3. Để lại Relative Symlink 0-Byte trên SSD!"| T3["🧊 TẦNG 3: Cold HDD Storage (/media/cold)<br/>(Kho Lạnh vĩnh viễn · Tiết kiệm đĩa tối đa)"]

Tôi mở terminal, gõ một dòng giải thích cho Đ hai bí kíp làm nên sự khác biệt giữa dân chuyên nghiệp và tay mơ:

A. Cạm bẫy ngây thơ của ngày đăng (createdAt / FIFO) và sự cứu rỗi của LRU relatime

  • *"Nhiều hệ thống viết script dọn kho ảnh rất ngây thơ: cứ thấy ảnh có createdAt > 90 ngày là đá xuống ổ HDD lạnh. Kết quả là một sản phẩm Màu Vẽ Best-Seller đăng từ 2 năm trước, ngày nào cũng có hàng trăm khách click xem, bị văng xuống ổ đĩa cơ quay rọt rẹt làm web tải chậm rì! Trong kiến trúc của ta, script quét vòng đời dựa trên LRU (lastAccessedAt / Linux atime) kết hợp LFU (accessCount30d): $$\text{idleDays} = \frac{\text{now} - \max(\text{mtime}, \text{atime})}{86.400\text{ giây}}$$ Trên Linux ext4/xfs, cơ chế relatime tự động cập nhật atime khi có người đọc file mà không hề gây write amplification. Ảnh sản phẩm Best-Seller dù đăng 2 năm trước nhưng hôm qua có khách xem thì idleDays = 1 < 90 ngày, nó vĩnh viễn nằm lại trên NVMe SSD! Chỉ khi nào một tấm ảnh bị bỏ xó tròn 365 ngày và accessCount30d === 0, nó mới bị coi là ảnh lạnh!"*

B. Tuyệt kỹ "Symlink 0-Byte" — Giải phóng 100% dung lượng SSD mà URL không bao giờ vỡ

  • *"Khi tấm ảnh /media/hot/products/art-brush-01.jpg nặng 3 MB trên SSD chính thức trở thành ảnh lạnh, tiến trình Daemon sẽ:
  1. Bắn qua imgproxy nén sang @webp (hoặc @avif) ép dung lượng từ 3 MB xuống còn 400 KB (giảm 85%).
  2. Ghi file 400 KB đó xuống ổ HDD cơ học /media/cold/products/art-brush-01.webp.
  3. Xóa file 3 MB trên SSD, nhưng tạo ngay một Relative Symlink (tốn đúng 0 byte đĩa):
ln -s ../../cold/products/art-brush-01.webp /media/hot/products/art-brush-01.jpg
  1. Kỳ tích xảy ra: Ổ SSD giải phóng 100% chỗ trống. Database và Reverse Proxy giữ nguyên URL gốc 100%. Khi khách bấm vào link ảnh cũ, Linux tự trượt theo symlink sang HDD đọc ảnh trong < 50ms. Zero 404, Zero gián đoạn!"*

3. MÀN 26: CFO SOI HÓA ĐƠN VÀ ĐƯỜNG CONG ZIPF THẦN THÁNH 📈🧮

Đang say sưa thì Đ nhìn sang bảng đề bài Màn 26 (cost_per_request_audit — CFO soi hóa đơn):

  • "Ối anh ơi, Màn 26 này căng não hơn nhiều! Sau chiến dịch cao điểm, các ông dev nhà mình nâng toàn bộ node lên Tier XL cho chắc ăn rồi quên béng không hạ xuống. Giám đốc Tài chính (CFO) cầm bảng chi phí gõ bàn đòi hạ Cost mỗi 1 triệu request xuống dưới $0.049, tổng ngân sách không quá $165.00/tháng!"

Hệ thống phục vụ tải ổn định:

  • Đọc trang (page_read): $1.200\text{ req/s}$ (mỗi lượt đọc sinh ra 3 query: $1.200 \times 3 = \mathbf{3.600\text{ sql_read/s}}$).
  • Đăng bài (page_write): $\mathbf{150\text{ req/s}}$.
  • Tổng HTTP request: $1.200 + 150 = \mathbf{1.350\text{ req/s}}$ (tương đương $1.350 \times 2,592 = \mathbf{3.499,2\text{ triệu req/tháng}}$).

Thằng Đ lại cầm chuột:

  • "Để em nâng Read Replica lên gánh 3.600 sql_read/s nhé?"
  • *"Mày tính thử xem: 1 con Read Replica Tier L giá $50 gánh được 1.500 req/s. Để đỡ 3.600 sql_read/s, mày phải mua tới 3 con Tier L ($150/tháng). Riêng tiền DB phụ đã ngốn $150, cộng thêm Load Balancer, App Server và DB chính thì hóa đơn vọt lên hơn $250, vượt xa trần $165 của CFO! Không có In-memory Cache, không hệ thống nào sống nổi!"*
flowchart TD
    U["Users (1.350 HTTP req/s: 1.200 đọc + 150 ghi)"] -->|"http"| LB["Load Balancer Tier S ($40.25)"]
    LB -->|"http"| APP["App Server Cluster: 3 máy Tier S ($45.00)<br/>(Tải 90% steady-state an toàn)"]
    
    APP -->|"kv: 3.600 sql_read/s"| CACHE["In-memory Cache Tier L ($25.00)<br/>(Hit Rate 90% nhờ đường cong Zipf!)"]
    CACHE -.->|"sql_read (miss 10% = 360 req/s)"| RR["Read Replica Tier S ($15.00)<br/>(Gánh 360 req/s · Tải 72% êm ru!)"]
    
    APP -->|"sql: 150 sql_write/s"| DB["Relational DB Tier S ($15.00)<br/>(Chỉ gánh 150 write/s · Tải 30% mát rượi!)"]

A. Tại sao nạp thêm $10 mua Cache Tier L lại đánh bại 6.5 con Read Replica? (Định luật Zipf)

Tôi kéo Đ lại gần bảng vẽ:

  • *"Tại sao trong game và ngoài đời, nâng cấp Redis từ Tier S ($15) lên Tier L ($25) lại nhảy tỷ lệ hit từ 80% lên 90%? Đó không phải vì thuật toán khôn hơn, mà vì mày mua thêm RAM cho Working Set theo Định luật Zipf (Power-Law Distribution)! Trong thế giới thực, hành vi đọc tin tức của người dùng tuân theo hàm lũy thừa:
  • Top 1% bài viết hot nhất chỉ cần 1 GB RAM (Tier S · $15) đã chiếm tới 80% lượt đọc. Nhưng vì RAM chật, thuật toán LRU liên tục đuổi bài cũ ra ngoài, khiến 20% lượt đọc bị rớt xuống DB ($720\text{ req/s}$).
  • Thêm đúng $10 để lên Tier L ($25, 4 GB RAM): RAM đủ rộng để giữ trọn Top 5% bài viết ấm mà không bị LRU đá văng sớm! Tỷ lệ trúng vọt lên 90% Cache Hits, cắt giảm ngay một nửa lượng cache miss: từ $720$ tụt xuống chỉ còn $360\text{ sql_read/s}$! Một hộp Cache Tier L ($25) nuốt gọn $3.240\text{ sql_read/s}$, tương đương với sức mạnh của 6,5 con Read Replica Tier S ($97.50) cộng lại! Đó chính là Điểm Ngọt FinOps!"*

B. Tại sao 3 máy App Server ở 90% tải phẳng lại an toàn, nhưng DB lại cần Read Replica?

  • "Anh ơi, 3 con App Server Tier S gánh 1.350 req/s trên tổng công suất 1.500 req/s, tải chạm 90% liệu có toang vì nghẽn hàng đợi không?" — Đ lo lắng.

  • "Theo lý thuyết xếp hàng M/M/c, thời gian chờ trong hàng đợi tỷ lệ nghịch với $1 - \rho$. Nhưng đề bài ghi rõ: traffic ở màn này là hoàn toàn phẳng (steady) suốt cả chu kỳ mà không có đột biến (bursty spikes). Ở mức tải 90% phẳng, độ trễ xếp hàng chỉ cộng thêm ~35 ms. Nhờ 90% lượt đọc trả về từ RAM trong 1 ms, latency trung bình chỉ quanh quẩn 110–140 ms, quá dư dả so với SLA 300 ms! Đó chính là bản chất của Right-Sizing mà CFO yêu cầu!"

  • "Thế sao Database vẫn phải thêm Read Replica hả anh?"

  • *"Nhìn vào cái bẫy ở đáy sơ đồ kìa! Nếu mày không dùng Read Replica, con Relational DB Tier S (trần 500 req/s) sẽ phải nhận đồng thời: $$360\text{ sql_read (miss)} + 150\text{ sql_write} = \mathbf{510\text{ req/s}} > 500\text{ req/s} \text{ (Quá tải 102%!)}$$ Chỉ cần thêm đúng 1 con Read Replica Tier S ($15/tháng):

  • Nối cổng miss của Cache L sang Read Replica: nó gánh $360\text{ req/s}$ (mức tải $72%$).

  • Con DB chính Tier S chỉ còn gánh thuần túy $150\text{ write/s}$ (mức tải $30%$, mát rượi!)."*

Tôi nhấn nút tính toán chi phí tổng thể trên bàn cờ:

  • Load Balancer Tier S: $20.00 base + $20.25 traffic = $40.25
  • App Server: 3 máy Tier S = $45.00
  • In-memory Cache: 1 hộp Tier L (90% hit) = $25.00
  • Read Replica: 1 máy Tier S = $15.00
  • Relational DB: 1 hộp Tier S = $15.00 $$\text{Tổng chi phí} = $40.25 + $45.00 + $25.00 + $15.00 + $15.00 = \mathbf{$140.25/\text{tháng}}$$

Đơn giá trên mỗi triệu request: $$\text{Unit Cost} = \frac{$140.25}{3.499,2\text{ triệu request}} = \mathbf{$0.04008/\text{triệu request}} \ll \mathbf{$0.04900}$$

Thằng Đ nhìn ba ngôi sao vàng rực lóe sáng trên màn hình cùng dòng chữ Best Cost: $140.25 (Top 1 Bảng Xếp Hạng), nó thở phào một hơi dài rồi hí hoáy ghi ngay vào cuốn sổ tay bỏ túi:

"Bài học xương máu: Đừng bao giờ cắm CDN theo thói quen khi storage latency đã dưới 30ms; đừng dùng R2 để ôm cả trăm TB kho lạnh; và hãy để Định luật Zipf chỉ đường cho từng đồng RAM Cache trước khi nghĩ đến chuyện cắm thêm máy chủ cơ sở dữ liệu!" 🏆✨

🔬
Kido Engineering Blog • Deep-Dive Architecture

Giải Phẫu Kỹ Thuật & Kiến Trúc Thực Chiến Của Tập 216

blog.khanhdp.com ↗

Muốn xem chi tiết mã nguồn, bảng so sánh kiến trúc Sơ khởi ➔ Điểm nghẽn khi Scale ➔ Chuẩn Big Tech Production và phân tích kỹ thuật đằng sau câu chuyện ở tập này? Đọc ngay bài viết chuyên sâu: