Firebase 配额超限:找出失控源并修好

Firestore 读取、Functions 调用、Hosting 带宽——免费档每项指标都有红线。本文教你定位是哪一项爆了,并立刻止血。

你的 Firebase 项目突然全线挂掉:Firestore 读取报 8 RESOURCE_EXHAUSTED: Quota exceeded、Functions 返回 503、Hosting 抛配额错误——但你一行代码都没改。Console 红色横幅写着 Quota exceeded for quota metric ...。这基本只有两种情况:Spark(免费)计划某项指标打到当天硬上限,或者某段失控循环把额度烧光了。

最快的修复:打开 Firebase ConsoleUsage and billing,找出那个停在 100% 的指标。如果是 Firestore 读/写,每日配额在美国太平洋时间午夜前后重置——只要那个吃额度的东西已经停下,服务过一夜会自动恢复。如果等不了,升级到 Blaze(按量付费),并在同一次操作里设好预算告警(见下方 Step 2)。但别停在这里:一定要找出是什么烧光了配额,否则明天额度重置后又会被烧爆。

理解关键:Firebase 不是单一额度,而是几十个独立计费的指标——Firestore reads / writes / deletes / 存储 GB、Functions invocations / GB-seconds、Hosting 数据传输、Cloud Storage 操作、phone auth 验证。任一项打到 cap,依赖它的功能就跟着挂。

2026 年的两个变化(先看这个)

有两项计划变动会让旧的 Firebase 教程失效,排查前先核对你的项目:

  • Cloud Functions 现在必须用 Blaze 计划。 Spark 上已经无法部署或运行 Cloud Functions。如果你的 Functions 还在跑,说明你早就在 Blaze 上了——也就是说你没有硬上限,你面对的是一段在烧真金白银的失控循环。(旧的”Spark 免费 125K invocations/月”已不存在;Blaze 免费额度是 2M invocations/月,超出后计费。)
  • Cloud Storage 已于 2026 年 2 月 3 日从 Spark 移除。 Spark 项目无法访问默认 bucket,Storage API 调用返回 402/403。如果你的存储坏了,那不是配额问题,而是这次计划变动——要用 Cloud Storage 必须升 Blaze。

所以动手前先确认:你在 Spark(硬性每日上限,不能透支)还是 Blaze(免费额度用完后计费,会透支)。Console 左上角齿轮 / Usage and billingDetails & settings 能看到你的计划。

免费额度(截至 2026 年 6 月)

以下就是你可能撞到的红线。Spark 是硬封顶(服务停摆,不能透支);Blaze 给同样的免费额度,超出后开始计费。

指标免费额度计划
Firestore reads50K / 天Spark + Blaze
Firestore writes20K / 天Spark + Blaze
Firestore deletes20K / 天Spark + Blaze
Firestore 存储数据1 GiB 总量Spark + Blaze
Hosting 存储10 GBSpark + Blaze
Hosting 数据传输360 MB / 天Spark + Blaze
Functions invocations2M / 月仅 Blaze
Functions GB-seconds400K / 月仅 Blaze
Cloud Storage 存储5 GB仅 Blaze
Cloud Storage 下载1 GB / 天仅 Blaze
Phone auth 验证按地区,每日小额度Spark + Blaze

注意:旧的 Hosting “10 GB/月带宽”已不是现在的计费方式——Hosting 免费传输现在是 360 MB/天(30 天大约 10.5 GB,但它是按天卡的,所以哪怕你整月流量很小,某天突然火一下也会触发)。

你属于哪一种情况?

症状最可能的原因跳转
reads 到 100%,用户数正常客户端监听循环或缺 limit()原因 3、7 / Step 5、7
reads 暴涨 100x,用户数没变安全规则开放 + 爬虫/攻击者原因 6 / Step 3
Functions invocations 上百万Function 自触发循环原因 2 / Step 4
Hosting 传输对照流量异常大大文件 / 视频走 Hosting 分发原因 4 / 预防
Storage 报 402/403,之前还能用Cloud Storage 退出 Spark(2026 年 2 月)升级 Blaze
存储超 1 GiB,用户却没几个没设 TTL,日志/会话堆积原因 5

