Flat JSON
Beta feature
本文介绍了Flat JSON的基本概念及如何使用此功能。
从2.2.0版本开始,StarRocks支持JSON数据类型,以实现更灵活的数据存储。然而,在查询JSON时,大多数场景并不涉及直接读取整个JSON数据,而是访问指定路径的数据。例如:
-- 将日志中需要的字段存储为固定字段,将其他随业务频繁变化的字段打包为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引入了Flat JSON功能,以提高JSON数据查询效率并降低使用JSON的复杂性。
- 此功能从3.3.0版本开始提供,默认禁用,需要手动启用。
什么是Flat JSON
Flat 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数据时,字段a
和b
在大多数JSON数据中存在且数据类型相似(均为INT)。因此,可以从JSON中提取字段a
和b
的数据,并分别存储为两个INT列。当在查询中使用这两列时,可以直接读取其数据,而无需处理额外的JSON字段,从而减少处理JSON结构的计算开销。
验证Flat JSON是否有效
导入数据后,可以查询相应列的提取子列:
SELECT flat_json_meta(<json_column>)
FROM <table_name>[_META_];
您可以通过观察以下指标,在Query Profile中验证执行的查询是否受益于Flat JSON优化:
PushdownAccessPaths
: 推送到存储的子字段路径数量。AccessPathHits
: Flat JSON子字段命中次数,包含具体JSON命中信息。AccessPathUnhits
: Flat JSON子字段未命中次数,包含具体JSON未命中信息。JsonFlattern
: 当Flat JSON未命中时,现场提取子列所花费的时间。
使用示例
-
启用该功能(参考其他章节)
-
创建包含JSON列的表。在此示例中,使用INSERT INTO将JSON数据加载到表中。
-- 方法1:创建包含JSON列的表,并在创建时配置Flat 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",
"flat_json.enable" = "true",
"flat_json.null.factor" = "0.5",
"flat_json.sparsity.factor" = "0.5",
"flat_json.column.max" = "50");
)
-- 方法2:需要启用Flat 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}')); -
查看
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)"] |
+---------------------------------------------------------------------------------------------------------------------------+ -
执行数据查询。
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]'; -
在Query Profile中查看Flat 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中的所有表模型都支持Flat JSON。
- 兼容历史数据,无需重新导入。历史数据将与Flat JSON扁平化的数据共存。
- 历史数据不会自动应用Flat JSON优化,除非加载新数据或进行Compaction。
- 启用Flat JSON会增加JSON的导入时间,提取的JSON越多,所需时间越长。
- Flat JSON仅支持物化JSON对象中的常用键,不支持JSON数组中的键。
- Flat JSON不改变数据排序方式,因此查询性能和数据压缩率仍会受到数据排序的影响。为了达到最佳性能,可能需要进一步调整数据排序。
版本说明
StarRocks存算一体集群从v3.3.0开始支持Flat JSON,存算分离集群从v3.3.3开始支持。
在v3.3.0、v3.3.1和v3.3.2版本中:
- 在加载数据时,支持提取常用字段并单独存储为JSON类型,无需类型推断。
- 提取的列和原始JSON数据都将存储。提取的数据将与原始数据一起删除。
从v3.3.3版本开始:
- Flat JSON提取的结果分为常用列和保留字段列。当所有JSON Schema一致时,不会生成保留字段列。
- Flat JSON仅存储常用字段列和保留字段列,不额外存储原始JSON数据。
- 在加载数据时,常用字段将自动推断为BIGINT/LARGEINT/DOUBLE/STRING类型。无法识别的类型将推断为JSON类型,保留字段列将存储为JSON类型。
启用Flat JSON功能(仅支持存算一体集群)
- 在创建表时,可以在表参数中设置
flat_json.enable
属性。参考表创建。 Flat JSON功能也可以通过直接修改表属性来启用或重新配置。示例:alter table t1 set ("flat_json.enable" = "true")
alter table t1 set ("flat_json.null.factor" = "0.1")
alter table t1 set ("flat_json.sparsity.factor" = "0.8")
alter table t1 set ("flat_json.column.max" = "90") - 启用FE分区裁剪功能:
SET GLOBAL cbo_prune_json_subfield = true;
启用Flat JSON功能(3.4版本之前)
- 修改BE配置:
enable_json_flat
,在3.4版本之前默认为false
。修改方法参考 配置BE参数 - 启用FE分区裁剪功能:
SET GLOBAL cbo_prune_json_subfield = true;