SelectDB 基于 Apache Doris 内核,通过内置
search()函数在一个引擎内融合全文检索与 SQL 分析。本文含完整配置代码、15 种查询算子详解、倒排索引建表模板,以及可复用的优化 checklist。
关键词:SelectDB · Apache Doris · search() · 全文检索 · 倒排索引 · ES 迁移 · BM25 · 日志分析
背景:为什么需要 search()?
AI 时代日志量巨大。一次推理请求从 prompt 输入到 token 输出,会在十几个环节产生日志。日处理千万级请求的推理服务,日志规模轻松达到数十 TB。
传统做法是两套系统:Elasticsearch 负责搜索,ClickHouse 负责分析。问题很明显:
- 两份数据,存储成本翻倍
- 中间多一条数据同步链路,实时性差、一致性难保证
- 运维要同时管 ES 和 OLAP 两套集群,复杂度高
SelectDB search() 的目标很简单:在 SQL 里做全文检索,让搜索和分析用同一份数据、同一条 SQL 完成。
快速上手:第一个 search() 查询
-- 搜索最近 1 小时内所有 GPU OOM 错误日志
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;
语法兼容 ES query_string,熟悉 Elasticsearch 的用户几乎零学习成本。
search() 两种模式:
-- 模式 1:query_string(默认),简洁直观
WHERE search('level:ERROR AND error_msg:"connection refused"')
-- 模式 2:Lucene,支持 MUST/SHOULD/MUST_NOT 语义
WHERE search(
'level:ERROR AND msg:"timeout" OR msg:"connection refused"',
'{"mode":"lucene", "default_operator":"and"}'
)
建表模板:日志表 + 倒排索引
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
) ENGINE = OLAP
DUPLICATE KEY(log_time)
PROPERTIES (
"inverted_index_storage_format" = "V3", -- V3 格式,ZSTD 压缩
"compression" = "ZSTD"
);
关键配置说明:
USING INVERTED:为目标字段创建倒排索引,search()依赖倒排索引加速support_phrase = "true":启用短语搜索能力,支持"CUDA out of memory"这种带引号的精确短语匹配parser = "unicode":中英文混合分词——ES 需要额外安装 IK 分词器,SelectDB 原生支持inverted_index_storage_format = "V3":V3 存储格式支持 ZSTD 字典压缩,索引体积比 ES 减少约 20%
注意:SelectDB 的倒排索引加减均不需要重写数据文件。可以先导入数据再按需添加索引,也可以随时删除冗余索引——整个过程无需停服、无需重新建表。
15 种查询算子速查表
search() 内核内置 15 种查询算子,可任意嵌套组合:
| 算子 | 语法示例 | 用途 |
|---|---|---|
| TERM | level:ERROR | 精确词匹配 |
| PHRASE | error_msg:"CUDA out of memory" | 短语匹配 |
| PREFIX | model_name:gpt* | 前缀通配 |
| WILDCARD | error_type:timeout* | 通配符匹配 |
| REGEXP | error_msg:/CUDA.*error/ | 正则匹配 |
| RANGE | latency:[500 TO *] | 数值范围 |
| NOT | NOT module:healthcheck | 排除 |
| IN | host:IN(gpu-01 gpu-02) | 多值匹配 |
| NESTED | NESTED(steps, status:error) | 嵌套 JSON 数组内搜索 |
| MUST / SHOULD / MUST_NOT | Lucene 模式 | 布尔逻辑组合 |
| BM25 打分 | score() | 相关性排序 |
典型组合:多条件定位故障
-- 5 种算子组合:TERM + PHRASE + RANGE + NOT + IN
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)
')
五种算子编译成一棵查询树,逐行推进求值,支持 AND 短路优化。
BM25 打分排序
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;
score() 列直接暴露 BM25 相关性评分(IDF 加权 + 文档长度归一化),配合存储层 TopN 打分优化,无需全量结果上传到上层排序。
嵌套 JSON 搜索
-- steps 是一个 VARIANT 类型数组,NESTED 算子可穿透检索
WHERE search('NESTED(steps, status:error AND tool:code_exec)')
无需将 JSON 拆分成多张表,无需额外的 ETL 管道。
与 SQL 分析混用
-- search + JOIN:定位 OOM 错误,关联模型配置
SELECT l.request_id, l.error_msg, m.gpu_memory_limit
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);
核心价值:search() 是一个普通 WHERE 谓词,直接参与 JOIN、聚合、窗口函数、子查询。
search() vs 多个 MATCH:为什么更快?
以一个同时过滤 4 个字段的日志查询为例:
-- 传统 MATCH:每个条件独立求值,各自生成 bitmap
WHERE level MATCH_ANY 'ERROR'
AND module MATCH_ANY 'inference'
AND error_msg MATCH_PHRASE 'CUDA out of memory'
AND context MATCH_ANY 'gpu'
-- search():编译成查询树,统一求值
WHERE search('level:ERROR AND module:inference AND error_msg:"CUDA out of memory" AND context:gpu')
MATCH 的执行路径:
MATCH_ANY 'ERROR' → bitmap A(可能百万级)
MATCH_ANY 'inference' → bitmap B
MATCH_PHRASE 'CUDA...' → bitmap C
MATCH_ANY 'gpu' → bitmap D
最终 = A ∩ B ∩ C ∩ D ← 4 次交集运算 + 4 次 IndexReader 加载
search() 的执行路径:
- 逐行推进,AND 短路:第一个条件匹配行 #100 → 第二个条件直接跳到 #100 检查,不匹配则跳过,无需物化中间 bitmap
- 共享 IndexReader:多字段复用已打开的 reader 实例,无重复加载开销
- DSL 级别缓存:以整个 DSL 字符串为缓存 key,命中率更高
结论:条件越多、数据分布越倾斜,search() 的性能优势越明显。
从 Elasticsearch 迁移指南
第一步:建表 + 索引映射
| ES Mapping | SelectDB 列定义 |
|---|---|
"type": "text" | TEXT + INDEX ... USING INVERTED |
"type": "keyword" | VARCHAR + INDEX ... USING INVERTED |
"type": "long" | BIGINT |
"type": "date" | DATETIME |
第二步:查询改写
- GET /logs/_search
- { "query": { "query_string": { "query": "level:ERROR AND error_msg:\"timeout\"" } } }
+ SELECT * FROM logs
+ WHERE search('level:ERROR AND error_msg:"timeout"')
大部分查询仅需将 REST API 改成 SQL WHERE,DSL 语法不变。
第三步:迁移收益
| 维度 | Elasticsearch + ClickHouse | SelectDB search() |
|---|---|---|
| 存储成本 | 倒排 + 正排 + 副本,膨胀 2-3 倍 | 倒排 + 列存独立压缩,整体降 50% |
| 运维复杂度 | 两套集群 + 同步链路 | 一套集群 |
| 查询延迟 | 分钟级(需跨系统搬运数据) | 秒级(同一引擎内完成) |
| 语法学习成本 | 需掌握 ES DSL + SQL | 只需 SQL |
可复用优化 Checklist
- 建表时打开 V3 倒排索引格式:
"inverted_index_storage_format" = "V3",减少约 20% 索引体积 - 对高频搜索字段加倒排索引:
level、error_msg、context、model_name等 - 短语匹配字段设置
support_phrase = "true":对涉及"..."精确匹配的字段必须开启 - 使用 search() 替代多个 MATCH 谓词:条件数 ≥ 3 时性能优势明显
- 对 JSON 嵌套字段使用 VARIANT 类型 + NESTED 算子:无需拆表、无需 ETL
- 利用 BM25 打分 + score() 排序:替代手工相关性排序逻辑
-
parser = "unicode"处理中英文混合分词:无需额外安装分词器 - 迁移 ES 查询时仅改 REST API → SQL WHERE:DSL 语法完全兼容
- 按需增删索引,无需停服:先导数据,后续针对性加索引
关于 Apache Doris:Apache Doris 是高性能实时分析数据库,支持 PB 级数据亚秒级查询,广泛应用于报表分析、Ad-hoc 查询、统一数仓等场景。SelectDB 是 Apache Doris 的商业化公司,提供企业级支持和云服务。欢迎加入 Doris 社区 交流更多实践。
3698



被折叠的 条评论
为什么被折叠?



