跳到主要内容
版本:v3.0.x

JSON 切碎 Compatible with Milvus 2.6.2+

通过将传统的基于行的存储转换为优化的列式存储,JSON 切碎可加速 JSON 查询。在保持 JSON 数据建模灵活性的同时,Milvus 在幕后执行列优化,从而显著提高了访问和查询效率。

JSON 切碎对大多数 JSON 查询场景都很有效。在以下情况下,性能优势会更加明显

  • 更大、更复杂的 JSON 文档- 随着文档大小的增加,性能收益也会增加

  • 读取繁重的工作负载--经常对 JSON 键进行过滤、排序或搜索

  • 混合查询模式- 不同 JSON 键的查询从混合存储方法中获益

工作原理

JSON 粉碎过程分为三个不同阶段,以优化数据,实现快速检索。

第 1 阶段:输入和密钥分类

随着新 JSON 文档的写入,Milvus 不断对其进行采样和分析,以建立每个 JSON 关键字的统计数据。这种分析包括关键字的出现率和类型稳定性(其数据类型在不同文档中是否一致)。

根据这些统计数据,JSON 关键字被分为以下几类,以便进行最佳存储。

JSON 关键字分类

| 键类型

|

描述

| | --- | --- | |

类型键

|

存在于大多数文档中且始终具有相同数据类型(如所有整数或所有字符串)的键。

| |

动态键

|

经常出现但具有混合数据类型的键(例如,有时是字符串,有时是整数)。

| |

共享键

|

不常出现或嵌套的键,低于可配置的频率阈值**。**

|

分类示例

考虑包含以下 JSON 键的 JSON 数据样本:

{"a": 10, "b": "str1", "f": 1}
{"a": 20, "b": "str2", "f": 2}
{"a": 30, "b": "str3", "f": 3}
{"a": 40, "b": 1, "f": 4} // b becomes mixed type
{"a": 50, "b": 2, "e": "rare"} // e appears infrequently

根据这些数据,这些键可分类如下:

  • 类型键af (始终为整数)

  • 动态键b (混合字符串/整数)

  • 共享键e (不经常出现的键)

第二阶段:存储优化

第 1 阶段的分类决定了存储布局。Milvus 使用专为查询优化的列格式。

Json Shredding FlowJson 粉碎流程

  • 切碎列:对于键入动态 ,数据被写入专用列。这种列式存储允许在查询时进行快速、直接的扫描,因为 Milvus 可以只读取给定键所需的数据,而无需处理整个文档。

  • 共享列:所有共享键都一起存储在一个紧凑的二进制 JSON 列中。在这一列上建立共享键反转索引。该索引对于加速低频键的查询至关重要,它允许 Milvus 快速剪裁数据,有效地将搜索空间缩小到仅包含指定键的行。

第 3 阶段查询执行

最后阶段利用优化的存储布局,为每个查询谓词智能选择最快的路径。

  • 快速路径:对键入/动态键(如json['a'] < 100 )的查询直接访问专用列

  • 优化路径:对共享键(如json['e'] = 'rare' )的查询使用倒排索引来快速查找相关文档

启用 JSON 切碎功能

要激活该功能,请在milvus.yaml 配置文件中将common.enabledJSONShredding 设置为true 。新数据将自动触发粉碎过程。

# milvus.yaml
...
common:
enabledJSONShredding: true # Indicates whether to enable JSON key stats build and load processes
...

一旦启用,Milvus 将在摄取时开始分析和重组 JSON 数据,而无需任何进一步的人工干预。

参数调整

对于大多数用户来说,一旦启用 JSON 切碎,其他参数的默认设置就足够了。不过,您可以使用milvus.yaml 中的这些参数对 JSON 切碎的行为进行微调。

| 参数名称

|

说明

|

默认值

|

调整建议

| | --- | --- | --- | --- | |

common.enabledJSONShredding

|

控制是否启用 JSON 切碎构建和加载流程。

|

|

必须设为true才能激活该功能。

| |

common.usingjsonShreddingForQuery

|

控制 Milvus 是否使用粉碎数据进行加速。

|

为真

|

设为false,作为查询失败时的恢复措施,恢复到原始查询路径。

| |

queryNode.mmap.jsonShredding

|

决定 Milvus 在加载粉碎数据时是否使用 mmap。

