ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Doris 倒排索引 + Variant 日志最佳实践:Schema Template、多索引组合落地

Doris 倒排索引 + Variant 日志最佳实践:Schema Template、多索引组合落地 日志分析常见会卡在哪做日志、埋点或可观测性的同学多半遇到过类似情况业务日志是 JSON字段随版本迭代经常变。一种做法是先建模——建宽表、写 ETL 把 JSON 打平。字段一加就要改表结构、改写入链路排障高峰期等字段上线并不少见。另一种做法是整段 JSON 存成 TEXT。写入简单但查询时要对每行做json_extract数据量到亿级聚合往往先被 CPU 拖住。检索也是高频操作。排障常用的是按关键字过滤——trace_id、报错堆栈、用户 ID。LIKE %keyword%本质是扫描加字符串匹配数据量大时容易到分钟级而排障往往需要秒级反馈。这类场景通常绕不开三条约束Schema 易变字段增删频繁预先建模跟不上业务迭代检索需求强关键字/短语过滤是日常操作不能依赖全表扫描量大、价值密度低每天可能有 TB 级写入多数数据写完很少再读存储成本和写入吞吐都要算清楚。常见选型里Elasticsearch 检索能力强但逐条建索引会限制写入吞吐正排、倒排、doc_values 多份数据也会推高存储复杂聚合往往不是它的主场。Hive 一类数仓存储成本友好、分析能力强但通常要先 ETL 建模也缺少开箱即用的全文检索。Apache Doris 里倒排索引在 2.0 引入对应检索Variant在 2.1.02024 年 3 月达到 GA对应半结构化字段。两者版本不同不要误以为 2.0 就有稳定可用的 Variant。下面按实现和使用边界拆开讲方便判断能不能落到自己的链路里。Variant写入时把 JSON 拆成列是什么Variant 是 Doris 2.1.0 GA 的半结构化类型合法 JSON标量、数组、嵌套对象可以直接写入不必预先声明内部字段之后按路径访问子字段CREATE TABLE app_log ( ts DATETIME, service VARCHAR(64), level VARCHAR(16), message VARIANT, -- 整个 JSON 写进这一列 INDEX idx_msg (message) USING INVERTED ) DUPLICATE KEY(ts, service) PARTITION BY RANGE(ts) () DISTRIBUTED BY RANDOM BUCKETS 16; -- 按路径取子字段 SELECT ts, message[user][id] AS uid FROM app_log WHERE message[ret_code] 500 AND service gateway;常见表设计是少量静态列时间、服务名等高频过滤字段 一个 Variant 列放动态 payload。这也是官方文档里比较常见的写法。写入时做动态子列化容易误会成原文存着查询时再解析。实际不是Doris 在写入阶段就会做列式化大致分三步路径提取与类型推断解析 JSON抽出 Key Path如user.id并推断类型BIGINT、DOUBLE、STRING 等动态列化出现频率高的路径物化为独立内部子列——物理上user.id可以是单独的 BIGINT 列存稀疏列兜底低频或结构复杂的路径不单独建列避免列爆炸收进类 JSONB 的稀疏列。数据仍在只是查询通常慢于物化子列。使用侧仍然写v[user][id]查询引擎会尽量走物化子列沿用列存和向量化执行。两个工程边界需要提前知道子列数量有上限默认 2048 条路径variant_max_subcolumns_count可调超出的长尾进稀疏列类型漂移要盯同一路径出现兼容类型会自动拓宽TINYINT → BIGINT类型不兼容时该路径可能退化成 JSONB谓词下推会受影响查询会变慢——生产环境建议监控这类字段。子列化之后能吃到哪些列存能力子列化完成后每个子列可以走 Doris 现有的列存优化按数据分布选编码枚举型字典编码、连续数值 RLE 等存储更紧凑Path 级列裁剪只读实际访问的子列不必把整块 JSON 拉进内存解析配合延迟物化只为谓词命中的行解码投影列减少读放大。超宽表上的两个存储层优化日志、画像场景里单表物化子列可能到几千甚至上万会带来新的压力Doris 在存储格式层做了对应处理。元数据外置Externalize MetaSegment Footer 原先带着所有列的 ColumnMeta。列很多时即使只查几列也要完整加载并反序列化 Footer。优化做法是把列元数据从 Footer 剥离到独立数据页Footer 只留轻量指针按需加载目标列元数据。官方给出的一组实测10,000 个 Segment、每个 7,000 个物化子列打开速度 65s → 4s内存 60GB → 1GB 以内。子列级 Vertical Compaction传统 Compaction 一次合并要扫并重写所有列千列场景下内存和 I/O 都容易失控。现在会按列分组多轮合并每轮只加载部分列组。实测口径千列级表单次合并内存约 50GB → 2GB吞吐基本持平。这是 Variant 在动态 Schema 超宽表上能否长期跑稳的关键点之一。使用边界主键、排序键、JOIN 键必须用静态列Variant 不能承担这些角色高频过滤/分组字段时间戳、service、trace_id也建议提成顶层定长列——分区、分桶、排序键通常只认定长列发生过类型漂移的字段查询时往往需要显式 CASTJSON 高基数、字段数膨胀时要盯稀疏列占比和元数据开销。倒排索引把关键字检索从扫描变成定位是什么Doris 2.0 引入了基于 CLucene 的列级倒排索引。思路和搜索引擎一致写入时对文本分词建立词项 → 行号列表posting list查询时按词项定位行复杂度从与数据量相关变成与命中行数更相关。两种常见建法-- 不分词等值/精确匹配适合 keyword 类字段trace_id、status_code INDEX idx_trace (trace_id) USING INVERTED -- 分词全文检索适合 message、异常堆栈等文本 INDEX idx_msg (message) USING INVERTED PROPERTIES (parser chinese) -- 可选 english / chinese / unicode / standard查询侧常见三种匹配WHERE message MATCH_ANY timeout exception -- 分词后任一词命中 WHERE message MATCH_ALL timeout exception -- 所有词都命中不要求相邻 WHERE message MATCH_PHRASE Connection refused -- 短语查询词序连续实践上关键字里带冒号等特殊符号时MATCH_PHRASE往往比普通MATCH更稳能减少分词把关键字切碎导致的漏查。和 Variant 一起用时要注意什么对 Variant 列建倒排索引时会自动落到其文本子列上。对日志场景比较省事索引定义一次后续新增字段有机会被自动覆盖不用每次改表。但 Variant 默认不能当整体做检索WHERE v MATCH Doris或WHERE v LIKE %doris%都走不到对整段 JSON 的倒排MATCH 要落到具体子路径。如果确实需要对整段 JSON 做关键字检索官方更常见的落地方式有两种并行 STRING 列额外保留一列存原始 JSON 文本并对该 STRING 列建倒排索引Variant 列继续负责按路径分析启用 DOC mode让 Variant 在子列化之外保留可检索的文档形态DOC mode 通过variant_enable_doc_mode true开启写入时额外保留原始 JSON 的文档形态适合整段 JSON 返回或写入优先的场景与稀疏列模式互斥。物化后的高频子列还会进入 Doris 的原生索引体系和倒排一起做剪枝ZoneMap默认开启记录数据块内子列最大/最小值v[properties][price] 1000这类范围条件可跳过无关数据块倒排索引文本检索以及 order_id、trace_id 等高基数等值 / IN 定位BloomFilter存储开销更敏感时可作为高基数等值过滤的替代选择延迟物化先剪枝定位再对命中行解码投影避免给已过滤数据做无谓解码。一条v[path] op value谓词的大致路径有倒排索引则直接取行号没有索引但子列已物化可走 ZoneMap/BloomFilter 页级剪枝落在稀疏列里则只能有限下推加扫描最后经延迟物化进入向量化执行。对关键子路径可以用 Schema Template 预声明类型和索引同一路径挂两种索引——分词索引服务 MATCHkeyword 索引服务精确等值优化器按查询形式选择。写法形如VARIANTpath : TYPE, ...下面是示意语法细节可能随版本变化落地前建议对照最新 VARIANT SQL 参考CREATE TABLE tbl ( k BIGINT, v VARIANTcontent : STRING, -- 分词索引MATCH 全文检索 INDEX idx_tokenized(v) USING INVERTED PROPERTIES( parser english, field_pattern content, support_phrase true ), -- keyword 索引精确等值 INDEX idx_keyword(v) USING INVERTED PROPERTIES(field_pattern content) ); SELECT * FROM tbl WHERE v[content] MATCH Doris; -- 走分词索引 SELECT * FROM tbl WHERE v[content] Doris; -- 走 keyword 索引field_pattern支持通配符如logs.*可以对一类路径批量配置。使用边界前导通配LIKE %abc加速不了倒排索引覆盖不了这类写法高基数等值 / IN 过滤如order_id、trace_id本身是倒排索引的适合场景BloomFilter 更适合存储受限时作为替代而不是默认优先于倒排索引存储格式 V2 文件更少、写入 IOPS 更低更适合存算分离新集群可以优先评估开启。合在一起怎么看如果同时用上 Variant、倒排索引再配上列存压缩和 TTL开头三条约束大致对应如下痛点常见手段Schema 易变VariantJSON 直接写入高频路径自动列式化关键字检索慢倒排索引 MATCH_*用索引定位替代全表扫描存储成本高列式存储 ZSTD 压缩 分区 TTL / 冷热分层一条常见接入链路Filebeat/Agent → Kafka → Flink 或 Routine Load → Doris静态列 Variant 倒排索引→ Grafana / 日志平台。适合与不适合Variant 解决的是写入前不必把 JSON 全建模倒排索引解决的是关键字过滤尽量别全表扫列存压缩和 TTL 负责把存储成本压住。三者不是万能组合边界要分开看。相对适合日志检索、可观测性Log/Trace、埋点、审计——也就是半结构化数据、关键字检索、聚合分析混在一起的负载。相对不适合纯聚合、几乎无检索、字段稳定的传统数仓明细表普通定长列往往更简单依赖前导通配%abc的模糊匹配倒排帮不上重度依赖 ES 生态特有能力的场景复杂 relevance 打分、Percolator 等。选型时建议先对照自己的写入量、字段变更频率、检索形态精确 / 分词 / 短语和冷热保留策略再决定 Variant 和倒排要开到什么粒度而不是默认全开。
返回列表