Lập trình 22/09/2026 8 phút

Phiên bản API: đo thật qua nginx cache — đánh phiên bản bằng header mà quên Vary là 100/200 client nhận sai bản

Ba cách đổi hợp đồng API khi còn client cũ đang gọi: phiên bản trong URL, phiên bản trong header Accept, và không đánh phiên bản mà chỉ thêm trường. Mình đặt nginx proxy_cache trước server Go rồi cho hai client xen kẽ đòi v1 và v2: cách URL và cách header có Vary: Accept đều đúng 200/200, còn header mà quên Vary thì 100/200 client nhận sai phiên bản — cache trả bản v1 cho client v2 mà không có lỗi nào. Đo thêm: client dùng decoder chặt vỡ ngay khi server chỉ thêm một trường, và lớp chuyển đổi v2 sang v1 tốn 14–17 µs, nằm trong nhiễu đo — cái giá của phiên bản không nằm ở CPU.

Lập trình 22/09/2026 8 phút

ETag và request có điều kiện: đo thật 1.000 lần poll — 304 cắt 62 MB xuống 0,1 MB, nhưng chỉ ETag từ version mới cắt được việc của server

Client poll một danh sách 200 sản phẩm 1.000 lần trong khi dữ liệu không đổi. Không ETag: 62,2 MB qua dây và 1.000 query nặng. ETag băm từ thân phản hồi: 999 lần 304, còn 0,1 MB — nhưng server vẫn chạy đủ 1.000 query và p50 y hệt 0,24 ms, vì phải render xong mới biết là không đổi. ETag tính từ version trong database: cũng 0,1 MB, nhưng chỉ 1 query nặng và p50 còn 0,098 ms. Cùng ETag đó dùng cho If-Match trên PUT: hai người cùng sửa một sản phẩm, không kiểm thì bản của người đầu mất không dấu vết, có If-Match thì người sau nhận 412.

Lập trình 22/09/2026 8 phút

Cache-Control trên HTTP API: đo thật qua nginx — max-age=5 cắt 99% request tới origin, và lúc hết hạn stale-while-revalidate cho 50 client đồng thời chỉ lọt 1

Origin Go mất 40 ms mỗi request, nginx proxy_cache đứng trước làm cache dùng chung, client bắn 300 request trong 12 giây cho từng chỉ thị Cache-Control. Không header, no-store và private: cả 300 tới origin, p50 44 ms. max-age=5: 3 tới origin, p50 0,8 ms, dữ liệu cũ tối đa 5,93 giây. Thứ max-age không chữa được là khoảnh khắc hết hạn: 50 client gọi đúng lúc đó thì 50 request lọt tới origin; thêm stale-while-revalidate=30 thì chỉ 1 lọt, 49 client còn lại nhận bản cũ trong 4 ms. Kèm chuyện private bị cache dùng chung bỏ qua, và s-maxage để tách hạn cho cache dùng chung với trình duyệt.

Lập trình 22/09/2026 8 phút

Nén response API: đo thật gzip và brotli — JSON 21 KB còn 2,2 KB trong 125 µs, brotli-11 tốn 16,85 ms cho thêm 2%, và thân 11 byte nén xong thành 35 byte

Đo nén trên JSON thật từ PostgreSQL, từ 308 byte tới 2,09 MB: gzip-6 đưa 21,2 KB xuống 2,2 KB (10,6%) với 125 µs nén và 26 µs giải. Mức nén gần như không quan trọng với JSON — gzip-1 và gzip-9 chênh 0,3 điểm phần trăm — nhưng brotli-11 tốn 16,85 ms, gấp 135 lần brotli-4, để mua thêm 1 điểm. Hai trường hợp nén làm hại: thân dưới 100 byte phình thêm 24 byte, và JSON chứa ảnh base64 chỉ còn 75% nhưng tốn CPU gấp 17 lần. Kèm thương lượng Accept-Encoding qua HTTP thật, header Vary bắt buộc, và điểm hoà vốn theo băng thông tính từ số đo.

Lập trình 22/09/2026 7 phút

Thiết kế payload: đo thật N+1 qua HTTP — 51 request mất 172,7 ms so với 1 request 3,1 ms, và expand mà server tự N+1 vào database thì chỉ đổi chỗ vấn đề

Một màn hình cần 50 review kèm tên và giá sản phẩm. Client làm N+1 qua HTTP: 51 request, 51 query, 172,7 ms với RTT giả lập 2 ms; chạy song song 10 luồng còn 31,2 ms nhưng vẫn 51 request. Endpoint bulk ?ids= còn 2 request, 7,6 ms. ?expand=product còn 1 request, 3,1 ms — nhưng chỉ khi server gom id thành một query IN; bản server tự lặp từng sản phẩm vẫn tốn 51 query. Chọn trường ?fields=id,ten,gia cắt 3,0 KB xuống 0,4 KB vì mỗi sản phẩm mang theo mô tả 256 ký tự màn hình không hiện. Ở RTT 20 ms, N+1 là 1,17 giây cho 50 dòng.

Lập trình 22/09/2026 9 phút

Timeout, retry và deadline xuyên suốt: đo thật chuỗi gateway → A → B — retry ở mỗi tầng đẩy p99 lên 771 ms và 98 lần A làm việc vào hư không, deadline truyền xuống giữ p99 255 ms

Ba service trong một chuỗi gọi, B chậm 400 ms ở 30% request, người dùng chịu được 250 ms. Không timeout: p99 410 ms. Mỗi tầng tự đặt timeout: gateway bỏ đi lúc 250 ms nhưng A vẫn chờ B tới cùng — 61 lần làm việc vào hư không, 139/200 thành công. Mỗi tầng tự retry: 194/200 thành công nhưng p99 771 ms, B nhận gấp 2,1 lần request, 98 lần A làm việc vô ích. Deadline 250 ms truyền qua header và context: 180/200, p99 255 ms, 0 việc vô ích, và khi gateway huỷ thì cả A lẫn B dừng theo — 80 lần B dừng sớm thay vì chạy hết 400 ms.