当你的AI助手问“我去年夏天听了什么歌?”,它需要从EB级数据湖中快速找到你的听歌记录。如果用传统分布式SQL引擎(如Trino、BigQuery)来查,即使只取一行数据,也可能要等上几秒——因为查询规划、任务调度、元数据扫描的开销远大于实际数据读取。Spotify最近公开的RAP(Random Access Parquet)方案,正是为了解决这个矛盾:让数据湖上的在线点查询(point query,即根据单个键精确查找一行数据)达到毫秒级,媲美Bigtable等专用KV存储,而且直接查询现有的Parquet文件,无需额外复制数据。

分布式SQL引擎在大数据湖中查找单条记录的路径:需要经过分区裁剪、布隆过滤、文件内元数据扫描等多步依赖读取,每一步都增加延迟。
问题:为什么数据湖上的点查询这么慢?
Spotify的数据架构中,PB级热数据在Bigtable中服务在线请求,但EB级历史数据存储在GCS数据湖中。底层云存储本身并不慢——GCS单次请求30-100ms,S3 Express One Zone和GCS Rapid Storage甚至达到个位数毫秒。瓶颈出在查询引擎上:Trino、BigQuery这类分析引擎为吞吐量优化,即使只查一行,也要经过解析、规划、调度、元数据发现等步骤,额外增加数秒延迟。
更具体地说,要回答“去年夏天听了什么”,听歌历史数据横跨数十亿用户,每天产生数千个Parquet文件。假设夏天90天,每天1000个文件,就是9万个文件。即使按用户ID分桶(每天1000桶),候选文件从9万降到90;再用布隆过滤器(Bloom filter,一种概率性数据结构,可快速判断元素是否在集合中)排除不包含该用户ID的文件,最终可能只剩12个文件(用户实际活跃的天数)。
但问题还没完:每个文件内部,要找到某一行数据,需要一串依赖读取——先读文件尾部(footer)获取行组元数据,再扫描键列定位行号,然后通过列索引和页索引找到值列对应的数据页。每个步骤都是一次往返请求,而且必须等上一步完成才能开始下一步。12个文件,每个文件多次往返,加上并发查询争抢IOPS,延迟可想而知。

RAP方法的核心:用外部索引直接定位,消除依赖读取链。
RAP:用外部索引打破依赖链
RAP的思路很直接:既然依赖读取是瓶颈,那就提前计算好每个键的位置,把链式查找变成一次索引查找+并行数据读取。具体来说,RAP构建一个外部索引(external index,独立于数据文件的索引),记录每个键(如用户ID)出现在哪些文件的哪些行。查询时,先O(1)查索引,得到文件和行号;然后通过缓存的文件元数据(页位置)直接发起精确的范围读取(ranged read),只取需要的字节。这些读取可以并行发出,互不依赖。
与Parquet内置的PageIndex或布隆过滤器不同,那些是概率性的,只能缩小扫描范围,不能消除扫描。RAP的索引是确定性的:给定一个键,直接返回精确位置,完全不需要扫描文件。
索引构建与存储
RAP索引构建器读取现有Parquet文件的页脚和页位置,扫描键列,建立键到位置的映射,然后写入索引文件。索引是多重映射(multimap),一个键可能对应多个文件中的多行。每个条目包含:键、文件ID(字典编码)、行号、可选的值计数(用于分页)。索引大小约为源数据的1/10:索引TB级数据产生GB级索引,索引PB级数据产生TB级索引。大规模索引通过哈希分桶自然分布。

索引构建流程:读取现有Parquet文件的元数据,扫描键列,输出索引片段。

索引条目格式:紧凑设计,支持多文件多行映射。

外部索引与Parquet内置索引的对比:前者确定性,后者概率性。
针对Parquet的深度优化
RAP可以直接查询未修改的Parquet文件,但会读取包含目标行的整个数据页(可能4MB只取100字节)。对于延迟或成本敏感的场景,可以在写入时对文件进行优化,使读取更精确。优化分为三类:
集中键数据
- **按键排序**:确保同一键的所有行在文件内连续,集中在尽可能少的页中。很多管道已经产生排序输出。
- **哈希分桶**(如Spark、Scio SMB、Iceberg bucket transform):每个键确定性地映射到每个分区的一个文件,减少跨文件数量。
- **共分组(co-grouping)**:通过ARRAY_AGG等操作将同一键的多行聚合成一行嵌套结构,每键每文件只出现一次。
- **更粗粒度分区**:从每天分区改为每周分区,每个键每年从365个文件降到52个,减少索引条目和并行读取数,但牺牲分区裁剪能力。
减少每次读取字节
- 使用更小的页大小(page size),使单次读取更轻量。
- 对点查询列禁用压缩或使用轻量压缩,避免解压开销。
- 将点查询列与批量分析列分离存储,或使用不同的列编码。
减少读取次数
- 将多个相关列的数据放在同一个页中,一次读取获取所有需要字段。
- 使用列级布隆过滤器进一步减少不必要的页读取(但RAP的索引已经精确到行,这一步收益有限)。

三类优化策略的示意图:集中数据、减少字节、减少次数。
启发与适用场景
RAP的核心思想——用外部索引将链式查找变为并行直接读取——适用于任何需要从大规模列式存储中进行低延迟点查询的场景。对于已经使用Parquet作为统一存储格式的公司,RAP提供了一条无需复制数据即可获得在线查询能力的路径,避免了维护两套系统(数据湖+KV存储)的成本和一致性负担。
但需要注意:RAP主要针对点查询(point query),不适合分析型扫描查询;索引的构建和更新需要与数据管道同步,适合批处理场景而非实时写入;索引本身也有存储成本(约数据的1/10),需要权衡。另外,RAP的优化措施(排序、分桶、共分组)需要写入时干预,如果数据管道无法调整,则只能使用未优化模式,延迟可能仍不理想。
总体而言,RAP展示了在云存储性能不断提升的背景下,查询引擎架构的瓶颈如何通过巧妙的设计来突破。对于正在构建AI Agent或在线个性化服务的团队,这是一个值得关注的方向。
内容与图片版权归原作者所有 · 原文: https://engineering.atspotify.com/2026/7/indexing-the-data-lake-for-online-point-queries/