有关详情,请参阅使用 mmap

|

|

此设置通常为性能优化。只有在系统有特定内存管理需求或限制的情况下才会调整它。

| |

dataCoord.jsonShreddingMaxColumns

|

将存储在粉碎列中的 JSON 键的最大数量。

如果频繁出现的键的数量超过此限制,Milvus 将优先对最频繁出现的键进行粉碎,其余键将存储在共享列中。

|

1024

|

这足以满足大多数情况的需要。对于有数千个频繁出现密钥的 JSON,可能需要增加这一限制,但要监控存储空间的使用情况。

| |

dataCoord.jsonShreddingRatioThreshold

|

要将一个 JSON 密钥粉碎到粉碎列中,该密钥必须具备的最小出现率。

如果一个密钥的出现比率高于这个阈值,则该密钥被视为频繁出现。

|

0.3

|

如果符合粉碎标准的密钥数量超过dataCoord.jsonShreddingMaxColumns 限制,则增加(例如增加到 0.5)。这将使阈值更加严格,减少符合粉碎条件的钥匙数量。

如果你想粉碎更多出现频率低于默认 30% 阈值的密钥,则将阈值降低(例如降低到 0.1)。

|

性能基准

我们的测试表明,在不同的 JSON 密钥类型和查询模式下,性能都有显著提高。

测试环境和方法

  • 硬件:1 核/8GB 集群

  • 数据集来自JSONBench的 100 万个文档

  • 平均文档大小:478.89 字节

  • 测试持续时间100 秒,测量 QPS 和延迟

结果:键入键

该测试测量的是查询大多数文档中存在的键时的性能。

| 查询表达式

|

键值类型

|

QPS (不粉碎)

|

QPS (已粉碎)

|

性能提升

| | --- | --- | --- | --- | --- | |

json['time_us'] > 0

|

整数

|

8.69

|

287.50

|

33x

| |

json['kind'] == 'commit'

|

字符串

|

8.42

|

126.1

|

14.9x

|

结果:共享键

本测试重点查询属于 "共享 "类别的稀疏嵌套键。

| 查询表达式

|

键值类型

|

QPS (不粉碎)

|

QPS (已粉碎)

|

性能提升

| | --- | --- | --- | --- | --- | |

json['identity']['seq'] > 0

|

嵌套整数

|

4.33

|

385

|

88.9x

| |

json['identity']['did'] == 'xxxxx'

|

嵌套字符串

|

7.6

|

352

|

46.3x

|

关键信息

  • 共享关键字查询显示了最显著的改进(快达 89 倍)

  • 键入式查询可持续提高 15-30 倍性能

  • 所有查询类型都从 JSON 破碎处理中获益,性能没有下降

常见问题

  • 如何验证 JSON 破碎处理是否正常工作?

    1. 首先,使用Birdwatcher工具中的show segment --format table 命令检查数据是否已构建。如果成功,输出将在Json Key Stats 字段下包含shredding_data/shared_key_index/

      Birdwatcher OutputBirdwatcher 输出

    2. 接下来,在查询节点上运行show loaded-json-stats 验证数据是否已加载。输出将显示每个查询节点已加载碎纸数据的详细信息。

  • 如果遇到错误怎么办?

    如果构建或加载过程失败,可以通过设置common.enabledJSONShredding=false 快速禁用该功能。要清除任何剩余任务,请使用Birdwatcher 中的remove stats-task <task_id> 命令。如果查询失败,可设置common.usingjsonShreddingForQuery=false 恢复到原始查询路径,绕过粉碎数据。

  • 如何在 JSON 切碎和 JSON 索引之间进行选择?

    • JSON切碎非常适合文档中频繁出现的键,尤其是复杂的 JSON 结构。它结合了列式存储和反转索引的优点,非常适合查询许多不同键的重读取场景。不过,对于非常小的 JSON 文档,不建议使用这种方法,因为性能提升微乎其微。键值占 JSON 文档总大小的比例越小,粉碎带来的性能优化效果就越好。

    • JSON 索引更适合对基于特定键值的查询进行有针对性的优化,而且存储开销更低。它适用于较简单的 JSON 结构。请注意,JSON 切碎不包括对数组内部键的查询,因此需要 JSON 索引来加速这些查询。

    有关详情,请参阅JSON 字段概述