Một repo, nhiều Worker
AnyRouter là một monorepo pnpm, ship thành nhiều Cloudflare Worker. Một lần deploy toàn bộ gồm 17 Worker chính và hai Worker proxy nhỏ. API, website, dashboard, docs, blog và MCP server là các Worker riêng. Phần lớn chúng dùng chung code từ cùng các package.
Agent merge thay đổi cả ngày và deploy khoảng mỗi giờ một lần. Bài đầu tiên của loạt bài nói về vòng lặp đó. Bài này nói về một lệnh bên trong nó, pnpm ops deploy, và ba câu hỏi lệnh này phải trả lời ở mỗi lần chạy.
- Mỗi Worker đang chạy gì ngay lúc này?
- Thay đổi này có thể ảnh hưởng tới những Worker nào?
- Checkout này có mới hơn production không?
Vì sao chúng tôi thôi deploy tất cả
Cho tới đầu tháng 10, một lần deploy sẽ ship cả đội Worker mỗi khi một package dùng chung thay đổi, mà gần như thay đổi nào cũng chạm tới package dùng chung. Một lần deploy toàn bộ mất trung vị khoảng 1.130 giây, đo trên 5 lần chạy toàn bộ thành công gần nhất trong log deploy, từ 6 đến 9 tháng 10.
Giai đoạn lớn nhất mất 385 giây. Nó crawl danh mục model thành HTML tĩnh rồi deploy lại Worker web cùng các trang đó. Chỉ Worker web ship HTML đó. Một thay đổi chỉ ở docs vẫn phải trả cho lần crawl, và mất khoảng 513 giây, trong đó 425 giây tạo ra các trang không bao giờ được deploy.
Mỗi Worker ghi lại commit của chính nó
Bản sửa đầu tiên là thôi đoán xem production đang chạy gì. Mỗi lệnh wrangler deploy mà ops chạy đều truyền --message anyr-sha=<commit>. Cloudflare lưu message đó trên version của Worker, nên bất kỳ máy nào có API token đều đọc lại được. Việc này không tốn gì và không cần database mới.
Một lệnh pnpm ops deploy thường sẽ đọc version đang chạy của mọi Worker song song. Lần chúng tôi đo vào ngày 12 tháng 10, việc đọc này mất 1,8 giây. Cập nhật secret tạo ra một version mới không có message, nên bộ đọc bỏ qua các version đó và tìm version code bên dưới.
Sau đó diff cho mỗi Worker là <commit đang chạy của nó>..HEAD, cộng với các thay đổi chưa commit. Trước đây chúng tôi diff với origin/main. Diff đó rỗng ngay khi một merge được push, và ngày 11 tháng 10 một lần deploy với --since HEAD~1 đã bỏ sót một merge nằm dưới một commit docs mới hơn. Commit đang chạy là mốc duy nhất khớp với thứ thật sự đang chạy.
Một Worker không đọc được commit thì luôn được deploy. Điều đó gồm Worker chưa từng được đánh dấu, một lần deploy từ cây có thay đổi chưa commit, hoặc lỗi API. Deploy thừa thì an toàn. Deploy thiếu thì không.
Đồ thị import chọn mục tiêu
Tiếp theo, scripts/lib/worker-graph.ts ánh xạ các file thay đổi sang Worker. Với mỗi Worker, nó gom các workspace package mà Worker phụ thuộc và các package @anyrouter/* mà source thật sự import, rồi áp dụng cùng quy tắc đó qua từng package. File test không bao giờ thêm cạnh.
Package UI dùng chung được xử lý theo từng file. Với mỗi Worker, bộ lập kế hoạch đi theo các import từ source của chính Worker đó vào packages/ui. Một file UI thay đổi chỉ deploy những Worker chạm tới nó, và bản dry run ghi tên file import nó. Nếu gặp một import không lần theo được, như import() động với template string, Worker đó bị coi là chạm tới toàn bộ package UI.
Một số đầu vào có thể đổi mọi bundle, nên chúng buộc đi đường toàn bộ. Gồm package.json, lockfile, cấu hình TypeScript và Vite ở gốc, chính các script deploy, migration database, và mọi đường dẫn mà bộ lập kế hoạch không nhận ra. Một ghi chú kb trong docs/kb tự nó không deploy gì.
pnpm ops deploy --dry-run in kế hoạch với một dòng cho mỗi Worker, lý do của mỗi mục tiêu và thời gian ước tính. Đây là ước tính của bộ lập kế hoạch cho vài loại thay đổi, dựa trên trung vị từng giai đoạn trong log deploy:
| Thay đổi | Deploy | Ước tính | Trước đây |
|---|---|---|---|
| Chỉ dashboard | dashboard | khoảng 65 s | khoảng 1.130 s |
| Chỉ app docs | docs | khoảng 95 s | khoảng 513 s |
| Các section trang chủ | web và phụ thuộc runtime của nó | khoảng 620 s | khoảng 1.130 s |
| Chỉ một ghi chú kb | không gì cả | 0 s | 0 s |
Một checkout cũ không thể đưa prod lùi lại
Ngày 12 tháng 10, hai máy chạy pnpm ops deploy cùng lúc. Một máy deploy commit mới hơn đè lên máy kia giữa chừng, nên lần chạy đầu tiên thất bại ở bước kiểm tra cuối. Lần đó không gây hại, vì bản build mới hơn đã chứa các commit cũ hơn. Nếu thứ tự ngược lại, production đã quay về một bản build cũ hơn.
Giờ có hai lớp chặn chạy trước lần upload hoặc migration đầu tiên.
- Một lease deploy. Lần deploy ghi một key lease vào Workers KV với tên máy, commit, thời điểm bắt đầu, một nonce ngẫu nhiên, và thời hạn 25 phút. KV không có compare-and-set, nên bên ghi chờ 1 giây rồi đọc lại key. Chỉ máy có nonce được lưu mới sở hữu lease. Máy khác sẽ chờ, kiểm tra lại mỗi 15 giây, và mặc định bỏ cuộc sau 15 phút. Một tiến trình nền gia hạn lease mỗi phút trong lúc build.
- Một lớp chặn rollback. Sau khi lấy lease, lần deploy so HEAD với
anyr-shađang chạy của từng mục tiêu. Nếu HEAD là tổ tiên thực sự của một commit đang chạy, nó huỷ với thông báo rằng production mới hơn và bảo bạn pull. Một commit đang chạy mà git không nhận ra thì không bao giờ chặn deploy.
Lớp kiểm tra thứ ba đến từ một sự cố khác. Một lần deploy đọc checkout dùng chung trong nhiều phút, và có lần một session khác chạy git pull ở đó giữa lúc build. Lần deploy đó crawl trang bằng script mới hơn trên một bundle cũ hơn, và 4 trang model rơi về render phía server. Giờ ops deploy ghi lại HEAD và trạng thái các file được theo dõi khi bắt đầu, rồi kiểm tra lại cả hai trước mỗi lần build, upload và crawl. Nếu một trong hai thay đổi, nó huỷ và ghi tên các Worker đã ship.
Lần crawl được cache và nhanh hơn
Mỗi lần deploy web thay toàn bộ file tĩnh của Worker, nên lần nào cũng phải ship các trang đã prerender. Bỏ chúng đi sẽ đưa danh mục về render phía server. Vì vậy bộ lập kế hoạch không bao giờ bỏ qua lần crawl. Nó dùng lại một lần crawl cũ.
Key của cache là hash của bundle đã build, không phải của source, vì HTML nhúng tên asset của bản build. Chạy lại một lần deploy thất bại trên cùng bản build sẽ dùng lại lần crawl và bỏ qua hẳn bước đó. Một lần deploy không chạm Worker web thì hoàn toàn không crawl.
Bản thân lần crawl cũng nhanh hơn. Trước đây nó chạy từng trang một với khoảng nghỉ 800 ms, cấu hình chọn cho gói Workers Free. Trên gói trả phí, giờ nó chạy 2 trang một lúc với khoảng nghỉ 100 ms. Trong 18 lần deploy thành công được ghi trên một máy ngày 25 và 26 tháng 10, cùng bước đó, gồm crawl và deploy lại Worker web, mất 84 đến 129 giây, trung vị khoảng 100. Trước đây là 385.
Cái giá hiện tại
Đây là log deploy của một máy trong ngày 25 và 26 tháng 10. Các máy khác cũng deploy, nên đây là một mẫu, không phải tổng.
- 18 lần deploy thành công và 3 lần thất bại ở chế độ theo file thay đổi.
- Trung vị 522 giây từ đầu tới cuối trên cả 18 lần, và trung vị 419 giây cho tới khi production phục vụ code mới và qua bài smoke test quan trọng 5 giây. Lần crawl đầy đủ và các bước kiểm tra chạy sau thời điểm đó.
- 11 trên 18 lần đi đường toàn bộ, trung vị 530 giây. 7 lần còn lại ship từ 11 đến 17 Worker, trung vị 493 giây.
Con số cuối là quan trọng nhất. Chính các agent thường xuyên sửa script deploy, lockfile và package dùng chung, và mỗi thứ đó buộc một lần deploy rộng. Chọn từng Worker tiết kiệm nhiều nhất với các thay đổi nhỏ, cục bộ, như một bản sửa dashboard hay một chỉnh sửa docs. Vì vậy mức giảm của các lần đi đường toàn bộ, từ khoảng 1.130 xuống 530 giây, chủ yếu đến từ hai thay đổi khác. Lần crawl giảm từ 385 xuống khoảng 100 giây, và việc upload giờ chạy trong lúc bản build tiếp theo đang chạy. Đó là cách chúng tôi đọc thời gian từng giai đoạn trong log, không phải một phép thử có đối chứng.
Tiếp theo, chúng tôi muốn có ít đầu vào buộc đi đường toàn bộ hơn. Dashboard là một ví dụ. Khi đếm vào ngày 10 tháng 10, 102 trên 126 file UI dashboard mà Worker dashboard chạm tới cũng được bundle vào Worker web qua một cây route cũ, nên sửa một file trong số đó cũng deploy cả Worker web. Xoá cây cũ đó sẽ khiến các chỉnh sửa này chỉ deploy dashboard.
Bài liên quan
Building an LLM gateway on Cloudflare Workers
AnyRouter runs as a set of Cloudflare Workers, not a long-running server. That constrains the design in specific ways — a 128MB memory ceiling that rules out buffering responses, a 3MiB script cap that forced a multi-Worker split, and a hard rule that every upstream call goes through Cloudflare's AI Gateway. Here's what that architecture actually looks like.
How automatic failover works in an LLM gateway
A single provider call has a single point of failure: one rate limit, one outage, one bad deploy on their end, and your request fails. Here's the actual mechanism AnyRouter uses to route around that — circuit breakers, escalating cooldowns, and a fallback chain — and why the extra hop barely registers next to model generation time.
Gửi request đầu trong hai phút
Bắt đầu miễn phí với key của bạn, hoặc nạp rồi trả theo token. Go: $4 credit/tháng — $2/tháng, hoặc miễn phí khi góp một provider key.
Bắt đầu miễn phí