动态字段
什么是动态字段
动态字段(Dynamic Field)是指数据库可以根据客户写入的数据自动发现表Schema中未曾定义的字段,并自动修改Schema以新建该字段,最后能够完成数据正常下入的特性。基于动态字段特性,客户在早期建表时,不必预先指定不确定是否存在的,或者不确定字段的数据类型的潜在字段,从而实现更好的兼容性和灵活性。
VectorDB允许客户在建表时,设定是否启用动态字段特性。HTTP API建表接口中的enableDynamicField
参数和SDK建表接口中的enable_dynamic_field
参数用于设定此特性是否启用。API和SDK中均默认不启用。
动态字段特性实现原理
当代理节点接受到一个写入请求时,会逐个确认请求中的指定的字段(名称)是否在当前表Schema中存在,若有个/些字段确实尚不存在,那么进一步判断,该表是否允许动态字段特性,若不允许则报错并返回错误。若该表启用了动态字段特性,那么代理节点尝试解析并推理这个/些新字段的名字、取值和值的类型,只有在名字合法,且类型合法,且取值合理的情况下,才能进入下一步。下一步,代理节点向Master发送一个AddFields请求,描述需要新增的字段信息,只有当收到Master新建字段成功的消息之后,才能进入最后一步,即允许将数据最终写入到对应的分片中。即使代理节点预先做了全面的合法性校验,但是Master也不一定就会AddFields成功,核心原因在于,也许此时另外一个代理节点也在并发地向Master申请新建同名的字段,而且已经提前新建成功,并且当前这个代理节点对字段的属性描述与已经新建成功的该字段的属性描述并不完全一样,比如类型不一致,那么当前代理节点的AddFields请求自然不可能成功。
此外,代理节点在推导新字段的值的类型时,存在确定性的推导规则:
- 首先判断值的类型是否是
BOOL
类型,若是则认为推导成功; - 若无法解析为
BOOL
类型,则判断值的类型是否是字符串,若是则进一步按照DATETIME
、DATE
、UUID
的顺序进行解析,若均解析失败,则默认解析为STRING
类型,并认为推导成功; - 若判断不是字符串类型,则进一步按照
DOUBLE
浮点类型进行解析,若解析成功则认为推导成功; - 若无法解析为浮点类型,则进一步按照整型进行解析,具体而言,按照先
UINT64
后INT64
的顺序进行解析,若解析成功则认为推导成功; - 若上述解析均失败,则报错。
动态字段特性的使用限制
动态字段特性看起来确实带来了很大的灵活性,但仍然存在存在诸多的使用限制,说明如下:
- 支持有限的八种数据类型:根据上述的推导规则可以看出,动态字段只支持
BOOL
、DATETIME
、DATE
、UUID
、STRING
、DOUBLE
、UINT64
和INT64
这八种类型。 - 同名新字段以第一次推导并新建成功的为准:通过上述的原理描述可以看出,一旦一个新字段推导并新建成功,那么同名字段只能以这次的属性为准,若后续写入的数据,该字段的取值无法解析为当时推导出来的类型,则这次写入失败。
- 动态添加的字段无法建立二级索引:因为在新建该字段之前就已经插入的记录,它们对应的该字段的取值全是Null,因此无法对该字段建立二级索引。
使用示例
我们通过Python SDK展示一下动态字段特性的使用。
- 创建一个莫愁客户端对象。
import time
import json
import random
import pymochow
import logging
from pymochow.configuration import Configuration
from pymochow.auth.bce_credentials import BceCredentials
from pymochow.exception import ClientError, ServerError
from pymochow.model.schema import Schema, Field, SecondaryIndex, VectorIndex, HNSWParams
from pymochow.model.enum import FieldType, IndexType, MetricType, ServerErrCode
from pymochow.model.enum import TableState, IndexState
from pymochow.model.table import Partition, Row, AnnSearch, HNSWSearchParams
logging.basicConfig(filename='documentinsight.log', level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
if __name__ == "__main__":
account = '$您的账户名称'
api_key = '$您的账户API密钥'
endpoint = '$您的实例访问端点' # 例如:'http://127.0.0.1:5287'
db_name = 'DocumentInsight'
table_name = 'DynamicFieldTable'
# 根据配置创建一个MochowClient对象
config = Configuration(credentials=BceCredentials(account, api_key), endpoint=endpoint)
mochow_client = pymochow.MochowClient(config)
- 创建一个基本表,该表开启动态字段特性。
# 建库
db = mochow_client.create_database(db_name);
# 若库已存在,则直接使用下述代码获取一个库对象
# db = mochow_client.database(db_name)
# 建表
fields = []
fields.append(Field("DocId", FieldType.UUID, primary_key=True, not_null=True))
fields.append(Field("URL", FieldType.STRING, not_null=True))
fields.append(Field("Department", FieldType.STRING, partition_key=True, not_null=True))
table = db.create_table(
table_name=table_name,
enable_dynamic_field=True, # 启用动态字段特性
replication=1,
partition=Partition(partition_num=2),
schema=Schema(fields=fields, indexes=[])
)
while True:
time.sleep(1)
table = db.describe_table(table_name)
if table.state == TableState.NORMAL:
break
- 获取并观察该表的Schema信息。
# 打印该表的Schema信息
print(table.to_dict())
执行完上述代码之后,我们可以从控制台输出中观察到当前表的Schema信息,如下所示,当前表仅包含预先设定的3个字段:
{'database': 'DocumentInsight', 'table': 'DynamicFieldTable', 'description': '', 'replication': 1, 'partition': {'partitionType': <PartitionType.HASH: 'HASH'>, 'partitionNum': 2}, 'enableDynamicField': True, 'schema': {'fields': [{'fieldName': 'DocId', 'fieldType': 'UUID', 'primaryKey': True, 'notNull': True}, {'fieldName': 'URL', 'fieldType': 'STRING', 'notNull': True}, {'fieldName': 'Department', 'fieldType': 'STRING', 'partitionKey': True, 'notNull': True}], 'indexes': []}, 'aliases': [], 'createTime': '2024-03-14 20:17:15', 'state': <TableState.NORMAL: 'NORMAL'>}
- 插入一行数据,该行数据新增了7个字段,恰好覆盖了VectorDB当前所能支持的推导类型,且成功插入。
# 插入数据
rows = [
Row(DocId="37a9523d-3afb-f576-91ad-7075d6e3c8eb",
URL="bos://hellocompany/tech/HelloWorld产品技术详解.pdf", Department="技术部",
HasBeenModified=True, # 该字段将被推导为BOOL类型
LastModifyTime="2024-03-14T19:28:58Z", # 该字段将被推导为DATETIME类型
LastModifyDate="2024-03-14", # 该字段将被推导为DATE类型
Author="王建国", # 该字段将被推导为STRING类型
SizeInMB=1.001, # 该字段将被推导为DOUBLE类型
SizeInBytes=1049624, # 该字段将被推导为UINT64类型
SizeDiffInBytes=-996 # 该字段将被推导为INT64类型
),
]
table.insert(rows=rows)
- 再次描述该表,并打印该表的Schema信息。
# 再次获取表对象并输出Schema信息
time.sleep(1)
table = db.describe_table(table_name)
print(table.to_dict())
完成上述代码执行之后,我们可以从控制台输出中看到该表的新的Schema,如下所示,很明显,该表新增了7个字段且每个新增字段推导出来的类型完全符合第4步的预期:
{'database': 'DocumentInsight', 'table': 'DynamicFieldTable', 'description': '', 'replication': 1, 'partition': {'partitionType': <PartitionType.HASH: 'HASH'>, 'partitionNum': 2}, 'enableDynamicField': True, 'schema': {'fields': [{'fieldName': 'DocId', 'fieldType': 'UUID', 'primaryKey': True, 'notNull': True}, {'fieldName': 'URL', 'fieldType': 'STRING', 'notNull': True}, {'fieldName': 'Department', 'fieldType': 'STRING', 'partitionKey': True, 'notNull': True}, {'fieldName': 'HasBeenModified', 'fieldType': 'BOOL'}, {'fieldName': 'LastModifyTime', 'fieldType': 'DATETIME'}, {'fieldName': 'LastModifyDate', 'fieldType': 'DATE'}, {'fieldName': 'Author', 'fieldType': 'STRING'}, {'fieldName': 'SizeInMB', 'fieldType': 'DOUBLE'}, {'fieldName': 'SizeInBytes', 'fieldType': 'UINT64'}, {'fieldName': 'SizeDiffInBytes', 'fieldType': 'INT64'}], 'indexes': []}, 'aliases': [], 'createTime': '2024-03-14 20:04:58', 'state': <TableState.NORMAL: 'NORMAL'>}
- 查询该记录并打印。
# 标量查询
primaryKey = {}
primaryKey['DocId'] = "37a9523d-3afb-f576-91ad-7075d6e3c8eb"
partitionKey = {}
partitionKey['Department'] = "技术部"
results = table.query(primary_key=primaryKey, partition_key=partitionKey)
print("Query result: {}".format(results))
执行完上述操作之后,我们可以从控制台输出中看到该行的完整数据,确实已经包含了通过动态字段特性增加的字段,且取值与写入时的值完全一致:
Query result: {metadata:{content__length:u'347',content__type:u'application/json',request_id:u'572c6339-0606-4bce-a2e9-3112a3262821'},row:{'DocId': '37a9523d-3afb-f576-91ad-7075d6e3c8eb', 'URL': 'bos://hellocompany/tech/HelloWorld产品技术详解.pdf', 'Department': '技术部', 'HasBeenModified': True, 'LastModifyTime': '2024-03-14T19:28:58Z', 'LastModifyDate': '2024-03-14', 'Author': '王建国', 'SizeInMB': 1.001, 'SizeInBytes': 1049624, 'SizeDiffInBytes': -996},code:0,msg:u'Success'}