alitrack

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。

展开来看是这样的:

  1. 01gRPC 服务接收 Agent 发来的查询请求,用 Protobuf 序列化
  2. 02请求转成 SQL,丢给嵌入在进程里的 DuckDB
  3. 03DuckDB 在内存里跑查询——无磁盘 IO,全部在 RAM 里完成
  4. 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