常见原因

按命中率从高到低排列。

1. Spark 计划某项打到每日硬上限

Spark 是硬封顶——你不能透支,指标到线就停。最常见的是 Firestore reads(50K/天)。只要吃额度的东西停下来,服务会在下一次每日重置(太平洋时间午夜)后恢复。

如何判断:Console → Usage and billing → 看哪一项 100%。客户端日志里的报错:8 RESOURCE_EXHAUSTED: Quota exceeded

2. Functions 死循环烧调用数

经典反模式——Firestore 写触发器又往同一个 collection 写:

// 写 Firestore 触发 onWrite → 又写 Firestore → 又触发 onWrite → ...
exports.onUserUpdate = onDocumentWritten('users/{uid}', async (event) => {
  await db.doc(`users/${event.params.uid}`).update({...}); // 触发自身
});

在 Blaze 上(Functions 现在都跑在这),这能在几小时内烧完 2M 免费 invocations,然后整月剩下的全计费。

如何判断:Console → Functions → 看单个函数的 invocations 异常高(百万级)。Cloud Logging 里也能看到。

3. 客户端死循环读 Firestore

function Comp() {
  useEffect(() => {
    onSnapshot(query, snap => {
      setItems(snap.docs);  // 触发 re-render → useEffect 又跑 → 又 subscribe
    });
  }); // 缺 deps array
}

几个活跃用户就能在一小时内把 50K reads 清零。

如何判断:Console → FirestoreUsage → reads/sec 相对活跃用户数异常地高。

4. 大文件 / 视频撑爆 Hosting 数据传输

把一个 100 MB 的视频放 Firebase Hosting,几百个人看,几分钟就超过 360 MB/天 的免费传输。

如何判断:Hosting 数据传输对照你的真实访问量异常大。

5. 没设 Firestore TTL,老数据堆积

Firestore 里存的 logs / sessions / analytics 没清理,迟早把 1 GiB 存储上限撑爆。

如何判断:存储超过 1 GiB,但真实用户数据量很小。

6. 爬虫 / 攻击者扫数据

宽松的 Firestore 安全规则让任何拿到你客户端配置的人都能直接 query 任意 collection。爬虫找到 endpoint 后,reads/writes 双爆。

如何判断:用户数没变,但 reads 暴涨 100x。看 Firestore Usage 曲线有没有在没上线新功能的情况下出现台阶式跳升。

最短修复路径

Step 1:定位是哪一项爆了

Firebase Console → Usage and billing
  → 看 Firestore / Functions / Hosting / Storage / Auth 各项
  → 到 100% 的那项(Blaze 上则是曲线突然拉直上扬的那项)就是元凶

看仪表盘,别凭感觉猜。

Step 2:紧急止血——升 Blaze + 设预算告警

如果你需要立刻恢复(Spark 要等太平洋时间午夜才重置),升级到 Blaze,并在同一次操作里设好预算告警:

1. Google Cloud Console → Billing → Budgets & alerts → Create budget
2. Budget amount:你愿意承受的最大月支出(比如 $20)
3. Alert thresholds:50%, 90%, 100%
4. Alert recipients:你的 email
5. 可选硬停:把 Pub/Sub 预算通知接到一个 Cloud Function,
   调用 projects.updateBillingInfo 关掉计费

现实提醒:预算告警不会封顶消费——它只是给你发邮件。唯一真正的硬停是 Pub/Sub 触发的”关闭计费”自动化,而且它生效前已经发生的费用照样要付。(Google 官方的封顶指南 有这个 Cloud Function 的完整写法。)如果想要零透支风险的硬上限,那就留在 Spark,改去修失控源。

Step 3:定位并停掉失控源

失控源 = 客户端循环 / Function 循环 / 攻击者

Function 循环:
  Console → Functions → 找 invocations 异常那个 → Disable
  (或删掉部署,改好再重新 deploy)

客户端循环:
  回滚最新前端部署
  或 hotfix useEffect(加 deps array——见 Step 5)

攻击者 / 规则开放:
  立刻全锁死,再按需逐项放开:
