Mở đầu — vì sao bài này quan trọng
Hãy tưởng tượng bạn vừa build xong một sản phẩm rất ổn: kiến trúc sạch, database chọn đúng, API thiết kế cẩn thận. Nhưng khi khách hàng ở Cần Thơ, Đà Nẵng hay tận Singapore mở trang lên, họ phải chờ 3-4 giây mới thấy ảnh hiện ra. Server của bạn đặt ở một datacenter tại Frankfurt hoặc us-east-1, và mỗi request phải đi vòng nửa vòng trái đất. Không có lỗi code nào cả — vấn đề nằm ở khoảng cách vật lý, ở tốc độ ánh sáng trong sợi cáp quang.
CDN (Content Delivery Network) là câu trả lời kiến trúc cho bài toán này, và nó không chỉ là "cache ảnh cho nhanh". Với vai trò người ra quyết định kiến trúc, bạn cần hiểu CDN như một tầng hạ tầng có ảnh hưởng đến latency, chi phí băng thông, khả năng chống chịu tấn công, và cả trải nghiệm người dùng cuối. Quan trọng hơn: bạn cần phân biệt được chiến lược cache cho static content (dễ) và dynamic content (khó và nhiều cạm bẫy). Đây chính là trọng tâm của bài học hôm nay.
Nhiều kỹ sư dừng lại ở mức "bật CDN cho thư mục /assets" rồi coi như xong. Nhưng một Principal Engineer nhìn CDN như một quyết định trải dài từ đường đi của một byte cho tới hóa đơn cloud cuối tháng. Chúng ta sẽ đi từ nền tảng đến chiến lược áp dụng thực tế.
Khái niệm cốt lõi
CDN là gì và hoạt động ra sao
CDN là một mạng lưới các máy chủ đặt phân tán khắp thế giới, gọi là PoP (Point of Presence) hoặc edge server. Thay vì mọi người dùng phải kết nối trực tiếp tới origin server (máy chủ gốc của bạn), họ kết nối tới edge server gần nhất về mặt địa lý. Edge server này giữ một bản sao (cache) của nội dung, và phục vụ trực tiếp cho người dùng.
Khi một request đến edge:
- Nếu edge đã có bản cache còn hợp lệ, nó trả về ngay — gọi là cache HIT.
- Nếu edge chưa có hoặc bản cache đã hết hạn, nó đi lấy từ origin (hoặc từ một tầng cache trung gian), lưu lại rồi mới trả về — gọi là cache MISS.
Bốn lý do chính để dùng CDN
1. Latency — đưa nội dung tới gần người dùng. Đây là lý do quan trọng nhất. Một gói tin đi từ TP.HCM tới us-east-1 (Virginia) round-trip mất khoảng 200-250ms. Nếu edge server đặt ngay tại Việt Nam hoặc Singapore, con số này rơi xuống 5-30ms. Với một trang web tải hàng chục file, sự khác biệt cộng dồn có thể là vài giây.
2. Bandwidth — giảm tải cho origin. Mỗi byte phục vụ tại edge là một byte origin không phải phục vụ. Điều này giảm chi phí egress (băng thông ra) từ cloud provider — vốn thường rất đắt — và giảm số lượng server origin bạn cần chạy.
3. Chống DDoS. CDN đứng chắn giữa Internet và origin của bạn. Với hạ tầng khổng lồ, các CDN lớn như Cloudflare hấp thụ được những đợt tấn công hàng terabit mà một origin đơn lẻ không bao giờ chịu nổi. Origin của bạn được "giấu" phía sau, kẻ tấn công chỉ nhìn thấy IP của CDN.
4. Caching layer. CDN là một tầng cache HTTP hoàn chỉnh, hoạt động dựa trên các HTTP header chuẩn. Nó không cần bạn viết logic cache riêng — chỉ cần cấu hình header đúng.
Static content — "chiến thắng dễ dàng"
Static asset là nội dung không đổi giữa các người dùng và không đổi theo thời gian (cho tới khi bạn deploy phiên bản mới): file CSS, JavaScript, ảnh, font, video, file PDF tải về. Đây là mảnh đất màu mỡ nhất cho CDN vì:
- Nội dung giống hệt nhau với mọi người dùng, nên cache một lần dùng cho tất cả.
- Có thể cache rất lâu (hàng tháng, hàng năm) một cách an toàn.
app.js, build tool tạo ra app.a3f9c2.js — chuỗi hash phản ánh nội dung file. Khi nội dung đổi, hash đổi, tên file đổi, và trình duyệt coi đó là file hoàn toàn mới. Nhờ vậy bạn có thể set header cực kỳ mạnh:Cache-Control: public, max-age=31536000, immutable
max-age=31536000 là một năm; immutable báo trình duyệt "đừng bao giờ hỏi lại". Vì tên file đổi mỗi lần nội dung đổi, không có nguy cơ phục vụ file cũ.
Dynamic content — nơi mọi thứ trở nên thú vị
Dynamic content là nội dung thay đổi theo người dùng, theo phiên, hoặc theo thời gian: trang cá nhân hóa, kết quả API, giá sản phẩm cập nhật liên tục, giỏ hàng. Theo bản năng, nhiều người nghĩ "cái này không cache được". Sai lầm. Có nhiều tầng chiến lược:
- TTL ngắn (micro-caching): Cache nội dung dynamic trong 1-10 giây. Nghe ngắn, nhưng với trang có 10.000 request/giây, cache 1 giây cũng giảm tải origin xuống chỉ còn 1 request/giây cho endpoint đó.
- Edge Side Includes (ESI) / stale-while-revalidate: Cache phần khung tĩnh, chỉ để lại phần cá nhân hóa tải riêng; hoặc phục vụ bản cũ trong khi âm thầm lấy bản mới.
- Dynamic acceleration: Ngay cả khi không cache được nội dung, CDN vẫn tăng tốc bằng cách giữ kết nối TLS "ấm" giữa edge và origin, tối ưu route mạng (private backbone), và dùng HTTP/2, HTTP/3.
- Edge compute: Chạy logic ngay tại edge (chủ đề của Bài 48) để cá nhân hóa mà không cần về origin.
Cache-Control (public/private, max-age, s-maxage, no-store), Vary (cache khác nhau theo header nào — ví dụ Vary: Accept-Encoding), và Cache-Control: private cho nội dung riêng tư tuyệt đối không được cache tại tầng chia sẻ.Tình huống thực tế
Ví dụ 1: Sàn thương mại điện tử Việt Nam trong đợt sale 12/12
Một công ty thương mại điện tử tại Việt Nam (gọi là ShopViet) chuẩn bị cho đợt sale 12/12. Origin của họ đặt tại AWS Singapore (ap-southeast-1). Ngày thường tải bình thường, nhưng đội ngũ lo lắng cho đỉnh lưu lượng — dự kiến gấp 40 lần.
Họ phân loại nội dung:
- Static (ảnh sản phẩm, CSS, JS, banner): chiếm tới 80% dung lượng byte truyền tải. Họ đặt sau CDN với fingerprint và
max-agemột năm. Kết quả: hit ratio 97%, nghĩa là origin gần như không phải phục vụ file ảnh nào trong ngày sale. - Trang danh mục sản phẩm (semi-dynamic): thay đổi nhưng không tức thời. Họ áp micro-caching với
s-maxage=30(cache tại CDN 30 giây) kèmstale-while-revalidate=60. Một endpoint danh mục trước đây nhận 8.000 req/giây về origin, sau khi bật micro-cache chỉ còn khoảng 30-40 req/giây. - Giỏ hàng, thanh toán (dynamic thuần): gắn
Cache-Control: private, no-store. Tuyệt đối không cache, vì cache nhầm giỏ hàng của người này cho người khác là thảm họa.
Ví dụ 2: Sự cố cache nhầm nội dung riêng tư của một startup fintech
Một startup fintech ở Đông Nam Á từng gặp sự cố nghiêm trọng. Họ bật CDN cho toàn bộ site để "tăng tốc". Trang dashboard trả về JSON chứa số dư tài khoản, nhưng response thiếu header Cache-Control: private. CDN mặc định cache response 200 OK theo URL. Kết quả: người dùng B mở dashboard và thấy số dư của người dùng A — vì cả hai gọi cùng URL /api/dashboard và CDN đã cache bản của A.
Nguyên nhân gốc: họ không phân biệt được đâu là nội dung có thể chia sẻ (shared cache) và đâu là nội dung riêng từng người. Cách khắc phục:
- Mọi response chứa dữ liệu người dùng phải có
Cache-Control: private, no-store. - Với API có auth, cấu hình CDN bypass cache khi có header
Authorizationhoặc cookie session. - Thiết lập nguyên tắc "default deny": mặc định không cache API, chỉ opt-in cache cho những endpoint đã kiểm chứng an toàn.
Ví dụ 3: Báo điện tử chống đỉnh traffic breaking news
Một tòa soạn báo điện tử tại Việt Nam thường gặp đỉnh traffic đột ngột khi có tin nóng — một bài viết có thể nhận vài trăm nghìn view trong 10 phút. Origin CMS của họ (chạy trên PHP) không thể chịu tải này.
Giải pháp là micro-caching cho trang bài viết. Trang bài viết về bản chất là dynamic (có phần bình luận, số view), nhưng nội dung chính gần như tĩnh trong khoảng thời gian ngắn. Họ cấu hình:
- Nội dung bài viết:
s-maxage=10vớistale-while-revalidate=30. CDN phục vụ trực tiếp, chỉ chạm origin mỗi 10 giây cho mỗi bài. - Phần bình luận và số view: tách thành một API riêng, tải bằng JavaScript phía client, không cache tại CDN.
Bài học: Ranh giới static/dynamic không phải nhị phân. Tách trang thành phần "gần tĩnh" (cache được ngắn hạn) và phần "thật sự động" (tải riêng) là kỹ thuật cực mạnh để chịu tải cao.
Hướng dẫn từng bước
Đây là quy trình xây dựng CDN strategy khi bạn ra quyết định kiến trúc:
Bước 1 — Kiểm kê và phân loại nội dung. Lập danh sách mọi loại response mà hệ thống trả ra. Với mỗi loại, trả lời: Nó có giống nhau giữa các người dùng không? Nó thay đổi bao lâu một lần? Nó có chứa dữ liệu nhạy cảm không? Chia thành ba nhóm: static bất biến, semi-dynamic (cache ngắn được), và dynamic riêng tư (không cache).
Bước 2 — Xử lý static asset trước. Đây là chiến thắng dễ nhất. Bật fingerprint/hash trong build pipeline, đặt Cache-Control: public, max-age=31536000, immutable. Đảm bảo file HTML (là entry point, hay đổi) thì cache ngắn hoặc no-cache để luôn trỏ tới bản asset mới nhất.
Bước 3 — Đặt header rõ ràng, đừng dựa vào mặc định. Với mỗi loại nội dung, gắn Cache-Control tường minh. Dùng s-maxage để điều khiển CDN riêng biệt với max-age (điều khiển trình duyệt). Dùng Vary khi response khác nhau theo header (ví dụ nén, ngôn ngữ). Nguyên tắc vàng: default deny cho nội dung động.
Bước 4 — Áp micro-caching cho semi-dynamic. Với endpoint tải cao nhưng nội dung không cần tức thời, thử TTL ngắn (1-30 giây) kèm stale-while-revalidate. Đo cache hit ratio trước và sau.
Bước 5 — Cấu hình cache key và bypass. Xác định cache key gồm những gì (URL, query string nào có ý nghĩa, header nào). Loại bỏ query string tracking (như utm_source) khỏi cache key để tránh phân mảnh cache. Cấu hình bypass rõ ràng cho request có auth.
Bước 6 — Thiết lập cơ chế invalidation. Khi cần đẩy nội dung mới ngay (ví dụ gỡ một bài viết sai), bạn cần purge cache. Ưu tiên purge theo tag/surrogate key thay vì purge toàn bộ. Với static asset đã fingerprint, bạn gần như không bao giờ cần purge — chỉ cần deploy tên file mới.
Bước 7 — Đo lường và giám sát. Theo dõi cache hit ratio, latency P50/P99 theo vùng địa lý, lưu lượng origin, và tỷ lệ lỗi. Đây là vòng lặp cải tiến liên tục, không phải cấu hình một lần rồi quên.
Lỗi thường gặp & mẹo
Lỗi: Cache nhầm nội dung riêng tư. Như ví dụ fintech ở trên — thiếu Cache-Control: private khiến CDN phục vụ dữ liệu người này cho người khác. Mẹo: mặc định không cache API; chỉ opt-in sau khi kiểm chứng.
Lỗi: Không dùng fingerprint cho asset. Đặt max-age dài nhưng giữ nguyên tên file app.js. Khi deploy bản mới, người dùng vẫn thấy bản cũ suốt cả năm. Mẹo: luôn fingerprint file static; HTML thì cache ngắn.
Lỗi: Phân mảnh cache do cache key quá rộng. Đưa toàn bộ query string vào cache key khiến ?utm_source=facebook và ?utm_source=google tạo hai bản cache khác nhau cho cùng nội dung, làm hit ratio tụt thảm hại. Mẹo: chuẩn hóa cache key, loại bỏ tham số không ảnh hưởng nội dung.
Lỗi: Quên Vary: Accept-Encoding. Có thể phục vụ nội dung nén gzip cho client không hỗ trợ nén, gây lỗi hiển thị. Mẹo: cấu hình Vary đúng, hoặc để CDN xử lý nén tại edge.
Lỗi: Coi CDN là "bật là xong". Không đo hit ratio, không biết mình đang tiết kiệm được gì. Mẹo: hit ratio là KPI trung tâm; dưới 90% cho static là dấu hiệu cấu hình sai.
Mẹo về chi phí: Băng thông CDN thường rẻ hơn egress từ cloud, nhưng vẫn là chi phí lớn ở quy mô. So sánh giá theo vùng — nhiều CDN tính giá khác nhau cho châu Á so với Bắc Mỹ. Với thị trường Việt Nam, kiểm tra xem CDN có PoP đặt trong nước hay gần (Singapore, Hong Kong) không, vì điều đó ảnh hưởng trực tiếp tới latency.
Mẹo về bảo mật: Đặt CDN chắn trước origin và chặn truy cập trực tiếp vào origin (chỉ cho phép IP của CDN). Nếu không, kẻ tấn công tìm ra IP origin sẽ vòng qua lớp chống DDoS của bạn.
Bài tập thực hành
Bài 1 — Phân loại nội dung. Lấy một sản phẩm bạn đang làm (hoặc một trang web bất kỳ). Liệt kê ít nhất 8 loại response khác nhau (HTML trang chủ, ảnh sản phẩm, file CSS, API danh mục, API giỏ hàng, API profile...). Với mỗi loại, quyết định: static bất biến / semi-dynamic / dynamic riêng tư, và viết ra header Cache-Control phù hợp.
Bài 2 — Tính toán tác động micro-cache. Một endpoint nhận 6.000 request/giây. Bạn áp s-maxage=5. Trong trường hợp lý tưởng (nội dung giống nhau với mọi người), origin phải xử lý bao nhiêu request/giây cho endpoint này? Tính cả cache hit ratio tương ứng.
Bài 3 — Điều tra sự cố. Người dùng phàn nàn họ thấy nội dung của người khác trên trang dashboard. Viết ra 3 giả thuyết nguyên nhân liên quan tới CDN và cách kiểm tra từng cái. Sau đó viết cấu hình header khắc phục.
Bài 4 — Viết mini-ADR. Soạn một Architecture Decision Record ngắn (dùng cấu trúc đã học ở Bài 4) cho quyết định: "Có nên đưa API danh mục sản phẩm vào sau CDN với micro-caching không?" Nêu context, các phương án, trade-off, và quyết định cuối cùng.
Tóm tắt
CDN không chỉ là "cache cho nhanh" — nó là một quyết định kiến trúc ảnh hưởng tới latency, chi phí băng thông, khả năng chống DDoS và trải nghiệm người dùng. Bốn động lực chính để dùng CDN là: đưa nội dung tới gần người dùng (latency), giảm tải origin (bandwidth), chống tấn công (DDoS), và một tầng caching HTTP sẵn có.
Điểm mấu chốt của bài này là phân biệt static và dynamic. Static asset là chiến thắng dễ dàng: fingerprint tên file rồi cache thật lâu với immutable. Dynamic content phức tạp hơn nhưng vẫn có nhiều tầng chiến lược — micro-caching TTL ngắn, stale-while-revalidate, tách phần gần tĩnh khỏi phần thật sự động, và edge compute. Nguyên tắc điều khiển tất cả là các HTTP header đúng đắn, cùng tư duy default deny cho nội dung động để tránh thảm họa cache nhầm dữ liệu riêng tư.
Cuối cùng, CDN strategy là một vòng lặp: kiểm kê, phân loại, đặt header, đo hit ratio, và cải tiến liên tục. Người kỹ sư giỏi nhìn thấy cả đường đi của một byte lẫn hóa đơn cloud cuối tháng — và biết rằng một cấu hình cache đúng có thể là ranh giới giữa một đợt sale thành công và một sự cố downtime.