Nhiều app, một tài khoản
Khi bạn có app thứ hai, thứ năm, thứ mười. Bẫy dùng chung cơ sở dữ liệu, quy ước tên, và cách để app này hỏng không kéo app kia theo.
Người đi hết giáo trình này thường không dừng ở một app. Sau một năm bạn có thể có 5–20 Worker, vài chục tên miền, và một mớ cơ sở dữ liệu. Bài này nói về lúc đó.
Bẫy lớn nhất: dùng chung một D1
Khi hết hạn mức số cơ sở dữ liệu, cách làm hiển nhiên là nhét app mới vào D1 có sẵn, phân biệt bằng tiền tố tên bảng:
minhtrung-checkin (một D1)
├─ nguoi_dung, bao_cao, anh ← app chấm công
├─ td_hang, td_phieu ← app quản lý kho
└─ cn_doi_soat, cn_cong_no ← app công nợ
Nó chạy được, và đôi khi là lựa chọn đúng. Nhưng phải biết cái giá:
Đây không phải chuyện lý thuyết. Khi app công nợ chạy một truy vấn đối soát nặng, app chấm công khựng theo, dù hai app chẳng liên quan gì nhau và dùng bảng khác hẳn.
Hệ quả thực tế:
- Sự cố ở app A trở thành sự cố ở app B, C, D.
- Khi gỡ lỗi, bạn tìm trong app B mãi không ra — vì nguyên nhân ở app A.
- Một lệnh sao lưu SELECT * trên bảng lớn của app A làm chậm toàn bộ.
Luật: app có người dùng thật và app thử nghiệm không bao giờ chung một D1. Nếu buộc phải chung, hãy ghi rõ trong README của cả hai app, và khi gỡ lỗi chậm thì kiểm app kia trước.
Khi nào dùng chung chấp nhận được: các app cùng một tổ chức, cùng do bạn vận hành, lưu lượng thấp, và quan trọng nhất — không app nào chạy truy vấn nặng trong giờ làm việc.
Quy ước đặt tên — làm từ app thứ hai
Khi có 20 Worker, cái bạn cần nhất là nhìn tên biết ngay là gì.
<tổ-chức>-<app>[-<môi-trường>]
mt-checkin app chấm công của Minh Trung
mt-checkin-thu bản thử của chính nó
mt-congno app công nợ
changan-pos app bán hàng quán Chang An
Và đặt tên tài nguyên theo app, đừng đặt tên chung chung:
D1: mt-checkin-db (KHÔNG: "database", "app-db")
R2: mt-checkin-anh
KV: mt-checkin-phien
Ba tháng sau, một bucket tên anh sẽ làm bạn mất 10 phút để nhớ nó của app nào.
Một bảng kê, và nó phải sống
File HA-TANG.md ở một chỗ cố định, liệt kê mọi thứ bạn đang chạy:
| App | Worker/Pages | Tên miền | D1 | R2 | Repo | Ai dùng |
|---|---|---|---|---|---|---|
| Chấm công | mt-checkin | ck.cty.com | mt-checkin-db | mt-checkin-anh | gh:.../checkin | 180 NV |
| Công nợ | mt-congno | — (pages.dev) | DÙNG CHUNG mt-checkin-db (tiền tố cn_) | — | gh:.../congno | 4 kế toán |
| Web quán | changan-web | changan.com | — | — | gh:.../changan | công khai |
Cột "Ai dùng" là cột quan trọng nhất: nó cho bạn biết app nào hỏng thì ai gọi điện, và app nào có thể tắt mà không ai để ý.
Bảng này tự sinh được:
npx wrangler deployments list
npx wrangler d1 list
npx wrangler r2 bucket list
npx wrangler kv namespace list
Đưa mọi Worker lên GitHub
Khi có nhiều app, việc mã nguồn chỉ nằm trên máy bạn trở thành rủi ro thật. Mỗi Worker nên có một repo riêng tư, và nối Workers Builds (Cloudflare tự build khi bạn push).
Khi CI chạy wrangler deploy, cấu hình trong wrangler.toml của repo thay thế cấu hình đang chạy. Nếu bạn từng thêm binding (D1, KV, R2, secret) bằng tay trên dashboard mà chưa ghi vào file, lần deploy đầu tiên qua CI sẽ làm nó biến mất.
Triệu chứng: app vẫn chạy nhưng env.DB thành undefined, lỗi báo ra không hề nhắc tới binding.
Quy trình an toàn khi đưa một Worker đang chạy lên GitHub:
1. Mở dashboard, chụp màn hình danh sách binding hiện tại.
2. Chép đủ vào wrangler.toml — mọi binding, mọi biến.
3. Secret thì nạp lại bằng wrangler secret put (secret không nằm trong file).
4. Deploy một lần, rồi mở dashboard đối chiếu đủ danh sách.
Hai loại Worker cần lưu ý khi chuyển:
- Worker dùng Static Assets — phải đưa cả thư mục tài nguyên lên repo, và kiểm cấu hình
[assets]đúng thư mục. - Worker có Durable Object — migration phải nằm trong
wrangler.toml; lần deploy đầu từ CI có thể cần chạywrangler deploythẳng một lần.
Tách để hỏng không lan
Bốn mức tách, từ rẻ tới đắt:
| Mức | Tách cái gì | Khi nào |
|---|---|---|
| 1 | Worker riêng cho mỗi app | Luôn luôn |
| 2 | D1 riêng cho app có người dùng thật | Khi app có khách |
| 3 | Tài khoản Cloudflare riêng cho khách lớn | Khi khách yêu cầu, hoặc khi dữ liệu nhạy cảm |
| 4 | Dùng tài khoản của chính khách | Khi bàn giao, hoặc khi điều khoản dịch vụ yêu cầu |
Mức 4 đáng nghĩ sớm hơn bạn tưởng: nếu khách dùng tài khoản của chính họ, bạn tránh được cả vấn đề điều khoản bán lại hạ tầng (Chi phí) lẫn chuyện bàn giao khi hết hợp tác. Bạn làm phần ứng dụng, họ sở hữu phần hạ tầng.
Gọi giữa các app: service binding
Khi app A cần gọi app B, đừng gọi qua Internet — dùng service binding, nối thẳng Worker này sang Worker kia:
[[services]]
binding = "MAIL"
service = "mt-mail-api"
const r = await env.MAIL.fetch(new Request("https://noi-bo/gui", {
method: "POST",
body: JSON.stringify({ den: "a@b.com", tieu_de: "...", noi_dung: "..." }),
}));
Lợi: không đi vòng ra Internet (nhanh hơn, không tốn thêm request vào), không cần khóa API giữa hai app của chính bạn, và tránh được lỗi khi một Worker gọi một Worker khác cùng tài khoản qua tên miền công cộng — một tình huống hay gây lỗi khó hiểu.
Mười app × 5 phút chú ý mỗi tuần = gần một giờ, chỉ để biết chúng còn sống. Và đó là lúc mọi thứ bình thường.
Ba việc tôi làm để chịu được số lượng:
- Một trang trạng thái chung. Một Worker nhỏ gọi
/api/songcủa tất cả app, hiện một bảng xanh/đỏ. Mở một trang biết hết. Mất một buổi để dựng, tiết kiệm mỗi tuần. - Bảng kê
HA-TANG.mdphải sống — cập nhật ngay khi tạo app mới, không để sau. - Dám khai tử. Mỗi quý nhìn cột "Ai dùng": app nào không ai dùng 3 tháng thì xuất dữ liệu ra, lưu lại, rồi xóa. App chết vẫn tốn chú ý, vẫn là một cánh cửa bảo mật phải canh, vẫn là một dòng trong hóa đơn.
Điểm thứ 3 là điểm khó nhất về mặt tâm lý — không ai muốn xóa thứ mình làm ra. Nhưng mỗi app giữ lại vô ích là một khoản thuế đánh vào khả năng làm app tiếp theo.
Thật. Tôi thử, tôi học, tôi dựng, rồi bỏ đó. Đến lúc cần dọn thì không dám xóa cái nào vì sợ cái đó đang chạy thật.
Cách tôi gỡ, mất một buổi:
1. Liệt kê hết bằng wrangler deployments list.
2. Với mỗi cái, xem lần deploy cuối và có lưu lượng không (dashboard → Requests 7 ngày).
3. Không có lưu lượng + deploy cách đây trên 6 tháng → gần như chắc chắn là đồ bỏ.
4. Trước khi xóa: xuất dữ liệu, tải mã nguồn về máy, rồi mới xóa.
Từ đó tôi có một luật nhỏ: đặt tên Worker thử nghiệm có tiền tố thu-, và mỗi quý xóa hết những cái mang tiền tố đó. Rất đơn giản, và nó giữ cho danh sách sạch.
Chạy các lệnh sau rồi tổng hợp thành bảng Markdown trong HA-TANG.md:
wrangler deployments list
wrangler d1 list
wrangler r2 bucket list
wrangler kv namespace list
Bảng gồm cột: App | Worker/Pages | Tên miền | D1 | R2 | KV | Repo |
Lần deploy cuối | Ghi chú.
Với mỗi Worker, đọc wrangler.toml tương ứng trong C:\NVAI\Claude
(nếu tìm thấy) để điền tên miền và binding. Cái nào không tìm thấy
mã nguồn trên máy thì ghi "CHƯA CÓ MÃ NGUỒN LOCAL" — đó là rủi ro
cần xử lý.
Cuối bảng, liệt kê riêng:
- Các D1 đang bị DÙNG CHUNG bởi nhiều app (nguy cơ nghẽn chéo)
- Các Worker deploy cách đây trên 6 tháng (ứng viên khai tử)
Chỉ đọc, không xóa, không sửa gì.
Dòng "CHƯA CÓ MÃ NGUỒN LOCAL" là dòng đáng sợ nhất và đáng chạy nhất: nó chỉ ra những app bạn không sửa được nữa nếu có sự cố.
- Đủ: bẫy D1 dùng chung, quy ước đặt tên, bảng kê hạ tầng, đưa Worker lên GitHub an toàn, bốn mức tách, service binding, kỷ luật khai tử.
- Đã trả giá thật: D1 dùng chung gây nghẽn chéo; CI ghi đè binding; Worker không còn mã nguồn trên máy.
- Rủi ro còn lại: bài này giả định bạn là người duy nhất vận hành. Khi có người thứ hai, bạn cần thêm quy ước về quyền truy cập và bàn giao — chưa nằm trong phạm vi giáo trình.
- Chạy đề dựng bảng kê, tạo
HA-TANG.mdthật. - Tìm xem có D1 nào đang bị dùng chung không. Có thì ghi cảnh báo vào README của cả hai app.
- Tìm các Worker chưa có mã nguồn trên máy — tải mã đang chạy về, đưa lên repo riêng tư.
- Khai tử một app không ai dùng: xuất dữ liệu, lưu mã nguồn, rồi xóa.
Việc 3 là việc cấp bách nhất. Một app đang phục vụ người thật mà bạn không có mã nguồn là một sự cố đang chờ xảy ra.