跳到主要内容
版本: 最新版本-3.5

扁平 JSON

本文介绍扁平 JSON 的基本概念以及如何使用此功能。

从 2.2.0 版本开始,StarRocks 支持 JSON 数据类型,以允许更灵活的数据存储。但是,在查询 JSON 时,大多数场景不涉及直接读取整个 JSON 数据,而是访问指定路径中的数据。例如

-- Store required fields in logs as fixed fields, and package other fields that frequently change with business as JSON.
SELECT
time,
event,
user,
get_json_string(remain_json, "$.from_system"),
get_json_string(remain_json, "$.tag")
FROM logs;

由于 JSON 类型的特殊性质,其在查询中的性能不如标准类型(INT、STRING 等)。原因包括

  • 存储开销:JSON 是一种半结构化类型,需要存储每一行的结构信息,导致存储使用率高且压缩效率低。
  • 查询复杂性:查询需要根据运行时数据检测数据结构,因此难以实现矢量化执行优化。
  • 冗余数据:查询需要读取整个 JSON 数据,其中包括许多冗余字段。

StarRocks 引入了扁平 JSON 功能,以提高 JSON 数据查询效率并降低 JSON 的使用复杂度。

  • 此功能从 3.3.0 版本开始提供,默认禁用,需要手动启用。

什么是扁平 JSON

扁平 JSON 的核心原则是在加载期间检测 JSON 数据,并从 JSON 数据中提取常用字段以作为标准类型数据存储。在查询 JSON 时,这些常用字段可以优化 JSON 的查询速度。示例数据

1, {"a": 1, "b": 21, "c": 3, "d": 4}
2, {"a": 2, "b": 22, "d": 4}
3, {"a": 3, "b": 23, "d": [1, 2, 3, 4]}
4, {"a": 4, "b": 24, "d": null}
5, {"a": 5, "b": 25, "d": null}
6, {"c": 6, "d": 1}

加载以上 JSON 数据时,字段 ab 出现在大多数 JSON 数据中,并且具有相似的数据类型(均为 INT)。因此,可以从 JSON 中提取字段 ab 的数据,并将其作为两个 INT 列单独存储。当在查询中使用这两列时,可以直接读取其数据,而无需处理额外的 JSON 字段,从而减少了处理 JSON 结构的计算开销。

验证扁平 JSON 是否有效

加载数据后,您可以查询相应列的提取子列

SELECT flat_json_meta(<json_column>)
FROM <table_name>[_META_];

您可以通过 查询 Profile 观察以下指标来验证执行的查询是否受益于扁平 JSON 优化

  • PushdownAccessPaths:下推到存储的子字段路径的数量。
  • AccessPathHits:扁平 JSON 子字段被命中的次数,其中包含有关特定 JSON 命中的详细信息。
  • AccessPathUnhits:扁平 JSON 子字段未被命中的次数,其中包含有关特定 JSON 未命中的详细信息。
  • JsonFlattern:未命中扁平 JSON 时现场提取子列所花费的时间。

