Milvus JSON 实用手册大放送:更简便、更灵活、更贴心

原创
06/30 18:41
阅读数 101


作为全球领先的开源向量数据库,Milvus 一直致力于满足不同用户的场景和需求,聆听社区的声音。最近,我们发现,很多用户的数据中常常包含各种不确定类型的数据,也有用户提出希望以 RESTful API 的方式访问 Milvus。


为此,我们引入了 dynamic field 的概念。通过开启 dynamic field,用户可以自由插入各种类型的数据,包括 Milvus 支持的所有数据类型。即使对于同一个键,在不同行中,其对应的值的类型也可以不同。然而,对于 Milvus 来说,动态增加列的方式过于复杂。因此,Milvus 选择用 JSON DataType 来实现 dynamic field。


简单来说,当用户插入 dynamic field 数据时,Milvus 会将这些数据整合到一个 JSON 中,并在表中新增一个名为 $meta 的列(请注意,不允许用户的动态列中出现以 $meta 为键的数据)。用户的 dynamic data 将会插入到 $meta 列中。


通过这种方式,Milvus 保证了使用的简便性,并提供了动态字段的灵活性,以满足不同用户的需求。



01.

Dynamic Field 定义


所谓 dynamic field 是指不在用户定义的 schema 中的扩展列。例如用户的 collection 只有 id(int64), vector(float_vector) 列,但用户 enable_dynamic_field = True. 这个时候如果用户插入的数据是 {id: 0, vector:[1.0,2.0,...,100.0], x: "abc"},这行数据里多一个 x 列,这个 x 列就会被当做 dynamic field。


02.

Dynamic Field 和 JSON DataType 在 collection 中的体现(以 python sdk 为例)


  • Create Collection

要想使用 dynamic field 的功能,需要在建表时开启。其字段名称为 enable_dynamic_field = True.


from pymilvus import (
    connections,
    utility,
    FieldSchema, CollectionSchema, DataType,
    Collection,
)

connections.connect()

int64_field = FieldSchema(name="int64", dtype=DataType.INT64, is_primary=True)
json_field = FieldSchema(name="json", dtype=DataType.JSON)
float_vector = FieldSchema(name="float_vector", dtype=DataType.FLOAT_VECTOR, dim=128)

schema = CollectionSchema(fields=[int64_field, json_field, float_vector], enable_dynamic_field=True)
hello_milvus = Collection("hello_milvus", schema=schema)


在这个例子中,明确指定了三列 schema,其类型分别为 INT64、 JSON、 FLOAT_VECTOR。但由于 enable_dynamic_field = True, Milvus 会在后台新建一列类型为 JSON 的列来存放动态数据,这个列的列名是 $meta,类型是 JSON,describe_collection 不会看到这一列。


>>> hello_milvus
<Collection>:
-------------
<name>: hello_milvus
<description>: 
<schema>: {'auto_id'False'description''''fields': [{'name''int64''description''''type': <DataType.INT64: 5>, 'is_primary'True'auto_id'False}, {'name''json''description''''type': <DataType.JSON: 23>}, {'name''float_vector''description''''type': <DataType.FLOAT_VECTOR: 101>, 'params': {'dim'128}}], 'enable_dynamic_field'True}

  • Insert


可以通过行式插入的方式插入动态数据。


import numpy as np
rng = np.random.default_rng(seed=19530)
rows = []
rows.append({"int64"0"float_vector": rng.random((1128))[0], "json": {"a"1"b"True"c""abcd""d": [1,2,3,4], "e"100}, "x""1234""y"123"z"10.12})
rows.append({"int64"1"float_vector": rng.random((1128))[0], "json": {"a"2"b"True"c""abc""d": [2,3,4,5,6], "f""hh"}, "x""abcd""y"10})
rows.append({"int64"2"float_vector": rng.random((1128))[0], "json": {"a"3"b"True"c""ab""d": ["a""b""c"], "g""aa"}, "x""1234""z"228})
rows.append({"int64"3"float_vector": rng.random((1128))[0], "json": {"a"4"b"True"c""a""d": ["a""e""f"]}, "y""abcd""z""zyx"})
rows.append({"int64"4"float_vector": rng.random((1128))[0], "json": {"a"5"b"True"c""aa""d": [8,7,6,5]}, "$x""1234""y"123})
rows.append({"int64"5"float_vector": rng.random((1128))[0], "json": {"a"6"b"True"c""aaa""d": [1]}, "x""abcd""$y""haha"})
# invalid data, the key of a dynamic field cannot be "$meta".
# rows.append({"int64": 0, "float_vector": rng.random((1, 128))[0], "json": {"a": 1, "b": True, "c": "abcd", "d": [1,2,3,4]}, "x": "abcd", "y": "haha", "$meta": "abc"})
hello_milvus.insert(rows)


在上面的例子中,插入了 6 行数据,其中每一行的数据中都包含 int64, float_vector 和 json 之外,还包含一些其他的动态数据,"x", "y", "z", "x""y" 等。其中 int64, float_vector 和 json 的数据会插入到对应的列中,后面的动态数据每一行会组织成一个 json 格式插入 meta  行  meta 列中插入的数据为{"x": "1234", "y": 123, "z": 10.12}。第二行的数据为 {"x": "abcd", "y": 10},注意在插入的数据中动态数据的 key 不能为 $meta, 否则插入时会报错。

  • CreateIndex and Load


index_type = "IVF_FLAT"
index_params = {"nlist"128}
hello_milvus.create_index("float_vector",
                          index_params={"index_type": index_type, "params": index_params, "metric_type""L2"})