// 紧急锁死——贴上、部署、喘口气,再选择性放开
match /{document=**} { allow read, write: if false; }

部署:firebase deploy --only firestore: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,  // 打标记,让下一次触发短路掉
  });
});

更彻底的做法:把源数据和派生数据拆成两个 collection,让写入路径喂不回触发器。

Step 5:客户端 useEffect 加 deps

useEffect(() => {
  const unsub = onSnapshot(query, snap => setItems(snap.docs));
  return () => unsub();   // 卸载时清理监听
}, []);   // 空 deps:只订阅一次,而不是每次渲染都订阅

Step 6:收紧 Firestore 安全规则(最小权限)

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。部署前先在 Console → FirestoreRulesRules Playground 里测一遍规则。

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

如何确认已修好

  1. Console → Usage and billing:之前到 100% 的指标应该停止增长(Blaze 上则是 reads/sec 曲线几分钟内回落到基线)。
  2. Functions 标签页:调用速率回到正常,没有单个函数飙升。
  3. 客户端:日志里不再出现 8 RESOURCE_EXHAUSTED,读取成功。
  4. Spark 上,完整服务会在下一次太平洋时间午夜重置后恢复——但前提是失控源已经死掉。如果指标重新填满、第二天又触发,说明你只压住了症状没堵住源头:回到 Step 3。

预防建议

  • 上线先在 Google Cloud 设预算告警(50% / 90% / 100%);如果在 Blaze 上,再接好 Pub/Sub 的”关闭计费”自动化。
  • Firestore 规则默认 deny,只放开你确实需要的路径。绝不要上线 allow read, write: if true;
  • Function 永远不要订阅自己也会写的 collection。如果非要,用 flag 打断循环(见 Step 4)。
  • useEffect 一定要有 deps array;用 eslint-plugin-react-hooks 强制。
  • Firestore query 永远加 limit(),开发阶段也要——对一个还在增长的 collection 做无界 getDocs 就是一颗慢动作配额炸弹。
  • 大文件 / 视频别走 Firebase Hosting(免费传输只有 360 MB/天)。用 R2 / S3 + CDN。
  • 给 logs / sessions / analytics 加 Firestore TTL policy,让存储自动清理。
  • 监控 reads/sec 和 writes/sec,异常 spike 立刻告警。
  • 上线前估算月成本——比如 Firestore reads 是 (每日 reads × 30 ÷ 100000) × $0.06 + ...——心里有个”正常值”的概念。

常见问题

我什么都没改,Firebase 怎么就挂了? 某个免费档指标打到了每日上限——通常是 Firestore reads(50K/天),背后是客户端监听循环、Function 循环,或利用开放规则的爬虫。它积累了几个小时,然后一下子越线。

它会自己恢复吗? 在 Spark 上,Firestore 每日配额在美国太平洋时间午夜前后重置,所以服务过夜会恢复——但前提是吃额度的东西已经停了。如果它还在跑,第二天又会触发。

Spark 还是 Blaze 能保我不被账单吓到? Spark。它不能透支,指标到 cap 就停。Blaze 给同样的免费额度,但之后无硬上限地计费,除非你自己搭好 Pub/Sub 的”关闭计费”自动化。单靠预算告警只会发邮件。

我的 Cloud Storage / Cloud Functions 突然返回 402/403 或部署不了。是配额吗? 不是——这两个现在都要 Blaze 计划。Cloud Storage 于 2026 年 2 月 3 日退出 Spark,Functions 也早就要 Blaze 了。把项目升到 Blaze(并设预算告警)才能用。

怎么找出到底是哪个函数在循环? Console → Functions 会显示每个函数的调用数,按它排序。把 Cloud Logging 过滤到那个函数名,能看出每次运行是不是又触发了一次写。在改代码之前,先 Disable 那个函数止血。

在 Firebase Console 里翻数据会算进我的读配额吗? 会——Console 渲染文档时会去读 Firestore,这也计数。已经接近 cap 时,别在 Data 标签页反复刷大 collection。

相关阅读

标签: #Firebase #排查 #排查