使用示例

  1. 启用该功能(请参阅其他部分)

  2. 创建一个具有 JSON 列的表。在此示例中,使用 INSERT INTO 将 JSON 数据加载到表中。

    CREATE TABLE `t1` (
    `k1` int,
    `k2` JSON,
    `k3` VARCHAR(20),
    `k4` JSON
    )
    DUPLICATE KEY(`k1`)
    COMMENT "OLAP"
    DISTRIBUTED BY HASH(`k1`) BUCKETS 2
    PROPERTIES ("replication_num" = "3");

    INSERT INTO t1 (k1,k2) VALUES
    (11,parse_json('{"str":"test_flat_json","Integer":123456,"Double":3.14158,"Object":{"c":"d"},"arr":[10,20,30],"Bool":false,"null":null}')),
    (15,parse_json('{"str":"test_str0","Integer":11,"Double":3.14,"Object":{"a":"b"},"arr":[1,2,3],"Bool":true,"null":null}')),
    (15,parse_json('{"str":"test_str1","Integer":111,"Double":3.14,"Object":{"a":"b"},"arr":[1,2,3],"Bool":true,"null":null}')),
    (15,parse_json('{"str":"test_str2","Integer":222,"Double":3.14,"Object":{"a":"b"},"arr":[1,2,3],"Bool":true,"null":null}')),
    (15,parse_json('{"str":"test_str2","Integer":222,"Double":3.14,"Object":{"a":"b"},"arr":[1,2,3],"Bool":true,"null":null}')),
    (16,parse_json('{"str":"test_str3","Integer":333,"Double":3.14,"Object":{"a":"b"},"arr":[1,2,3],"Bool":true,"null":null}')),
    (17,parse_json('{"str":"test_str3","Integer":333,"Double":3.14,"Object":{"a":"b"},"arr":[1,2,3],"Bool":true,"null":null}')),
    (18,parse_json('{"str":"test_str5","Integer":444,"Double":3.14,"Object":{"a":"b"},"arr":[1,2,3],"Bool":true,"null":null}')),
    (19,parse_json('{"str":"test_str6","Integer":444,"Double":3.14,"Object":{"a":"b"},"arr":[1,2,3],"Bool":true,"null":null}')),
    (20,parse_json('{"str":"test_str6","Integer":444,"Double":3.14,"Object":{"a":"b"},"arr":[1,2,3],"Bool":true,"null":null}'));
  3. 查看 k2 列的提取子列。

    SELECT flat_json_meta(k2) FROM t1[_META_];
    +---------------------------------------------------------------------------------------------------------------------------+
    | flat_json_meta(k2) |
    +---------------------------------------------------------------------------------------------------------------------------+
    | ["nulls(TINYINT)","Integer(BIGINT)","Double(DOUBLE)","str(VARCHAR)","Bool(JSON)","Object(JSON)","arr(JSON)","null(JSON)"] |
    +---------------------------------------------------------------------------------------------------------------------------+
  4. 执行数据查询。

    SELECT * FROM t1;
    SELECT get_json_string(k2,'\$.Integer') FROM t1 WHERE k2->'str' = 'test_flat_json';
    SELECT get_json_string(k2,'\$.Double') FROM t1 WHERE k2->'Integer' = 123456;
    SELECT get_json_string(k2,'\$.Object') FROM t1 WHERE k2->'Double' = 3.14158;
    SELECT get_json_string(k2,'\$.arr') FROM t1 WHERE k2->'Object' = to_json(map{'c':'d'});
    SELECT get_json_string(k2,'\$.Bool') FROM t1 WHERE k2->'arr' = '[10,20,30]';
  5. 查询 Profile 中查看与扁平 JSON 相关的指标

       PushdownAccessPaths: 2
    - Table: t1
    - AccessPathHits: 2
    - __MAX_OF_AccessPathHits: 1
    - __MIN_OF_AccessPathHits: 1
    - /k2: 2
    - __MAX_OF_/k2: 1
    - __MIN_OF_/k2: 1
    - AccessPathUnhits: 0
    - JsonFlattern: 0ns

功能限制

  • StarRocks 中的所有表类型都支持扁平 JSON。
  • 与历史数据兼容,无需重新导入。历史数据将与扁平 JSON 展平的数据共存。
  • 除非加载新数据或发生 Compaction,否则历史数据不会自动应用扁平 JSON 优化。
  • 启用扁平 JSON 会增加加载 JSON 所花费的时间。提取的 JSON 越多,花费的时间越长。
  • 扁平 JSON 只能支持物化 JSON 对象中的常用键,而不支持 JSON 数组中的键。
  • 扁平 JSON 不会更改数据排序方法,因此查询性能和数据压缩率仍会受到数据排序的影响。要实现最佳性能,可能需要进一步调整数据排序。

版本说明

StarRocks shared-nothing 集群从 v3.3.0 开始支持扁平 JSON,shared-data 集群从 v3.3.3 开始支持。

在 v3.3.0、v3.3.1 和 v3.3.2 版本中

  • 加载数据时,它支持提取常用字段并将其作为 JSON 类型单独存储,无需类型推断。
  • 提取的列和原始 JSON 数据都会存储。提取的数据将随原始数据一起删除。

从 v3.3.3 版本开始

  • 扁平 JSON 提取的结果分为常用列和保留字段列。当所有 JSON Schema 一致时,不会生成保留字段列。
  • 扁平 JSON 仅存储常用字段列和保留字段列,而不额外存储原始 JSON 数据。
  • 加载数据时,常用字段会自动推断类型为 BIGINT/LARGEINT/DOUBLE/STRING。无法识别的类型将被推断为 JSON 类型,并且保留字段列将存储为 JSON 类型。

启用扁平 JSON 功能(3.4 版本之前)

  1. 修改 BE 配置:enable_json_flat,在 3.4 版本之前默认为 false。有关修改方法,请参阅 配置 BE 参数
  2. 启用 FE 剪枝功能:SET GLOBAL cbo_prune_json_subfield = true;

其他可选 BE 配置