你 Firebase 项目突然全线挂了:Firestore 读不到数据、Functions 全部 503、Hosting 返回 quota error——但你没改任何代码。Console 红色横幅写着 “Quota exceeded for quota metric …”。这是 Firebase Spark(免费)计划的硬上限触发,或 Blaze 计划碰到某个 per-day quota。问题不在于”配额满了”,而在于你没设过任何告警,所以满了你才知道。
理解关键:Firebase 不是单一额度,而是几十个 metric 分别计费(Firestore reads / writes / deletes / storage GB / Functions invocations / GB-seconds / Hosting bandwidth / Auth verifications…)。任一项打到 cap 就 503。
常见原因
按命中率从高到低:
1. Spark 免费计划某项打到硬上限
Spark 是硬封顶(不能透支),常见红线:
Firestore reads: 50K / day
Firestore writes: 20K / day
Firestore deletes: 20K / day
Firestore storage: 1 GB
Functions invocations: 125K / month
Hosting bandwidth: 10 GB / month
Auth verifications: unlimited (但 phone auth 是 100 / day)
如何判断:Firebase Console → Usage and billing → 看哪一项 100%。
2. Functions 死循环烧调用数
常见反模式:
// 写 Firestore 触发 onWrite → 又写 Firestore → 又触发 onWrite → ...
exports.onUserUpdate = onDocumentWritten('users/{uid}', async (event) => {
await db.doc(`users/${event.params.uid}`).update({...}); // ❌ 会触发自身
});
24 小时能烧完 1 个月免费额度。
如何判断:Console → Functions → 看单个函数的 invocations 异常高(百万级)。
3. 客户端死循环读 Firestore
function Comp() {
useEffect(() => {
onSnapshot(query, snap => {
setItems(snap.docs); // 触发 re-render → 又 useEffect → 又 subscribe
});
}); // ❌ 缺 deps array
}
10 个活跃用户能让你免费额度 1 小时清零。
如何判断:Console → Firestore → Usage → reads/sec 不正常地高。
4. 大文件 / 视频 hosting 带宽爆
把 100MB 视频放 Firebase Hosting,1000 个用户看 = 100GB,超 10GB 免费额度 10 倍。
如何判断:Hosting bandwidth 异常高,对照你的访问量。
5. 没设 Firestore TTL,老数据堆积
Firestore 存的 logs / sessions / analytics 没清理,几个月就把 1GB 撑爆。
如何判断:Storage 超 1GB 但你没那么多用户数据。
6. 恶意爬虫 / 攻击者扫数据
你的客户端 SDK 调 Firestore 没充分 security rules,攻击者发现 endpoint 后能直接 query 任意数据,bandwidth 和 reads 双爆。
如何判断:用户量没变但 reads 突增 100x。
最短修复路径
Step 1:定位哪一项爆了
Firebase Console → Usage and billing
→ 看 Firestore / Functions / Hosting / Auth 各项
→ 100% 红色那项就是元凶
不要凭感觉猜,看仪表盘。
Step 2:紧急止血——升 Blaze + 设硬上限
如果你需要立刻恢复,升 Blaze(按用量付费)但同时设 Budget Cap 和 Alert:
1. GCP Console → Billing → Budgets & alerts → Create budget
2. Budget amount: $20(你愿意承受的最大月支出)
3. Alert thresholds: 50%, 90%, 100%
4. Alert recipients: 你的 email
5. 重要:勾选 "Disable billing" actions 防止失控
注意:Blaze 不能真的”硬封顶”消费——这只是 alert + auto-disable,已发生的费用还是要付。
Step 3:定位异常源
异常源 = 用户脚本 / 死循环 / 攻击者
如果是 Functions 死循环:
立刻去 Console → Functions → 找异常那个 → Disable
如果是客户端死循环:
立刻回滚最新前端版本
或 hotfix 前端 useEffect 加 deps
如果是攻击者:
立刻临时把 Firestore rules 改紧(match /{document=**} { allow read, write: if false; })
然后慢慢恢复合理 rules
Step 4:把 Functions 加防呆
// 防止自己触发自己
exports.onUserUpdate = onDocumentWritten('users/{uid}', async (event) => {
// 如果是 _serverUpdate flag 触发的,跳过避免循环
const after = event.data?.after.data();
if (after?._serverUpdate) return;
await db.doc(`users/${event.params.uid}`).update({
derived: ...,
_serverUpdate: true, // mark
});
});
或更彻底——分两个 collection 隔离写入。
Step 5:客户端 useEffect 加 deps
useEffect(() => {
const unsub = onSnapshot(query, snap => setItems(snap.docs));
return () => unsub();
}, []); // ✅ empty deps
Step 6:紧 Firestore security rules
最小权限:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{uid} {
allow read, write: if request.auth.uid == uid;
}
match /posts/{post} {
allow read: if true;
allow write: if request.auth != null
&& request.resource.data.authorId == request.auth.uid;
}
}
}
部署:firebase deploy --only firestore:rules
Step 7:减少不必要 reads
// 慢:每次都全量 query
const all = await getDocs(collection(db, 'posts'));
// 快:分页 + cache
const first = await getDocs(query(
collection(db, 'posts'),
orderBy('createdAt', 'desc'),
limit(20)
));
// 客户端 React 用 SWR / TanStack Query 缓存,避免重复 fetch
预防建议
- 所有项目上线前先在 GCP Billing 设 Budget Alert 50% / 90% / 100%,邮件通知
- Firestore security rules 写紧——默认 deny all,按需 allow
- Functions 永远不要订阅自己写的 collection;如必须,用 flag 防循环
- 客户端 useEffect 一定有 deps array;用 lint 规则强制
- 大文件不要 Firebase Hosting,用 R2 / S3 + CDN
- 老数据加 Firestore TTL policy 自动清理(log / session / analytics)
- 监控 reads/sec / writes/sec,异常 spike 立刻 alert
- 项目用前估算月成本:(daily reads × 30 × $0.06/100K) + … 心里有数