从两套系统到一条 SQL:SelectDB search() 搞定日志的搜索与分析

从两套系统到一条 SQL:SelectDB search() 搞定日志的搜索与分析 导读AI 时代日志量巨大传统用 Elasticsearch 做搜索、ClickHouse 做分析的两套系统成本高且复杂。SelectDB基于 Apache Doris 内核研发的商业化产品 通过内置search()函数在同一个引擎内融合全文检索与 SQL 分析实现一份数据同时支持搜索和分析大幅简化架构、提升查询性能。AI 时代日志爆增带来的难题当下日志成为 AI 时代最丰富的数据资源。每一次推理请求从 prompt 输入到 token 输出都会在请求路由、模型调度、GPU 显存分配、KV Cache 命中率、输出质量评估等十几个环节产生大量的日志。对于一个日处理千万级请求的推理服务而言日志规模可轻松达到数十 TB。这些日志不仅需要长时间存储还需要进行有价值的分析排障时要定位追踪搜索比如定位一次 OOM 崩溃的请求上下文追踪一条异常推理链路的调用栈。运营决策时要分析统计各模型的 P99 延迟分布计算 token 成本分摊对比 A/B 实验中不同 prompt 策略的效果。当下比较常见做法 Elasticsearch 负责搜索ClickHouse 或其他 AP 数据库负责分析。两套系统、两份数据二者之间需要单独维护一条数据同步的链路。这不仅增加了架构的运维成本也让实时性、一致性面临挑战。而架构简化、统一已成为新一代日志处理架构的必经之路。在这一背景下SelectDB 基于 Apache Doris 内核研发的商业化产品 这一以分析型见长的数据库开始将搜索这一能力做到极致并已见成效。其内置的search()正是其核心。SelectDBwww.selectdb.com/) 作为 Apache Doris 的核心贡献者和商业化团队在 Doris 开源内核基础上提供了企业级特性、全托管运维服务及专业技术支持帮助企业更便捷地将 Doris 能力应用于生产环境。search()在 SQL 里做文本搜索先看一个例子SELECT request_id, model_name, error_msg, latency_ms FROM inference_logs WHERE search(level:ERROR AND error_msg:CUDA out of memory AND model_name:gpt*) AND log_time NOW() - INTERVAL 1 HOUR ORDER BY latency_ms DESC LIMIT 100;对于熟悉 Elasticsearch 的用户来说上手 SelectDB 的search()函数几乎没有学习成本其语法与 ES query_string 几乎一致。search()接受一个 DSL 字符串参数兼容 ES query_string 语法。search()支持Lucene 模式可通过第二个参数传 JSON 配置实现更复杂的布尔逻辑组合。-- Lucene 模式完整的 MUST/SHOULD/MUST_NOT 语义 WHERE search( level:ERROR AND msg:timeout OR msg:connection refused, {mode:lucene, default_operator:and} ) ​ -- 多字段搜索 WHERE search( CUDA error, {fields:[error_msg,stack_trace,context], mode:lucene} )Lucene 模式实现了 ES BooleanQuery 的 occur 语义MUST、SHOULD、MUST_NOT-也支持minimum_should_match。已经在用 ES query_string 的团队迁移时大部分查询仅换个函数名即可。15 种算子怎么用search()内核中有 15 种查询算子可以任意嵌套组合。挑几个典型场景来看1. 多条件组合定位故障推理服务出了问题工程师要同时限定错误级别、错误关键词、延迟范围还要排除健康检查的噪音、限定特定机器。传统做法是写大量由 AND 拼接的 MATCH或在 Elasticsearch 里构造嵌套 bool query。而在 SelectDB 中search()一行即可搞定-- TERM PHRASE RANGE NOT LIST 五种算子组合一次求值 WHERE search( level:ERROR AND error_msg:connection refused AND latency:[500 TO *] AND NOT module:healthcheck AND host:IN(gpu-node-01 gpu-node-02 gpu-node-03) )五种算子编译成一棵查询树一次性求值。注IN和数值类型的RANGE算子支持会在后续版本迭代支持当前版本尚未开放。2. 正则和通配符线上错误信息形式多样关键词检索难以覆盖所有变体。search()支持前缀通配PREFIX、通配符WILDCARD和正则REGEXP可提升检索命中与覆盖率-- 抓住所有 CUDA 相关错误不管具体是什么 WHERE search(error_msg:/CUDA.*error/ AND level:ERROR) ​ -- 前缀匹配所有以 timeout 开头的错误类型 WHERE search(error_type:timeout*)3. BM25 打分搜索结果可能有数千条如何能快速找到最相关的search()内置 BM25 打分IDF 加权 文档长度归一化并通过 score() 列直接暴露评分便于按相关性排序与筛选-- 按相关性排序最相关的错误日志排最前面 SELECT request_id, error_msg, score() AS score FROM inference_logs WHERE search(error_msg:memory allocation failed OR error_msg:CUDA error) ORDER BY score DESC LIMIT 20;SelectDB 在存储层还做了 TopN 打分优化不用把全量结果传到上层再排序。4. 嵌套搜索AI 应用的日志往往是嵌套的、非扁平结构。例如一条 Agent 调用日志中可能包含多个工具的调用结果和返回信息{ session_id: sess_001, steps: [ {tool: web_search, status: ok, latency: 200}, {tool: code_exec, status: error, error_msg: timeout} ] }而 SelectDB 的 VARIANT 类型可以原生存储这类嵌套结构配合search() 的 NESTED 算子可直接穿透数组进行内部检索-- 在 steps 数组内部搜索哪些会话中有工具调用失败 WHERE search(NESTED(steps, status:error AND tool:code_exec))无需将 JSON 拆成多张表无需额外的 ETL 管道。5. 多字段搜索排障时经常不确定错误信息在哪个字段。search()支持跨字段搜索有两种策略可快速定位-- best_fields关键词必须在同一个字段内匹配更精确 WHERE search(CUDA memory, {fields:[error_msg,context,stack_trace]}) ​ -- cross_fields关键词可以分散在不同字段更宽泛 WHERE search(CUDA memory, {fields:[error_msg,context], type:cross_fields})6. 和 SQL 分析能力混用search()返回布尔谓词可直接嵌入到 JOIN、窗口函数、子查询中便捷地实现在 SQL 层面深入关联与时序分析。-- search JOIN搜索 OOM 错误关联模型配置找出资源配置不足的模型 SELECT l.request_id, l.error_msg, m.gpu_memory_limit, m.max_batch_size FROM ( SELECT * FROM inference_logs WHERE search(level:ERROR AND error_msg:out of memory) AND log_time NOW() - INTERVAL 1 HOUR ) l JOIN model_configs m ON l.model_name m.model_name; -- search 窗口函数追踪错误趋势发现是否在恶化 SELECT model_name, DATE_TRUNC(hour, log_time) AS hour, COUNT(*) AS error_count, LAG(COUNT(*)) OVER ( PARTITION BY model_name ORDER BY DATE_TRUNC(hour, log_time) ) AS prev_hour_errors FROM inference_logs WHERE search(level:ERROR) AND log_time NOW() - INTERVAL 24 HOUR GROUP BY model_name, DATE_TRUNC(hour, log_time);在 Elasticsearch 中要做同样的分析一般需要编写复杂的聚合 DSL或是将数据导入到其他系统。为什么比多个 MATCH 快SelectDB 早已提供MATCH_ANY、MATCH_ALL、MATCH_PHRASE等全文检索谓词。search()的主要改进体现在多条件组合时的性能优势。以一个典型的日志查询为例同时过滤 4 个字段-- 写法 A传统 MATCH每个条件独立求值 SELECT * FROM logs WHERE level MATCH_ANY ERROR AND module MATCH_ANY inference AND error_msg MATCH_PHRASE CUDA out of memory AND context MATCH_ANY gpu -- 写法 Bsearch 函数统一求值 SELECT * FROM logs WHERE search(level:ERROR AND module:inference AND error_msg:CUDA out of memory AND context:gpu)从语法上看起来相似但执行逻辑截然不同。接下来分别看看MATCH和search()的执行逻辑便于对比。1. MATCHbitmap 物化 集合运算每个 MATCH 在 Segment 层独立执行MATCH_ANY ERROR → 打开 IndexReader → 搜索 → 生成 bitmap A MATCH_ANY inference → 打开 IndexReader → 搜索 → 生成 bitmap B MATCH_PHRASE CUDA out of... → 打开 IndexReader → 搜索 → 生成 bitmap C MATCH_ANY gpu → 打开 IndexReader → 搜索 → 生成 bitmap D 最终结果 A ∩ B ∩ C ∩ D每个条件都会生成一份完整的 bitmap即便最终交集只有几十行中间每个 bitmap 也可能包含上百万 bit。四个条件就意味着四次 IndexReader 的 open/search 操作加上三次 bitmap 交集运算。2. search()查询树 逐文档求值search()将所有条件编译成一棵查询树参考 Lucene 的 Weight/Scorer 架构执行。与单一 MATCH 谓词执行相比差异主要体现在三处A. 逐行推进支持 AND 短路并非先计算出每个条件的全量结果再进行交集而是逐**行推进**比如第一个条件匹配了行号#100那么第二个条件就可以直接跳到 #100 进行检查不匹配则跳过无需对中间产生的完整 bitmap 进行物化。当数据分布有倾斜时优势更大。比如level:ERROR只占日志的 0.1%大量数据行在第一个条件就被快速跳过。B. 共享 IndexReader避免重复开销多个字段共享已打开的 reader 实例无需重复加载索引文件。而多个独立的 MATCH 谓词条件各自维护自己的 reader这部分开销会显著叠加。C. DSL 级别缓存性能表现更佳search()以整个 DSL 表达式作为缓存 key同一查询在不同 segment 上的结果可以复用。而 MATCH 采用单谓词粒度的缓存命中率相对较低。在反复执行相似查询的交互式分析场景中性能差距更为显著。总结MATCH 是各条件独立求值后再合并而search()在一棵查询树内统一求值条件越多、数据倾斜越明显性能差异越大。三个实战场景以下用三个 AI 场景的 SQL展示search()和聚合分析怎么配合使用。1. 模型推理异常诊断推理服务出现 GPU OOM 时需要快速定位是哪些模型在报错、prompt 长度分布是否异常、延迟是否受影响。以下 SQL 在一条查询里完成过滤和聚合-- 搜索最近 1 小时所有 GPU OOM 错误按模型聚合统计 SELECT model_name, COUNT(*) AS error_count, AVG(prompt_tokens) AS avg_prompt_tokens, MAX(prompt_tokens) AS max_prompt_tokens, PERCENTILE_APPROX(latency_ms, 0.99) AS p99_latency FROM inference_logs WHERE search(level:ERROR AND error_msg:CUDA out of memory) AND log_time NOW() - INTERVAL 1 HOUR GROUP BY model_name ORDER BY error_count DESC;倒排索引先从数十亿行日志里过滤出目标数据MPP 引擎再进行聚合分析非常连贯且便捷。反观传统的 Elasticsearch OLAP 分治模式在这中间多一次数据搬运动作这就增加了延迟和复杂度。2. 模型评估 A/B 分析上线新版模型前通常要对比新旧版本在不同 prompt 长度下的质量、延迟和成本。短 prompt 表现好不代表长 prompt 也同样好需要分区间来看。以下 SQL 按 prompt 长度分桶对比两个版本的核心指标-- 对比两个模型版本在不同 prompt 长度区间的表现 SELECT model_version, CASE WHEN prompt_tokens 100 THEN short WHEN prompt_tokens 1000 THEN medium ELSE long END AS prompt_category, COUNT(*) AS request_count, AVG(quality_score) AS avg_quality, AVG(latency_ms) AS avg_latency, SUM(completion_tokens) * 0.00003 AS estimated_cost_usd FROM eval_logs WHERE search( task:evaluation AND status:completed AND model_version:IN(v2.1 v2.2), {mode:lucene} ) AND eval_time NOW() - INTERVAL 7 DAY GROUP BY model_version, prompt_category ORDER BY model_version, prompt_category;search()使用 Lucene 语法进行多条件过滤IN 操作可一次匹配多个版本。完成过滤后在 SQL 层直接做分桶聚合质量、延迟与成本即可在同一表中展示无需分别查询再合并。3. AI Agent 调用链追踪一次 Agent 执行可能触发十几次工具调用任一环节的报错或超时都会影响最终结果。排查问题时需要还原完整调用链查看每一步调用了哪些工具、输入输出内容及耗时。下面的 SQL 按执行顺序还原一次异常会话的完整链路-- 追踪某个异常 agent 会话的完整调用链 SELECT step_index, tool_name, input_summary, output_summary, latency_ms, token_usage FROM agent_trace_logs WHERE search(session_id:sess_abc123 AND (status:ERROR OR status:TIMEOUT)) ORDER BY step_index;按session_id定位具体会话同时过滤出报错和超时的步骤。对结果按step_index排序后即可得到一条完整的调用时间线问题发生的环节一目了然。从以上示例可以看出三个场景实则采用同一模式先搜索缩小范围再在 SQL 里进行聚合或关联分析。从 Elasticsearch 迁过来要花多少存储成本节省 50%Elasticsearch 的倒排索引 正排存储 副本通常比源数据膨胀 2-3 倍。而 SelectDB 的倒排索引和列存数据分开存储V3 存储格式支持 ZSTD 字典压缩dict_compression true索引体积比 Elasticsearch 减少约 20%。如果在 TB 级日志场景下整体存储成本能省 50% 以上。运维架构复杂度降低迁移至 SelectDB 后企业不再需要同时维护 ES 集群及其配套的 Kafka → Logstash → ES 同步链路。对于团队规模较小的 AI 公司而言这意味着可以直接省去一个专职运维岗位。迁移兼容 ES query_stringsearch()兼容 ES query_string 语法大部分原有查询仅需将 REST API 改成 SQL WHERE 即可。索引定义ES mapping 的字段类型对应 SelectDB 列定义 USING INVERTED。-- SelectDB 建表示例日志表 倒排索引 CREATE TABLE inference_logs ( log_time DATETIME, request_id VARCHAR(64), model_name VARCHAR(32), level VARCHAR(16), error_msg TEXT, context TEXT, prompt_tokens INT, completion_tokens INT, latency_ms INT, INDEX idx_level(level) USING INVERTED, INDEX idx_error(error_msg) USING INVERTED PROPERTIES( parser unicode, support_phrase true ), INDEX idx_context(context) USING INVERTED PROPERTIES( parser unicode, support_phrase true ), INDEX idx_model(model_name) USING INVERTED ) ENGINEOLAP DUPLICATE KEY(log_time) PROPERTIES ( inverted_index_storage_format V3 );另外SelectDB 的倒排索引加减均不需要重写数据文件。可以先导入数据再根据查询需求针对性添加索引也可以随时删掉冗余的索引释放空间——整个过程无需停服、无需重新建表。总结传统的日志分析方案往往是一条数据同步链路连接着两个世界Elasticsearch 负责搜索OLAP 引擎负责分析。两套系统各自独立部署存储冗余、运维复杂、版本升级相互牵制数据一致性存在隐患。而 SelectDBsearch()的出现让这一切变得简单起来。同一份数据倒排索引负责筛选MPP 引擎负责计算搜索与分析在同一个引擎内无缝融合。search()集成了 15 种查询算子、BM25 相关性打分、嵌套数组搜索、多字段跨字段检索等原本需要搜索引擎才能提供的丰富功能。文本检索由此变成了一个普通的 WHERE 谓词直接参与 JOIN、聚合、窗口函数、子查询相当的便捷。AI 场景下的日志数据量更大、字段结构更复杂既要能精确定位异常还要进行聚合统计。能够将搜索和分析写在同一条 SQL 里少维护一套系统少一次数据搬运查询延迟从分钟级提升至秒级正是 SelectDB 此举的价值所在。想立即体验SelectDB Cloud 免费试用www.selectdb.com/cloud需要私有化部署SelectDB Enterprise 下载试用www.selectdb.com/enterprise已是阿里云用户阿里云数据库 SelectDB 版 一键启用http://www.aliyun.com/product/selectdb?utm_contentg_1000410296