Spotify PB 级用户数据,AI Agent 用一行 SQL 就查了
AI Agent 要查你的 Spotify 听歌记录——"我 2019 年听得最多的歌手是谁?""给我播一首我从没听过的歌。""今年我的音乐品味变了多少?"
这些问题的答案,全埋在 Spotify 的 PB 级用户行为数据里。但 Agent 怎么查?
Agent 需要的不是自定义 API,不是微服务。直接给它 SQL。一行就行。
6 月 24 日,在阿姆斯特丹的 DuckCon #7 上,Spotify 工程师 Kian Mehrabani 分享了他们怎么给用户听歌历史搭了一层 SQL,专门服务 AI Agent。这场分享的标题很直白——"How we built a SQL layer over our user listening history for agentic access"。
我反复看了 slides 和录像,想跟你聊聊这个案例背后真正值得关注的东西。
● ● ●
Agent 要的不是 API,是 SQL
Spotify 手里握着 PB 级的用户行为数据。用户每次播放、跳过、收藏、加入歌单,全被记录下来。
这些数据以前怎么用?两条路。
一条是低延迟的近期数据通道,查你最近听了什么,秒级返回,给推荐系统用。另一条是长期预计算视图,提前把"年度回顾""月度听歌报告"跑好,等到年底推给你。
这两条路覆盖了过去的需求,但 AI Agent 一上来就打破了所有假设。
Agent 的问题不可预测。用户可能问"2019 年 top artist",也可能问"从没听过的歌",还可能问"今年和去年的品味差异"。你没法提前预计算所有维度。你需要的是一层能随时表达任意查询的接口。
Spotify 的答案是:给 Agent 一层 SQL。
● ● ●
架构比你想象的简单
整个系统的设计可以用一句话概括:stateless Java gRPC 服务,内嵌 DuckDB(内存模式),请求进 Protobuf,结果出 Arrow。
展开来看是这样的:
- 01gRPC 服务接收 Agent 发来的查询请求,用 Protobuf 序列化
- 02请求转成 SQL,丢给嵌入在进程里的 DuckDB
- 03DuckDB 在内存里跑查询——无磁盘 IO,全部在 RAM 里完成
- 04结果用 Apache Arrow 返回,零拷贝,直接喂给下游
这里最妙的是 Arrow。传统的序列化方式是"查出来 → 序列化成 JSON → 反序列化 → 再用",每一步都在拷贝数据。Spotify 用 Arrow 格式,DuckDB 写到内存里的 Arrow buffer,下游直接读同一块内存。数据不动,指针动。
架构图上还有一个关键细节:按 Pool 和 Key 分配内存。为什么要这么做?一会儿说。
这个服务是 stateless 的。没有外部依赖,没有缓存层,没有额外的存储。请求进来,DuckDB 在本地内存里跑,结果返回,进程不保留任何状态。扩容只需要加实例。
PB 级数据 + stateless + SQL = 任意查询。 这个等式在几年前是不可想象的。
● ● ●
jemalloc 救了 P50
一个你可能会忽略的细节:Spotify 把默认内存分配器换成了 jemalloc。
DuckDB 在内存模式下大量分配和释放内存。默认的 glibc malloc 在这种场景下会产生严重的内存碎片——小块内存反复分配释放后,即使总可用内存还很多,也可能分配不出一个连续的大块。
jemalloc 对多线程分配做了大量优化,碎片控制好得多。
但光换分配器还不够。Spotify 还按 Pool 和 Key 做了内存分区管理。不同用户的数据查询跑在不同的内存池里,互不干扰。这直接控制住了 P50 延迟——你不会因为某个重查询把所有人的响应时间都拖慢。
坦白讲,这两个优化都不是 DuckDB 层面的创新,而是工程落地时的必修课。但恰恰是这些细节,说明 Spotify 是认真把 DuckDB 当生产基础设施在跑,不是 PoC。
● ● ●
同一天的三个信号
Spotify 不是 DuckCon #7 上唯一讲 Agent + DuckDB 的团队。
同一场会议,MotherDuck 发布了 Flights——一个让 DuckDB 直接查询远程数据的协议。Altertable.ai 展示了 "Grep your lakehouse"——用自然语言在数据湖里做探索式查询,底层还是 DuckDB。
三个 talk,三条不同的路线,指向同一个方向:DuckDB 正在变成 AI Agent 的默认数据引擎。
这不是巧合。DuckDB 有几个天然适合 Agent 的特性:
嵌入式。 不需要单独部署一套数据库集群。duckdb 就是一个库,link 进你的进程就能跑。Spotify 的 gRPC 服务就是一个很好的例子——DuckDB 随着 Java 进程启动,没有额外的运维负担。
无服务化。 内存模式 + stateless 部署,天然适合容器化。起一个实例就是一个完整的查询引擎,不需要考虑数据分片、主从复制、连接池管理。
SQL 原生。 Agent 不需要学新的查询语言。LLM 生成 SQL 的能力已经被充分验证,而 SQL 的表达力足够覆盖绝大多数分析查询。
Arrow 零拷贝。 从查询引擎到 Agent 的推理链路,数据不需要序列化/反序列化,延迟极低。
这几条加在一起,你会发现 DuckDB + Agent 的组合几乎是必然的。不是 DuckDB 有多革命性,而是这个场景正好踩在了它的设计原点上。
● ● ●
最后
Spotify 的这个案例,对我来说最有冲击力的不是技术有多新,而是简洁。
PB 级数据,多年积累的用户行为,被抽象成了一层 SQL。AI Agent 不需要知道底层数据怎么存的、索引怎么建的、分区怎么拆的——它只需要写一行 SQL。
DuckDB 让这件事变得 trivial。而这恰恰是最可怕的信号。
当 SQL 变成 Agent 的默认数据接口,传统数据库和 API 网关之间的那条线就开始模糊了。你精心设计的 REST API、GraphQL schema、微服务之间的数据契约——在一个能直接写 SQL 的 Agent 面前,变成了额外的复杂度。
当然,Spotify 的架构也有它的边界。内存模式意味着单次查询的数据量受限于单机内存,不是所有分析场景都适用。但考虑到这是用户级别的查询(单个用户的行为数据),这个限制在实践上是可接受的。
Kian 在 slides 最后一页写了一句话:"AI Agent 不需要自定义 API。一行 SQL 就够了。"
参考:Spotify 演讲 slides https://blobs.duckdb.org/events/duckcon7/kian-mehrabani-spotify-sql-agentic-access.pdf | YouTube 录像 https://youtu.be/-9GY1CCJG5o