Cloudflare Worker phục vụ site tĩnh: hai đường tới cùng một file, lùi phiên bản 6 giây và trần 50 subrequest
Trên Cloudflare Worker có static assets, cùng một file có thể tới tay người gọi bằng hai lối: qua mã Worker, hoặc thẳng từ asset server. Trong vài giây đầu sau wrangler deploy, hai lối đó có thể trả hai phiên bản khác nhau. Bài này gom ba điều đáng biết khi phục vụ site tĩnh và API trên Worker, kèm số đo từ hecigo.com và Ads Insights: hai đường lệch nhau sau deploy, lùi phiên bản mất 6 giây, và trần 50 subrequest của gói Free.
Worker và asset server: ai trả lời request nào
Một Worker có static assets gồm hai phần: asset server trả file trong thư mục build, và mã Worker của bạn. Mặc định asset server đi trước, request khớp đúng một file thì mã Worker không chạy. Request tới static assets miễn phí và không giới hạn ở mọi gói; chỉ request làm mã Worker chạy mới bị tính.
hecigo.com là bản build Next.js tĩnh cộng một Worker đọc header Accept để trả HTML hay Markdown cho cùng một URL. Worker phải chạy trước asset server mới thấy header đó:
"assets": {
"directory": "./out",
"binding": "ASSETS",
"html_handling": "auto-trailing-slash",
"not_found_handling": "none",
"run_worker_first": ["/", "/*/"]
}Bản rút gọn, file thật liệt kê thêm các đường dẫn chuyển hướng. Hai điểm đáng chú ý:
run_worker_firstlà một hình dạng URL, không phải danh sách. Site bậttrailingSlashnên mọi URL trang kết thúc bằng/, còn không file nào như vậy. Ảnh, font và cả bó/_next/static/không bao giờ chạm tới Worker. Đặttruevẫn chạy đúng, nhưng mỗi ảnh thành một lần gọi bị tính.not_found_handling: "none"để đường dẫn không có file rơi xuống Worker dưới dạng 404 trơn, Worker tự chọn trả lỗi bằng JSON, HTML hay Markdown. Đặt"404-page"thì asset server trả trang HTML trước khi Worker kịp quyết.
Hai đường tới cùng một file có thể lệch sau deploy
| Request | Ai trả lời |
|---|---|
/blog/ + Accept: text/markdown | Worker chạy, gọi env.ASSETS.fetch('/blog.md') |
/blog.md | Asset server trả thẳng |
Trong hai tới bốn giây đầu sau deploy, hai lối này chưa chắc cùng thấy phiên bản mới, và lối chậm hơn trả bản cũ. Với người đọc trang, không có gì hỏng. Với agent đọc site bằng Markdown, hỏi bằng header và hỏi bằng đuôi .md cho hai kết quả khác nhau.
Purge cache không giải quyết được. cf-cache-status: HIT trên /blog.md đến từ tầng static assets của Workers, không phải cache HTTP của zone, và tầng đó khoá theo nội dung: /blog.md?x=<chuỗi ngẫu nhiên> vẫn HIT, cùng etag. Purge theo URL không có gì để xoá.
Cho cổng kiểm thử lại khi lệch cũng không phải cách. Một cổng biết thử lại là cổng đã học cách chấp nhận lệch.
Cách hecigo dùng: một bước chờ trước cổng kiểm, hỏi xem cả hai đường đã trả đúng từng byte của bản build chưa. Không so hai bên với nhau, vì hai bên cùng cũ cũng giống nhau.
// Rút gọn từ waitForDeploy trong scripts/wait-for-deploy.mjs: bỏ phần đọc out/, in báo cáo và xác thực staging.
const TIMEOUT_MS = 180_000
const INTERVAL_MS = 5_000
const STABLE_ROUNDS = 2 // một lượt sạch có thể là may
let clean = 0
const deadline = Date.now() + TIMEOUT_MS
while (clean < STABLE_ROUNDS) {
if (Date.now() > deadline) throw new Error('quá trần, còn đường trả bản cũ')
// checkRound: mọi trang, cả hai đường, so với từng byte trong out/; trả danh sách đường còn lệch
const stale = await checkRound({ fetchImpl: fetch, origin, pages, expected })
clean = stale.length === 0 ? clean + 1 : 0
if (clean < STABLE_ROUNDS) await sleep(INTERVAL_MS)
}Phải sạch hai lượt liền nhau vì các máy trong cùng một điểm hiện diện không nhận phiên bản mới cùng lúc, và cổng kiểm chạy ngay sau đó bắn hơn 300 request. Lỗi 5xx và lỗi mạng tính là chưa sẵn sàng, không tính là hỏng. Ở một lần đăng bài đã đo, bước chờ mất 18 giây.
📖Đổi host và bốn hành vi nền tảng đang làm hộ mà bạn không sở hữu
Chuyển hecigo.com sang một host khác. Adapter mất mười lăm dòng. Bốn hành vi nền tảng vẫn làm hộ thì mỗi cái hỏng một kiểu, và không cái nào làm...
Lùi phiên bản: asset có lùi theo
Mỗi lần deploy là một phiên bản Worker mới. Lùi là đổi phiên bản đang phục vụ, không đụng DNS:
npx wrangler versions list
npx wrangler rollback <version-id>Cloudflare cho lùi về tối đa 100 phiên bản gần nhất, chặn lùi nếu phiên bản đích bind tới R2, KV hay queue đã bị xoá, và wrangler cảnh báo rằng nó không lùi tài nguyên được bind (Durable Object, D1, R2, KV).
Điều tài liệu không nói thẳng: static assets có lùi theo phiên bản không? Nếu không, mã Worker cũ sẽ phục vụ asset mới, một tổ hợp chưa từng tồn tại ở lần build nào. hecigo kiểm bằng diễn tập trên production:
| Số đo | Kết quả |
|---|---|
| Asset lùi theo phiên bản | có, 6 giây sau lệnh lùi |
| Cổng hợp đồng khi ở phiên bản cũ | 452 phép kiểm, 0 hỏng |
| Cổng hợp đồng sau khôi phục | 452 phép kiểm, 0 hỏng |
| Production ở phiên bản cũ | khoảng 26 giây |
Diễn tập là một workflow GitHub Actions chạy tay, dùng chung hàng đợi với workflow deploy: lùi về phiên bản liền trước, chạy cổng hợp đồng, khôi phục bằng wrangler deploy, chờ hai đường khớp bản build, chạy cổng lần nữa. Kết quả chỉ đúng cho cách deploy lúc chạy, nên đổi cách build là chạy lại.
Trần 50 subrequest của gói Free
Ads Insights, MCP server đọc số quảng cáo của hecigo, cũng chạy trên Worker. Mỗi câu hỏi của trợ lý AI thành nhiều lời gọi tới API của Meta, Google và TikTok.
Gói Workers Free giới hạn 50 subrequest ra ngoài cho mỗi lần gọi Worker (gói trả phí mặc định 10.000). Mọi fetch đều tính, kể cả thử lại, làm mới token và từng bước trong chuỗi chuyển hướng. Vượt trần thì lần gọi chết với Too many subrequests, mất cả câu trả lời, kể cả phần của tài khoản đã có số.
Con số 50 hết nhanh hơn tưởng. TikTok chỉ nhận tối đa 30 ngày mỗi request khi báo cáo theo ngày, nên báo cáo 400 ngày là 14 cửa sổ cộng một lời gọi lấy đơn vị tiền tệ: 15 lời gọi cho một nhà quảng cáo. Bốn nhà quảng cáo là 60.
Ads Insights giữ trần bằng một ngân sách cho mỗi lần gọi công cụ, mặc định 45, đọc từ cấu hình:
// Rút gọn từ src/core/budget.ts của Ads Insights: bỏ phần đặt trước và trả lại.
export class SubrequestBudget {
private used = 0
constructor(readonly limit: number) {}
take(provider: string): void {
if (this.used >= this.limit) {
throw new SubrequestBudgetError(provider,
'ask a shorter range, or one account at a time (accountId)')
}
this.used++ // gọi trước MỖI lần fetch, kể cả thử lại và làm mới token
}
}- Đặt trước. Báo cáo TikTok biết trước số lời gọi cần, nên giữ đủ số đó trước lời gọi đầu tiên; không đủ thì từ chối nguyên tài khoản đó, thay vì chạy dở.
- Chừa chỗ. TikTok không được lấy một phần mười cuối ngân sách, để dành cho tài khoản Meta hay Google chỉ cần một hai lời gọi.
- Từ chối bằng câu đọc được. Trợ lý nhận lời khuyên hỏi khoảng ngày ngắn hơn hoặc từng tài khoản, các tài khoản khác vẫn trả số.
45 chứ không 50 vì sau câu trả lời đầu tiên Worker còn gửi vài sự kiện đo lường. Lên gói trả phí chỉ đổi một biến cấu hình. Lớp này dựng từ việc đếm lời gọi, chưa phải từ sự cố: hecigo chưa thấy Too many subrequests nào trong log production của Ads Insights.
Câu hỏi tự kiểm
- Đường dẫn nào thật sự cần mã Worker, và phạm vi
run_worker_firstcủa bạn là một hình dạng hay một danh sách sẽ dài dần? - Có file nào tới được bằng hai lối? Cổng kiểm sau deploy so cả hai với bản build, hay chỉ so hai bên với nhau?
- Lần diễn tập lùi gần nhất có sau lần đổi cách build gần nhất không?
- Lời gọi công cụ đắt nhất của bạn làm bao nhiêu
fetch, tính cả thử lại? Vượt trần thì hỏng một phần hay hỏng cả câu trả lời?
Câu hecigo chưa có đáp án: bước chờ chỉ che khoảng lệch cho cổng kiểm, agent hỏi đúng vào mấy giây ấy vẫn thấy hai bản khác nhau. Worker có cách nào biết mình đang ở giữa hai phiên bản không?
Đang chuyển site hay API lên Cloudflare Worker? Câu hỏi về phạm vi run_worker_first, cổng kiểm sau deploy hay ngân sách subrequest, nhắn hecigo. Muốn xem một MCP server chạy trên Worker thì thêm https://ads.hecigo.com/mcp vào trợ lý AI của bạn theo trang hướng dẫn.
MCP server là gì: mổ một MCP server đang chạy thật, từ 3 công cụ lên 9 và 18 KB mô tả trợ lý phải đọc
MCP server là gì, giải thích bằng một server đang chạy: hai đường truyền, danh sách công cụ trợ lý đọc, kết quả trả về, đăng nhập OAuth và ba chỗ...
Nguồn tham khảo
- Pricing - Cloudflare Workers docs
- Run your Worker script first - Cloudflare Workers docs
- Rollbacks - Cloudflare Workers docs
- Limits: Subrequests - Cloudflare Workers docs
- Workers are no longer limited to 1000 subrequests - Cloudflare Changelog