AI改变数据接口,而非查询引擎
有效阅读仅保存在当前浏览器,用于「为你」推荐;暂停或清除记录。
DataHot 速览
文章介绍一个灾难恢复分析工作流:AI agent生成受限SQL或Python,DuckDB从Parquet计算可复现事实,agent再解释结果。作者提出将确定性计算与概率性推理分离,并给出四条信号判断嵌入式引擎是否足够、何时需要托管lakehouse。生产级agent分析应保留源数据、配置、派生事实和模型输出,以支持验证与回放。
为什么值得关注:这篇文章给出AI分析Agent与执行引擎分工的可落地架构,并讨论DuckDB嵌入式引擎与托管lakehouse的选型边界,对数据平台和分析Agent从业者有直接参考价值。
本文目录 18 节
译文
AI 逐段翻译关键要点
- 读取 Parquet 的 AI 代理仍然依赖于执行引擎;AI 改变的是分析界面和解释层,而不是对精确计算的需求。
- 将确定性计算与概率性推理分离。引擎应负责过滤、聚合和连接,而代理应负责解释证据并传达结论。
- 当数据量适中、主要消费者是代理工作流且写入受控时,像 DuckDB 这样的嵌入式引擎可能就足够了。
- 当数据有多个独立消费者、必须与大型共享数据集连接、需要并发写入或需要组织级治理时,托管式湖仓就变得有价值。
- 生产级代理分析应保留源数据、配置、派生事实和模型输出,以便每份报告都能被验证和重放。
AI 代理现在可以生成分析代码、从对象存储读取文件,并将运营数据转化为结构化报告。这是否使数据湖、湖仓和 SQL 引擎过时了?我们在自动化分析一次大规模灾难恢复演练时遇到了这个问题。我们的第一个可行设计将三十秒粒度的服务指标存储为 S3 兼容对象存储中的 Parquet,使用 DuckDB 进行确定性聚合,并让 AI 代理对故障转移结果进行分类并解释证据。原型在不需要共享湖仓的情况下运行成功,但这并不是因为查询引擎消失了。相反,引擎变成了代理工作流内部的嵌入式组件。本文解释了该架构,介绍了确定性计算与概率性推理之间的实用分离,并提出了四个信号,用于判断何时嵌入式引擎已足够、何时需要托管式湖仓。
原型运行成功后出现的问题
这个问题并非来自白板练习。它是在一个真实工作流成功产出第一份报告之后出现的。我们当时正在分析一次灾难恢复(DR)孤岛演练。在演练期间,一个区域被有意隔离,流量预期会故障转移到幸存区域。对于每个服务,我们需要回答三个问题:
- 幸存区域是否吸收了离开隔离区域的流量?
- 在吸收流量后,服务是否保持可用?
- 延迟是否保持在可接受范围内,还是额外负载造成了持续的性能退化?
我们以三十秒分辨率导出服务指标,将其作为 Parquet 文件写入 S3 兼容对象存储,并让 AI 代理生成结构化分析报告。一旦工作流端到端运行成功,一个诱人的想法浮现出来:
如果代理可以从对象存储读取文件并得出答案,为什么我们还需要表、目录、湖仓或 SQL?
这个问题听起来合理,因为面向用户的体验已经改变了。工程师不再打开仪表板或编写查询,而是要求代理分析演练。代理识别相关数据,按需生成 DuckDB SQL 或 Python,通过确定性工具执行分析,并解释发生了什么。例如,为了验证故障转移期间流量是否从区域 A 转移到区域 B,代理可能会针对预定义的一组表和时间范围生成只读 DuckDB 查询。在执行之前,查询会根据可访问数据集和操作的允许列表进行验证,然后在沙盒化的分析运行时中执行。运行时返回结构化查询结果,确定性应用代码将其规范化为事实包,其中包含按区域划分的流量、时间戳和百分比变化等字段。一个简化的实现看起来像这样查询:
generated_sql = """
SELECT
region,
SUM(request_count) AS requests
FROM traffic_metrics
WHERE ts BETWEEN :start_time AND :end_time
GROUP BY region
"""
validate_sql(
generated_sql,
allowed_tables={"traffic_metrics"},
allowed_operations={"SELECT"},
)
rows = sandbox.execute(
generated_sql,
params={
"start_time": exercise_start,
"end_time": exercise_end,
},
)
fact_packet = {
"metric": "request_count",
"window": [exercise_start, exercise_end],
"by_region": {
row["region"]: row["requests"]
for row in rows
},
}在这个简化示例中,代理只生成分析查询。确定性验证层在执行前拒绝访问非允许列表中的表或非只读操作。然后查询在沙盒化运行时中运行,返回的行由应用代码转换为结构化事实包。模型接收这个事实包(例如,区域 A 和区域 B 的请求计数),而不是凭空编造或直接估算这些值。
为什么 DR 分析不仅仅是扫描指标
DR 分析的难点不在于检索时间序列,而在于判断时间序列意味着什么。假设区域 A 被隔离,区域 B 保持活跃。健康的故障转移应产生可识别的序列:
- 区域 A 的 QPS 下降。
- 区域 B 的 QPS 相应增加。
- 区域 B 的成功率保持在服务目标之上。
- 尾部延迟可能短暂上升,但不应持续升高。
每个陈述都需要上下文。QPS 增加并不自动意味着故障转移成功。它可能太小,无法解释隔离区域丢失的流量。如果指标包含预期的业务拒绝,较低的成功率并不自动意味着 DR 失败。切换边界处的延迟峰值如果系统迅速恢复稳定状态,可能是无害的。因此,我们使用两个基线和个稳态窗口:
- 历史基线。前一天的同一时间窗口,捕捉正常的时段行为。
- 即时基线,演练前的一小时,捕捉最近的服务状态。
- 稳态窗口,切换后间隔,排除短暂过渡期并聚焦于持续行为。
分析收集了四个核心信号族:
- qps
- err_qps
- success_rate
- latency_pNN,包括尾部延迟,如 p99
对于每个服务,确定性代码计算诸如流量吸收比、SLO违规、延迟变化和持续劣化时长等事实。随后,智能体在服务、演练配置和可用证据的上下文中解释这些事实。这一区分至关重要。困难之处往往在于判断,但只有当判断所依据的事实是确定性的时,判断才可信。
架构:对象存储、嵌入式引擎和一个智能体
该工作流使用了三个逻辑层:数据准备、确定性计算和智能体驱动的解释,并将可复现性视为一项横切要求。数据准备层使用了可复用的DuckDB构建逻辑,该逻辑生成一次后即被复用,用于将原始指标转换为对象存储中的自描述资产。对于每个分析请求,智能体选择相关输入、生成受约束的SQL或Python,并编排在DuckDB/Python中的确定性执行。随后,智能体对生成的结构化事实包进行解释,以生成最终报告。在整个工作流中,输入数据、配置、可复用构建逻辑、确定性事实和智能体执行元数据均被保留,以便日后能够重建、重放和评估该分析。
图1. AI智能体发起并编排分析,确定性工具计算事实,智能体解释生成的事实包。来源:作者自制插图,在ChatGPT协助下创建。
存储与自描述
物理数据存放在兼容S3的对象存储中。每次演练都有一个自描述目录:
s3://<bucket>/<root-prefix>/
index.json
<exercise-id>/
manifest.json
exercise_config.json
metrics-*.parquet
service_dimension.parquet这些文件各有不同用途:
- index.json列出了可用的演练。
- manifest.json记录了模式、行数、文件大小和校验和。
- exercise_config.json记录了时间窗口、区域、阈值和演练身份。
- metrics-*.parquet包含了三十秒粒度的指标事实。
- service_dimension.parquet包含了服务元数据,例如归属关系和服务层级。
清单之所以重要,是因为一堆文件尚不构成可信的数据资产。使用者需要知道其中有什么、使用了哪种模式、写入是否完成,以及这些文件日后能否被验证。
使用DuckDB进行确定性处理
DuckDB在存储边界的两侧都被使用。在摄取期间,它读取原始指标记录、对其进行排序、写出压缩Parquet文件,并为清单生成验证统计信息。在分析期间,它直接读取Parquet文件、过滤相关窗口、连接服务元数据,并计算智能体所使用的事实包。该架构受益于DuckDB的若干特性:
- 它运行在进程内,因此可以随智能体工作流一起移动,而无需单独的数据库服务。
- 它直接读取Parquet,并可将过滤器和列选择下推到Parquet扫描中。
- 它为聚合和连接提供SQL语义。
- 它可以将较大的操作溢出到本地存储,而不要求整个工作集都保留在内存中。
重点不在于DuckDB普遍优于Spark、Trino或托管湖仓一体方案。重点在于该工作负载尚不需要共享的分布式引擎。
使用AI智能体进行概率性解释
智能体并未接收数十万行原始指标数据,也未尝试自行统计错误或计算平均延迟。相反,它为每个服务接收一个紧凑的、确定性证据包。简化后的证据包如下例所示:
{
"service_id": "service-a",
"exercise_window": "post-cutover-steady-state",
"expected_failover_qps": 1200,
"observed_qps_increase": 910,
"traffic_absorption_ratio": 0.758,
"success_rate_baseline": 0.9992,
"success_rate_during_exercise": 0.9941,
"service_slo": 0.9990,
"p99_latency_baseline_ms": 84,
"p99_latency_during_exercise_ms": 191,
"sustained_degradation_minutes": 18,
"data_quality_flags": []
}智能体使用该证据包对结果进行分类、解释支撑证据并提出下一步调查建议。相应的输出可能如下:
{
"classification": "partial_failover",
"confidence": "high",
"evidence": [
"The surviving region absorbed approximately 76% of the expected traffic.",
"Availability remained below the service SLO during the steady-state window.",
"The p99 latency increase was sustained for 18 minutes."
],
"recommended_action": "Review routing capacity and downstream dependency limits."
}确切的数字来自引擎。模型的责任是确定这些数字应如何被解释和传达。
简单的基于阈值的规则并不总是足够。例如,某个服务可能在切换后立即暂时超过其p99延迟阈值,而流量已经正确转移且可用性仍处于服务目标之内。将任何阈值违规都视为失败的规则会将此服务归类为劣化。而智能体则可以将过渡窗口与稳态窗口进行比较,识别出延迟在几分钟内就恢复到基线,并将该事件归类为带有短暂劣化的成功故障转移,而非持续的DR失败。
更重要的是,有些情况包含相互冲突的证据,无法通过单一阈值或时间规则来解决。某个服务可能吸收了预期流量并恢复了自身延迟,但由于下游依赖接近饱和运行而仍处于风险之中。反之,一个Tier-2异步服务可能显示临时积压增加而未违反其可用性目标,而同样的模式出现在Tier-0同步依赖中则会需要升级处理。在这些情况下,确定性层仍然生成底层事实(即流量转移、延迟、成功率、积压、依赖饱和度、服务层级和SLO状态),但智能体将这些事实与服务上下文相结合,以确定哪些证据最重要以及该事件应如何分类。
因此,智能体的价值不在于替代确定性计算,而在于解决依赖上下文且有时相互冲突的证据,否则这些证据将需要日益庞大的服务特定规则集。
确定性分离
该项目最具可复用性的经验是我所称的确定性分离:将精确计算与上下文推理分开。
| 交给引擎 | 交给AI智能体 |
| 过滤、聚合、连接、排序和窗口计算 | 选择相关证据并解释其为何重要 |
| 精确且可复现的数值结果 | 对故障转移行为进行分类并提出调查路径 |
| 成本随数据量增长的操作 | 依赖上下文、权衡取舍和沟通的工作 |
| 数据质量检查和验证标记 | 在可用证据不足时放弃作答 |
表 1. 精确计算与上下文推理之间的实用划分。
这种划分避免了一种常见的反模式,即把 LLM 当作查询引擎。语言模型不应读取数十万行数据并计算错误计数、百分位数或平均值。这种方法速度慢、token 成本高,而且难以复现。更重要的是,它把确定性工作交给了概率性系统。更安全的模式是:
- 使用确定性引擎生成一小组权威事实。
- 允许 agent 生成推导这些事实所需的受约束 SQL 或 Python。
- 通过确定性工具执行该代码,并将生成的事实包连同必要上下文交给模型。
- 要求模型使用事实包中的字段解释结论,同时保留输入、生成的代码、执行元数据和输出,以便重放和审计。
agent 可以决定调查哪个问题、动态生成受约束的 DuckDB SQL 或 Python,并编排其执行。生成的代码可能是以概率方式提出的,但最终得到的数值事实必须来自确定性执行,而不是来自模型本身。
工作负载告诉我们关于架构的哪些信息
演练数据集包含:
- 三十秒指标分辨率
- 1,127,748 行事实数据
- 1,523 个服务
- 约 3.8 兆字节的压缩 Parquet 事实数据
在所有服务上进行的上游采集运行使用并发工作线程,耗时约一小时。生成的分析数据集足够紧凑,嵌入式引擎可以在接近 agent 的位置处理它,而无需引入共享数据平台。这一观察很重要,因为架构决策应遵循实际工作负载,而不是“大数据”这个标签。在超过一百万行在设计评审中听起来可能很大,但仅凭行数并不足以成为部署分布式湖仓栈的理由。列数、压缩、查询形态、并发、访问模式和数据移动都很重要。
一次有代表性的分析走查
以下是一个基于演练中所用工作流的代表性示例;服务标识符和值已匿名化。
对于某个服务,确定性执行层生成的事实包显示,幸存区域吸收了约百分之七十六的预期故障转移流量,可用性在稳态窗口期间低于服务 SLO,p99 延迟持续升高达十八分钟。与此同时,该服务继续提供流量,并未经历完全中断。
agent 将这些事实解释为部分故障转移,而非二元的成功或失败。其解释引用了流量吸收比例、持续的 SLO 违规和延迟退化,并建议审查路由容量和下游依赖限制。
随后,该结论根据底层确定性事实和演练时间线进行了验证。审查者可以将 agent 解释中的每一条陈述追溯到事实包中的字段,并针对保留的输入数据和配置重新运行相同的 DuckDB/Python 分析。一旦结论得到确认,由此产生的行动就是调查是路由容量还是下游瓶颈导致幸存区域无法吸收全部预期负载。
这一验证步骤很重要:agent 提出了解释和下一步行动,但数值证据仍然可复现且可独立验证。
使工作流可复现
只有当我们把可复现性作为一等需求后,原型才成为工程系统。对于每次演练,我们都保留了一个重放包,其中包含:
- Parquet 输入
- 清单和校验和
- 演练配置
- 生成的 SQL 或 Python 代码
- 执行元数据
- 确定性派生事实
- 模型输出
- agent 版本和提示词
- 模型标识符
这个重放包使得离线重跑分析、调查有争议的结论,或针对同一证据比较两个 agent 版本的行为成为可能。我们还显式编码了数据语义。一个微妙的例子是缺失值的含义。在某些上下文中,缺失的 QPS 或错误计数数据可能代表零活动。缺失的成功率或延迟数据通常意味着没有可测量的流量,而不是百分之零的成功率或零毫秒延迟。
将所有缺失值填充为零会制造出虚假的失败和虚假的恢复。schema 和数据质量标志需要保留这种区别,以便引擎和 agent 一致地解释数据集。
轻量级架构何时不再足够
agent 加 DuckDB 并不是每个分析系统的最终架构。它在工作负载具备合适形态时有效。有四个信号表明轻量级架构开始遇到压力。它们并不要求一次性采用完整的湖仓栈。每个信号都指向一种可以逐步引入的不同能力,包括用于共享发现和治理的目录、用于安全并发写入和演进的开放表格式,或在计算必须更接近大型共享数据集时使用的分布式查询引擎。
独立的消费者出现
如果仪表板、定时任务、笔记本或其他团队需要直接访问,数据就需要一个稳定的契约。这些消费者不应不得不调用原始 agent 或逆向工程桶布局。共享目录允许他们按名称发现表、理解其 schema、应用权限,并通过标准工具查询它。
数据变得太大,无法移动到 agent 环境
在 TB 级规模下,将文件拉取到本地或开发环境进行分析会变得低效。查询应在数据存储的位置执行(即 近数据处理),并配合分区裁剪、谓词下推和分布式处理。只有结果才应返回给 agent。
工作流必须与大规模型共享数据集进行连接
当两侧数据都足够小时,DuckDB 可以高效地连接本地文件。边界出现在另一侧是组织级权威数据集时,该数据集已注册在共享目录中,并且规模过大或更新过于频繁,无法安全导出。此时,更可靠的做法是将新数据注册到同一环境中,并通过共享引擎执行连接。
需要并发写入或表级演进
单一写入者约定对于早期工作流而言简单且有效。当多个生产者更新数据集、模式独立演进,或读取者需要一致快照时,这种约定就会失效。像 Apache Iceberg 这样的开放表格式在 Parquet 文件之上增加了表元数据、快照、模式演进和事务提交语义。托管湖仓(managed lakehouse)随后可以结合三项能力:
- 用于发现、所有权、权限、模式和分区的目录
- 用于快照、演进和安全写入的开放表格式
- 用于分布式扫描和连接的共享查询引擎
这些能力很有价值,但它们会带来集成、治理、计算和运营成本。应当根据真实的共享、规模和事务需求来采用它们,而不是因为假定每个 AI 项目从第一天起就需要湖仓。
实用决策表
| 工作负载信号 | 靠近 agent 的嵌入式引擎 | 托管湖仓 |
| 主要消费者是单个 agent 工作流 | 非常适用 | 通常不必要 |
| 中等规模、压缩的分析数据集 | 非常适用 | 可能过度设计 |
| 临时性或基于练习的分析 | 非常适用 | 仅在需要共享时有用 |
| 许多独立消费者 | 适用性弱 | 非常适用 |
| TB 级扫描 | 适用性弱 | 非常适用 |
| 与大型共享权威表连接 | 有限 | 非常适用 |
| 并发写入者和快照需求 | 适用性弱 | 非常适用 |
| 组织级目录和治理 | 有限 | 非常适用 |
表 2。根据访问模式、规模和写入语义选择平台。
决策不是 AI 还是 SQL,也不是文件还是表。真正的决策是:
一个嵌入式的、由 agent 驱动的分析引擎是否足够,还是说工作负载需要一个托管的、共享的数据平台?
结论:AI 改变的是接口,而非引擎
AI agent 让数据分析更易获得。它们可以将运维问题转化为分析步骤、选择相关证据、对行为进行分类,并撰写工程师可以据此采取行动的报告。它们并不能消除对精确执行的需求。
在我们的 DR 分析工作流中,Parquet 和 DuckDB 完成了机械性工作:存储、过滤、聚合、验证和连接。agent 完成了上下文相关工作:决定如何一并解释流量吸收、可用性和延迟,并传达结果。该架构有意保持轻量,因为真实工作负载尚不需要共享湖仓。
在运行该工作流之后发生改变的是我们对分析接口应位于何处的看法。工程师不再需要为每次调查都直接与查询引擎交互,但引擎对于产生可信事实仍然至关重要。agent 成为规划分析、生成所需 SQL 或 Python 并解释证据的层。
因此,主要的架构权衡不是 AI 与 SQL 的对立,也不是文件与表的对立。而是对于手头的工作负载,需要在 agent 背后放置多少分析基础设施。一个小型嵌入式引擎可能足以满足聚焦的工作流;共享目录、开放表格式和分布式引擎只应在共享、规模、写入并发或治理产生具体需求时引入。引擎仍然保留;改变的是接口。
这篇内容对你有用吗?
反馈只用于改善内容筛选,不等同于收藏