Data & Search / 数据检索
亿级数据检索:索引、查询与多租户设计
数据量上来之后,检索慢通常不是“Elasticsearch 配置不对”,而是索引模型、查询路径和权限过滤从一开始就没有设计好。
百万级数据时,很多问题不明显;到了千万、亿级,查询就会开始抖动:列表页越翻越慢、某些租户特别慢、写入高峰期查询超时、权限过滤把缓存全部打穿。真正稳定的检索系统,需要同时考虑数据模型、查询模式和资源隔离。
一、先分清事务查询与检索查询
事务查询按主键读写,要求强一致;检索查询按条件组合、排序和分页,允许最终一致。两者混用是很多系统变慢的起点:把 Elasticsearch 当主库,或者用 MySQL 的 like '%keyword%' 硬扛全文检索。
更稳妥的分工是:MySQL 等 OLTP 数据库保存权威数据,负责事务和主键查询;检索集群保存为查询优化的副本,负责条件筛选、全文检索和聚合。通过 CDC 或消息队列同步,接受秒级延迟,并明确重建索引的流程。
二、索引建模:keyword、text 与 doc_values
字段类型决定了查询能力和存储成本。常见误区是所有字符串都映射成 text,结果聚合、排序和精确过滤都用不了;或者全部用 keyword,又失去了分词检索能力。
| 字段类型 | 适用场景 | 注意事项 |
|---|---|---|
| keyword | 精确过滤、聚合、排序 | 注意高基数字段的堆内存和缓存 |
| text | 全文检索、分词匹配 | 通常配合 keyword 子字段使用 |
| 数值 / 日期 | 范围过滤、排序 | 利用 doc_values,避免脚本计算 |
| nested | 对象数组需要独立匹配 | 查询成本高,数量要控制 |
索引建模还要控制字段数量。关闭动态映射,建立明确的 mapping 模板,避免日志里多一个字段就多一份索引开销。对于多租户系统,公共字段和租户字段要提前规划,避免每个租户一套完全不同的结构。
三、查询路径:过滤优先,避免深分页
查询慢,往往慢在“扫描了太多文档”。优化顺序应该是:先用过滤条件缩小候选集,再做全文匹配和排序。过滤条件尽量走 filter 上下文,可以利用缓存,也不参与打分。
- 避免
from + size深分页。翻到几千页时,协调节点需要收集大量结果;改用search_after或游标。 - 限制返回字段。列表页只取必要字段,不要整条文档返回。
- 谨慎使用脚本。脚本排序和脚本字段无法利用索引,容易成为热点。
- 控制聚合规模。高基数 terms 聚合会消耗大量内存,必要时预聚合或走分析库。
- 设置合理的
track_total_hits。不需要精确总数时,不要强制全量计数。
四、多租户与权限过滤
多租户检索有三种常见做法:每租户独立索引、共享索引加租户字段、按租户路由分片。独立索引隔离性好,但索引数量多、运维成本高;共享索引成本低,但必须保证每个查询都带租户过滤,否则会越权。
权限过滤要下推到查询层,而不是先查出结果再在应用层过滤。后者不仅慢,还容易在分页时出现数量不一致。对于复杂权限,可以把用户可见的资源范围提前计算成过滤条件,并控制条件数量,避免查询过于庞大。
多租户系统里,最危险的不是慢查询,而是漏掉租户条件导致的越权。租户 ID 应该由服务端从会话中注入,不能信任前端参数。
五、写入、刷新与分片
写入和查询会互相影响。批量写入使用 bulk,控制单批大小;根据业务对实时性的要求调整 refresh_interval,不要为了“立刻可见”把刷新频率调得过高。分片不是越多越好,单分片控制在合理的数据量范围内,避免大量小分片增加集群开销。
副本数影响查询吞吐和容灾能力,但也增加写入和存储成本。写入高峰期可以通过调整副本、限流和错峰重建索引来保护查询链路。
六、缓存与热点
检索结果的缓存要区分“可缓存”和“不可缓存”。公共榜单、字典和稳定聚合适合缓存;带用户权限、实时库存或个人视角的查询要谨慎。缓存键必须包含租户和权限维度,避免不同用户命中同一份数据。
热点问题可以用本地缓存、请求合并和限流处理。对于突发流量,提前做压测,确认缓存击穿时的兜底策略,而不是等线上出现雪崩再补。
七、压测与可观测性
检索系统的优化必须用数据说话。压测时关注 P95/P99 延迟、错误率、线程池队列和 GC,而不是只看平均响应时间。开启慢查询日志,定期用 profile API 分析查询计划;对分片分布、磁盘水位、段合并和缓存命中率建立监控。
线上问题通常有迹可循:某个租户数据量突然增长、某个查询条件基数过高、某个分片成为热点。把这些指标和业务维度关联起来,定位会快很多。
八、落地清单
- 明确 OLTP 与检索集群的职责边界,设计同步和重建流程。
- 关闭动态映射,按查询模式设计字段类型和索引模板。
- 所有查询先过滤再检索,禁止无界深分页。
- 租户与权限条件由服务端注入,并在查询层下推。
- 压测写入与查询的混合场景,建立慢查询和容量告警。
小结
亿级检索不是一个组件能解决的问题,而是数据模型、查询路径、权限隔离和容量治理的组合。把这些基础工作做在前面,系统才有机会在数据增长时保持稳定。