4.2 性能避坑:查询设计、索引与监听滥用
Firestore 快不快,一半取决于它自己,另一半取决于你的查询长得像不像它希望的样子。这一节讲的都是我用流量和账单换来的教训,按查询、索引、监听、文档粒度四条线展开。
查询设计:别把数据库当数组用
最常见的反模式是:整个集合一把拉回客户端,再在内存里过滤排序。数据量小的时候毫无症状,等集合涨到几千上万条,首屏变慢、流量飙升、客户端内存告警会一起找上门。
Firestore 的强项就是服务端过滤,where、orderBy、limit 能在数据库层把数据裁到最小。宁可多花点心思设计查询,也别把过滤逻辑搬到客户端。
分页也是查询设计的一部分。limit 控制每页条数,翻下一页时用上一页最后一条文档做游标,也就是 startAfter 配合文档快照,而不是用偏移量思维硬算页码。数据在实时数据库里不停地变,基于位置的偏移天然不稳,基于文档的游标才是 Firestore 的原生姿势。
索引:便利与泛滥之间
每加一个组合查询,Firestore 就可能要求一条复合索引。控制台报错信息里直接放着一键创建的链接,点一下就解决,体验好到让人上瘾。
问题在于指数膨胀:每多一个查询组合,索引就多一条,写操作要同步维护的索引也就多一份。索引建多了,写放大、存储开销、排查问题时面对的长列表,全是日后的债。我的原则是:建索引之前先想清楚,这个查询是不是真的需要,能不能复用已有索引,能不能调整字段顺序少建一条。一键创建的便利留给原型期,进入正式开发后,索引变更应该和改表结构一样走评审。
监听滥用:实时不等于常驻
第二个上瘾点是实时监听。很多人在应用入口挂一个巨大的监听,订阅整个集合,从此不管用户在哪个页面、看不看这些数据,监听都活着。结果是客户端内存被长期占用、流量持续产生,断线重连风暴一来,体验雪崩。
更健康的模式是按页面订阅:进入页面建立监听,离开时退订,监听生命周期跟着视图走,全局只保留真正需要全程实时的一小撮数据。
| 方式 | 优点 | 代价 |
|---|---|---|
| 巨型常驻监听 | 写起来省事,数据随处可用 | 内存常驻、流量浪费、断线重连易雪崩 |
| 按页面订阅退订 | 资源跟着视图走,流量可控 | 需要认真管理监听生命周期 |
离线缓存也值得单独说一句。本地持久化带来的秒开与弱网体验非常香,但收益建立在监听覆盖合理的前提上;首次冷启动时本地没有缓存,该等的网络请求一样要等,别指望离线缓存把首屏问题一并解决。
文档粒度:热点文档是隐形天花板
Firestore 对单个文档的高并发写入有限制,这是很多人踩到才发现的硬约束。典型场景是计数器:热门内容的点赞数、浏览量都写进同一个文档,流量一上来就写入排队、频频失败,症状看起来像灵异事件。
解法是换思路:把一个计数拆成多条子文档分片,每次写随机命中一片,读取时汇总求和——这就是分布式计数的思路。写的时候多花一点聚合功夫,换来的是把写热点摊平。同理,任何被高频并发写的单文档设计,都应该先问一句:它扛得住吗?
老规矩,上表总结:
| 症状 | 病因 | 处方 |
|---|---|---|
| 集合变大后首屏变慢、流量飙升 | 整表拉回客户端再过滤 | 服务端 where 加 orderBy 加 limit,客户端只做展示 |
| 翻页时数据重复或跳变 | 用偏移量思维硬分页 | limit 加文档游标,基于最后一条文档翻页 |
| 写入越来越慢,索引列表越滚越长 | 复合索引无节制膨胀 | 建前评审、复用已有索引、调整字段顺序 |
| 流量异常,断线后体验雪崩 | 大监听常驻全应用 | 按页面订阅退订,全局只留必要监听 |
| 计数器类写入排队、失败 | 热点文档高并发写 | 拆分片求和的分布式计数思路 |
性能问题大多不是突发的,而是随数据量增长慢慢显形。这些处方越早用上,代价越小。
