如何在Milvus中高效存储大规模布尔数组并支持检索过滤
优化门店库存存储与过滤的解决方案
针对布尔数组存储占用过高、需保留按门店ID过滤的需求,结合80%产品多数门店有货的业务特征,提供以下高效方案:
方案1:反转存储逻辑 - 仅存储缺货门店ID列表
由于多数产品在大部分门店有货,只记录缺货的门店ID而非全量布尔数组,可大幅压缩存储空间。
实现细节:
- 修改Milvus Schema:
product_id = FieldSchema( name="product_id", dtype=DataType.INT64, is_primary=True, auto_id=False ) product_title_vector = FieldSchema( name="product_title_vector", dtype=DataType.FLOAT_VECTOR, dim=768, ) # 替换原布尔数组为存储缺货门店ID的INT64数组 out_of_stock_stores = FieldSchema( name="out_of_stock_stores", dtype=DataType.ARRAY, element_type=DataType.INT64, max_capacity=600 # 按20%缺货率预设,可根据实际业务调整 )
数据写入规则:
每个产品仅收集缺货的门店ID存入out_of_stock_stores;若全门店有货,该字段为空数组。检索过滤逻辑:
查询指定门店有货的产品时,判断门店ID不在缺货列表中即可:
store_id = 1 # 过滤表达式:门店ID不在缺货列表内 filter_expr = "NOT contains(out_of_stock_stores, {})".format(store_id) search_params = {"metric_type": "L2", "params": {"nprobe": 16}} search_results = collection.search( data=query_vector_resized, anns_field="product_title_vector", expr=filter_expr, param=search_params, limit=1 )
空间优势:
按80%产品缺货门店数少于10个计算,单条记录平均占用80字节(10个INT64),500万条仅需40GB;对比原方案的15GB?不对——哦原方案3000个BOOL按字节存储是3KB/条,500万条15GB,此方案多数记录远小于3KB,整体存储量会显著低于原方案。若门店扩容至40000,原方案需200GB,此方案仍能保持较低存储规模。
方案2:位图压缩存储 - 将布尔数组打包为整数数组
把门店库存的布尔状态打包成64位整数的数组,每个整数存储64个门店的状态,大幅降低存储空间。
实现细节:
- 修改Milvus Schema:
product_id = FieldSchema( name="product_id", dtype=DataType.INT64, is_primary=True, auto_id=False ) product_title_vector = FieldSchema( name="product_title_vector", dtype=DataType.FLOAT_VECTOR, dim=768, ) # 用INT64数组存储位图,3000门店需ceil(3000/64)=47个INT64;40000门店需625个 store_availability_bitmap = FieldSchema( name="store_availability_bitmap", dtype=DataType.ARRAY, element_type=DataType.INT64, max_capacity=47 )
- 数据写入转换:
将布尔数组转换为位图整数:
def bool_array_to_bitmap(bool_array): bitmap = [] for i in range(0, len(bool_array), 64): chunk = bool_array[i:i+64] value = 0 for idx, val in enumerate(chunk): if val: value |= (1 << idx) bitmap.append(value) return bitmap # 示例:将3000个布尔值转换为位图 bool_array = [True]*3000 bitmap = bool_array_to_bitmap(bool_array)
- 检索过滤逻辑:
计算门店对应的位图索引和位位置,用位运算判断库存状态:
store_id = 1 store_idx = store_id - 1 # 转换为0-based索引 array_idx = store_idx // 64 bit_pos = store_idx % 64 # 过滤表达式:对应位为1表示有货 filter_expr = "(store_availability_bitmap[{}] & (1 << {})) != 0".format(array_idx, bit_pos) search_params = {"metric_type": "L2", "params": {"nprobe": 16}} search_results = collection.search( data=query_vector_resized, anns_field="product_title_vector", expr=filter_expr, param=search_params, limit=1 )
空间优势:
3000门店单条记录仅需376字节(47个INT64),仅为原方案的12.5%,500万条仅需1.88GB;40000门店单条记录需5KB(625个INT64),500万条仅需25GB,存储压力大幅降低。
方案3:分离向量与库存数据(备选)
若允许检索后二次过滤,可将产品向量存在Milvus,库存数据存入关系型数据库(如MySQL),Milvus检索得到product_id后,再去数据库查询对应门店的库存状态。此方案无法使用Milvus的expr过滤,适合实时性要求稍低的场景。
实现步骤:
- Milvus Schema仅保留
product_id和product_title_vector; - MySQL创建表:
product_stores(product_id INT64, store_id INT64, is_available BOOL),并为(product_id, store_id)建立联合索引; - Milvus检索得到
product_id列表后,执行SQL查询:SELECT product_id FROM product_stores WHERE product_id IN (...) AND store_id = ? AND is_available = true。
内容的提问来源于stack exchange,提问作者Mohamed Niyaz
相关产品推荐
相关产品推荐