hello_milvus.load()

(注意:我们现在还不支持 json 列构建索引。)

  • Search or Query


search 或者 query 时都可以对 JSON field 或者 dynamic field 进行表达式过滤,过滤的方式与之前的标量列类似。但需要注意的几点是:


  • 不可以直接对 json 列进行过滤,如表达式 json == 1 是非法的。


  • 如果要访问 json 列的 key,需要明确写清楚列名+[key], 如 json["a"]


expr = r'json["a"] > 3'
res = hello_milvus.search([rng.random((1128))[0]], "float_vector", {"metric_type""L2"}, limit=6, expr=expr, output_fields=["$meta"])
print(res)

['["id: 3, distance: 17.838302612304688, entity: {\'y\': \'abcd\', \'z\': \'zyx\'}", "id: 4, distance: 19.294292449951172, entity: {\'$x\': \'1234\', \'y\': 123}", "id: 5, distance: 22.257793426513672, entity: {\'x\': \'abcd\', \'$y\': \'haha\'}"]']


  1. 如果访问的是 dynamic field 对应的 key,是可以忽略不写 列名的。如上面的 y,表达式可以是 y > 10 后台会将其 fullback 到 meta  $meta["y "] > 10


4. 如果 json 底层的实际数据与 表达式中的常量类型不匹配,算不命中,不会报错。


expr = r'y > 10'
res = hello_milvus.search([rng.random((1128))[0]], "float_vector", {"metric_type""L2"}, limit=3, expr=expr, output_fields=["$meta"])
print(res)

['["id: 4, distance: 21.50458526611328, entity: {\'$x\': \'1234\', \'y\': 123}", "id: 0, distance: 21.527101516723633, entity: {\'x\': \'1234\', \'y\': 123, \'z\': 10.12}"]']

(如该例子中只返回两条 y 的类型是 int 并且大于 10 的数据。)


5. 如果想访问 json 中的 array 数据,可以通过下标来访问,暂不支持直接拿来与 array 直接比较。

  • 表达式 json["d"] == [1,2,3,4] 是不正确的。

  • 表达式可以是 json["d"][0] > 1

expr = r'json["d"][0] > 1'
res = hello_milvus.search([rng.random((1128))[0]], "float_vector", {"metric_type""L2"}, limit=6, expr=expr, output_fields=["json"])
print(res)

['[\'id: 4, distance: 0.0, entity: {\\\'json\\\': b\\\'{"a":5,"b":true,"c":"aa","d":[8,7,6,5]}\\\'}\', \'id: 1, distance: 21.50458526611328, entity: {\\\'json\\\': b\\\'{"a":2,"b":true,"c":"abc","d":[2,3,4,5,6],"f":"hh"}\\\'}\']']

6. 从上面的例子中可以看到,outputFields 可以指定 schema 中的列,也可以指定是 $meta, 当然也可以是 dynamic field 的 key。如:

expr = r'y > 10'
res = hello_milvus.search([rng.random((1128))[0]], "float_vector", {"metric_type""L2"}, limit=6, expr=expr, output_fields=["y"])
print(res)

['["id: 0, distance: 18.593538284301758, entity: {\'y\': 123}", "id: 4, distance: 19.290794372558594, entity: {\'y\': 123}"]']

(本例中就只取回了 y 列。)


  1. 当我们只想查询 json 或者 dynamic field 中包含某个key 的数据时,就可以用 exists 表达式进行过滤。

expr = r'exists x'
res = hello_milvus.search([rng.random((1128))[0]], "float_vector", {"metric_type""L2"}, limit=6, expr=expr, output_fields=["$meta"])
print(res)

['["id: 1, distance: 21.053813934326172, entity: {\'x\': \'abcd\', \'y\': 10}", "id: 0, distance: 21.699125289916992, entity: {\'x\': \'1234\', \'y\': 123, \'z\': 10.12}", "id: 5, distance: 22.007081985473633, entity: {\'x\': \'abcd\', \'$y\': \'haha\'}", "id: 2, distance: 25.92767333984375, entity: {\'x\': \'1234\', \'z\': 228}"]']


expr = r'exists json["g"]'
res = hello_milvus.search([rng.random((1128))[0]], "float_vector", {"metric_type""L2"}, limit=6, expr=expr, output_fields=["json"])
print(res)

['[\'id: 2, distance: 21.64354705810547, entity: {\\\'json\\\': b\\\'{"a":3,"b":true,"c":"ab","d":["a","b","c"],"g":"aa"}\\\'}\']']

8. 当 dynamic field 的key 中包含特殊字符时(除 identitier 之外的,即「非字母数字下划线组合」),例如+, -, *, /, $ 等等特殊符号,如果想用表达式做过滤,需要明确写 $meta[key],例如现在要想对上面插入的 $x 做过滤,表达式只能是 $meta["$x"] == "1234" 这种类型,不能使用 fullback 的那种方式。

expr = r'$meta["$x"] == "1234"'
res = hello_milvus.search([rng.random((1128))[0]], "float_vector", {"metric_type""L2"}, limit=6, expr=expr, output_fields=["$meta"])
print(res)

['["id: 4, distance: 21.93911361694336, entity: {\'$x\': \'1234\', \'y\': 123}"]']


🌟全托管 Milvus SaaS/PaaS 即将上线,由 Zilliz 原厂打造!覆盖阿里云、百度智能云、腾讯云、金山云。目前已支持申请试用,企业用户 PoC 申请或其他商务合作请联系 business@zilliz.com。


张财
Zilliz 软件工程师

本文分享自微信公众号 - ZILLIZ(Zilliztech)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部