
1. 从一次“诡异”的性能抖动说起最近在负责一个AI Agent项目的后端数据观测平台遇到了一个让人头疼的问题。这个Agent的核心逻辑是动态生成并执行复杂的JSON配置这些配置决定了它的行为链、工具调用顺序和参数。为了方便分析和回溯我们将Agent每次推理产生的完整JSON上下文包括思维链、工具调用记录、最终结果等都作为一条记录写入了我们基于Apache Doris搭建的实时数仓里。表结构设计得挺直观一个自增ID一个Agent会话ID一个时间戳然后就是一个巨大的JSON类型的字段里面塞满了每次推理的动态内容。起初一切运行良好但随着接入的Agent数量和任务复杂度上升我们开始观察到一些间歇性的查询延迟。尤其是在业务方需要通过一些JSON内部的字段比如$.actions[0].tool_name来筛选特定工具的使用情况或者统计某个决策路径的出现频率时某些查询会突然变得很慢从毫秒级跳到秒级甚至触发超时。更让人困惑的是Doris监控显示这些慢查询在执行时ScanBytes和ScanRows的指标异常地高看起来像是进行了全表扫描但我们的查询条件明明包含了分区键和时间范围。团队里第一个冒出来的念头是“是不是JSON类型字段的查询性能就是不行毕竟它不像结构化字段有索引。” 于是我们尝试了各种优化对常用的JSON路径建了函数索引调整了查询写法甚至怀疑过是不是Doris版本的问题。但问题依旧像幽灵一样时隐时现。直到有一次我们为了排查另一个问题打开了查询的EXPLAIN计划才注意到一行不太起眼但至关重要的信息Predicates: CAST(json_field AS TEXT) LIKE ‘%some_pattern%‘。原来一些看似使用了JSON函数如json_extract的查询在某些条件下其执行计划中竟然出现了将整个JSON字段转换为长文本字符串拍平然后进行全字段扫描LIKE的操作。这就是所谓的“强行拍平”导致的“隐性全表扫描”它完美地绕过了我们的分区和索引直接拖垮了查询性能。这个问题在AI Agent这种JSON结构动态多变、数据体量增长迅速的场景下被急剧放大。2. 动态JSON观测需求与挑战的再审视在深入技术细节之前我们有必要先厘清为什么AI Agent场景下的JSON观测分析会如此特殊和棘手。这绝不仅仅是把日志存进去然后查那么简单。2.1 AI Agent JSON数据的核心特征AI Agent产生的JSON数据与我们熟悉的业务操作日志或静态配置JSON有本质区别结构极度动态与嵌套一个Agent的推理过程JSON可能包含session_id、user_query、thoughts一个数组里面每一步都有content、reasoning、actions工具调用数组每个工具又有name、args、result、final_answer等。而且thoughts和actions数组的长度每次都可能不同args的内容更是千变万化。这导致数据模式Schema是高度不固定的。查询需求复杂且不可预知业务团队的分析需求五花八门。例如“找出所有使用了‘天气查询’工具的会话。”“统计‘数据分析’工具调用失败result.status为error的前三大原因。”“分析在思考链thoughts中出现过‘用户可能想要X’模式但最终未执行相应工具的案例。”“对比使用策略A和策略B的Agent其平均推理步骤thoughts数组长度的差异。” 这些需求往往需要深度遍历和解析JSON内部的特定路径。数据体量与增长迅猛每个Agent任务都可能产生数KB甚至更大的JSON记录。在高并发场景下数据流入速度极快对写入和查询都是巨大考验。2.2 “观测分析”的真实含义这里的“观测”不是简单的记录而是要求我们能对海量、非结构化的Agent“思维过程”数据进行实时或近实时的穿透查询快速定位到符合特定内部状态的数据行。聚合统计对JSON内部的特定字段进行计数、求和、去重等计算。关联分析将JSON内的某个字段与其他结构化表如用户表、工具元数据表进行关联查询。时序追踪观察某个Agent会话ID下JSON内部状态随时间多个记录的演变序列。传统的“把JSON存成TEXT然后靠应用层解析”或“简单使用数据库的JSON函数”方案在面对上述挑战时往往会在性能、灵活性和开发效率上做出痛苦的权衡。而我们遇到的“强行拍平与全表扫描”问题正是这种权衡失控的典型表现。3. 深入拆解“强行拍平”与“全表扫描”是如何发生的让我们回到最初的问题在Doris其JSON处理逻辑与许多现代数据库类似中一次低效的查询究竟是如何产生的。3.1 一个“踩坑”查询示例假设我们有一张表agent_events分区字段是event_date 其中有一个context_json字段JSON类型。我们想查询使用了‘calculate’工具的记录。“好”的查询写法通常能利用索引SELECT session_id, event_time FROM agent_events WHERE event_date ‘2023-10-27‘ AND json_extract(context_json, ‘$.actions[*].tool_name‘) LIKE ‘%calculate%‘;或者使用JSON函数SELECT session_id, event_time FROM agent_events WHERE event_date ‘2023-10-27‘ AND JSON_EXISTS(context_json, ‘$.actions[*]?(.tool_name like “%calculate%“)‘);“坑”的查询写法可能导致全表扫描SELECT session_id, event_time FROM agent_events WHERE event_date ‘2023-10-27‘ AND context_json LIKE ‘%“tool_name”: “calculate”%‘;或者在某些复杂嵌套查询时优化器可能无法有效下推JSON路径谓词内部重写为类似后者的形式。3.2 执行计划层面的魔鬼细节当我们对“坑”的查询使用EXPLAIN时可能会看到这样的信息... TABLE: agent_events PREAGGREGATION: ON PREDICATES: event_date ‘2023-10-27‘, CAST(context_json AS VARCHAR) LIKE ‘%“tool_name”: “calculate”%‘ partitionsRatio1/1, tabletsRatio10/10 ...关键点在于CAST(context_json AS VARCHAR) LIKE ...。这意味着强行拍平为了执行LIKE操作数据库必须将每一行数据的context_json这个二进制或内部编码的JSON对象完整地转换为一个庞大的字符串VARCHAR。这个过程CAST本身就有CPU和内存开销。全表扫描转换完成后数据库需要对这整个长字符串进行模式匹配LIKE ‘%...%‘。由于LIKE以通配符%开头它无法利用任何基于前缀的索引。更糟糕的是这个谓词是在context_json字段上而不是在分区键event_date上。虽然event_date条件能帮助筛选数据分区但在每个符合日期条件的分区内引擎仍然需要扫描该分区下的每一个数据块tablet中的每一行对每一行都执行一次“拍平字符串匹配”的操作。这就是“隐性全表扫描”——在分区内全扫。3.3 为什么优化器会“选错”计划这背后有几个原因函数与操作符的代价估算优化器在估算成本时可能错误地认为json_extractLIKE的代价高于直接的CASTLIKE尤其是当JSON路径较复杂或表统计信息过期时。JSON路径的复杂性对于非常深或包含通配符[*]的JSON路径查询优化器可能无法生成高效的执行计划退而求其次选择最通用但低效的方式。隐式类型转换在复杂查询条件混合时可能会触发意料之外的类型转换规则导致索引失效。在AI Agent场景中由于JSON结构复杂、查询模式多样开发者很容易无意中写出触发此类问题的SQL而问题在数据量小的时候不易暴露一旦数据量增长性能便急剧恶化。4. 构建高效JSON观测分析系统的实战方案理解了问题的根源我们就可以系统地设计解决方案。目标是在保持JSON存储灵活性的前提下实现近似于结构化数据的查询性能。4.1 方案一查询优化与索引策略治标立竿见影这是首先应该采取的步骤成本最低能解决大部分因写法不当导致的问题。强制使用JSON函数建立团队规范禁止在WHERE/ORDER BY等关键子句中对JSON字段直接使用LIKE、等操作。统一使用数据库提供的原生JSON查询函数如json_extract,json_exists,json_contains等。这些函数通常能更好地与存储引擎协作。为高频查询路径创建函数索引-- 在Doris中可以为JSON字段的特定路径创建函数索引倒排索引 ALTER TABLE agent_events ADD INDEX idx_tool_name (context_json-‘$.actions[*].tool_name‘) USING INVERTED;这个索引会对所有actions数组中的tool_name值建立索引。当查询条件为json_extract(context_json, ‘$.actions[*].tool_name‘) LIKE ‘%calculate%‘时就有可能利用这个索引快速定位数据行避免全表扫描。注意索引并非银弹它会增加存储开销和写入延迟需要根据查询频率和模式谨慎选择创建。保持统计信息新鲜定期或在数据量变化较大时更新表的统计信息帮助优化器做出更准确的代价估算选择更优的执行计划。使用EXPLAIN验证对于核心的、频繁执行的查询养成使用EXPLAIN或EXPLAIN ANALYZE查看执行计划的习惯重点关注PREDICATES部分确保没有出现非预期的CAST操作。4.2 方案二数据模型优化——“动态JSON”的“静态化”处理治本推荐对于AI Agent这种JSON结构有一定规律可循的场景更根本的解决方案是在数据入库时就将动态JSON中高频查询、分析的字段提取出来转化为标准的结构化列。这是一种“宽表动态列”的思路。操作步骤识别核心维度与指标与业务方深度沟通确定最常被查询的JSON路径。例如session_id本身可能已在JSON中、final_answer、error_code、主要使用的tool_name、thoughts数组长度等。扩展表结构在原始的agent_events表基础上增加这些字段作为单独的列如primary_tool VARCHAR,thought_count INT,has_error BOOLEAN。在ETL过程中提取填充在数据写入Doris之前通过流处理如Flink或写入时使用Doris的INSERT … SELECT配合JSON函数实时解析原始JSON将值填充到这些新增的列中。原始JSON保留原始的context_json字段仍然保留用于低频的、需要完整上下文的深度调试或特殊分析。示例建表语句CREATE TABLE agent_events_enhanced ( id BIGINT, session_id VARCHAR(50), event_date DATE, event_time DATETIME, -- 提取的静态列 primary_tool VARCHAR(100), -- 从actions中提取的主要工具名 thought_count SMALLINT, -- thoughts数组的长度 final_answer TEXT, -- 最终答案文本 has_error BOOLEAN, -- 是否存在错误 error_reason VARCHAR(200), -- 错误原因 -- 原始动态数据 context_json JSON, -- 其他元数据... ) DUPLICATE KEY(id, session_id, event_date, event_time) PARTITION BY RANGE(event_date) () DISTRIBUTED BY HASH(session_id) BUCKETS 10 PROPERTIES (...);优势查询性能飞跃对primary_tool,has_error等列的查询可以直接利用列式存储、分区、分桶以及可能的Bloom Filter或ZoneMap索引性能比JSON路径查询高几个数量级。优化器友好标准的SQL谓词下推、聚合计算都能完美工作。开发体验好业务人员可以直接使用熟悉的SQL语法进行筛选和聚合无需学习复杂的JSON查询函数。挑战与权衡模式变更如果高频查询的字段发生变化需要修改表结构ALTER TABLE有一定运维成本。但这在业务分析场景下通常比频繁变更JSON查询模式更可控。存储冗余数据被存储了两次一次在结构化列一次在原始JSON增加了存储成本。但这通常是用存储成本换取计算性能和开发效率的经典权衡在大多数情况下是值得的。4.3 方案三使用更专业的处理工具架构升级如果数据规模和分析复杂度达到极致可以考虑引入专门的系统Elasticsearch对于全文搜索、模糊匹配、复杂的嵌套对象查询场景Elasticsearch的倒排索引能力是天然优势。可以将原始JSON直接索引实现毫秒级的复杂条件检索。通常用作Doris的补充将Doris处理后的明细数据或需要全文检索的数据同步到ES。Apache Druid特别擅长处理时序事件数据对JSON维度列有较好的支持在聚合查询性能上表现优异。适合做Agent事件的实时OLAP分析。 在我们的实践中方案二静态化提取结合方案一查询优化解决了95%以上的性能问题。我们将最关键的5个字段提取成了独立列并为context_json上的session_id和event_time路径创建了倒排索引以支持会话追踪。调整后之前那些秒级的查询全部降到了100毫秒以内。5. 避坑指南与最佳实践总结结合这次“强行拍平”的排查经历和后续的优化实践我总结出以下几点针对AI Agent类动态JSON数据观测分析的建议5.1 设计阶段就要考虑查询模式不要等到性能出问题再补救。在设计数据表之初就与业务方充分讨论未来的核心查询场景。即使第一版只存原始JSON也要在表结构上为将来可能提取的字段预留位置或做好注释。5.2 建立清晰的JSON查询开发规范禁用WHERE json_column LIKE ‘%...%‘。推荐使用数据库原生的、支持索引下推的JSON函数。强制对生产环境的重要查询进行EXPLAIN审查将其作为上线流程的一部分。5.3 实施分层的存储与查询策略热数据最近7天采用“静态化提取原始JSON”的宽表模式存储在Doris中支持高性能即时查询。温数据7天-90天可以保留在Doris但考虑使用ROLLUP或物化视图对常用聚合指标进行预计算进一步提速。冷数据90天以上将原始JSON压缩后归档到对象存储如S3在Doris中仅保留提取出的结构化维度列用于历史趋势分析。需要查明细时通过联邦查询或按需加载。5.4 监控与告警为数据观测平台本身建立监控。关注查询延迟P99/P95设置阈值告警。扫描行数/字节数异常对扫描行数远超预期值的查询进行捕获和记录。JSON函数执行耗时定位JSON处理本身是否成为瓶颈。AI Agent系统的复杂性不仅在于其推理逻辑也在于其产生的数据。对其运行过程进行有效的观测分析是优化Agent性能、理解其行为、发现潜在问题的基石。而处理好动态JSON这个“熟悉的陌生人”避免落入“强行拍平导致全表扫描”的陷阱是构建稳健、高效观测能力的关键一步。从我们的经验看与其在查询时与优化器斗智斗勇不如在入库时多做一点“静态化”的设计这往往是性价比最高的选择。