|
| 1 | +#!/usr/bin/env python3 |
| 2 | +"""步骤 4:验证数据完整性 —— 对比源/目标 Milvus 的 collection、行数、索引,并做检索冒烟。 |
| 3 | +
|
| 4 | +运行: |
| 5 | + python3 04-verify.py |
| 6 | +""" |
| 7 | +import os |
| 8 | +import sys |
| 9 | + |
| 10 | +from pymilvus import connections, utility, Collection |
| 11 | + |
| 12 | +DIM = int(os.environ.get("TEST_DIM", "768")) |
| 13 | +TARGET = os.environ.get("TEST_COLLECTION", "migration_demo") |
| 14 | + |
| 15 | +SRC = dict( |
| 16 | + host=os.environ.get("SRC_MILVUS_HOST", "localhost"), |
| 17 | + port=os.environ.get("SRC_MILVUS_PORT", "19530"), |
| 18 | + user=os.environ.get("SRC_MILVUS_USER", "root"), |
| 19 | + password=os.environ.get("SRC_MILVUS_PASS", "Milvus"), |
| 20 | +) |
| 21 | +DST = dict( |
| 22 | + host=os.environ.get("DST_MILVUS_HOST", "localhost"), |
| 23 | + port=os.environ.get("DST_MILVUS_PORT", "19530"), |
| 24 | + user=os.environ.get("DST_MILVUS_USER", "root"), |
| 25 | + password=os.environ.get("DST_MILVUS_PASS", "Milvus"), |
| 26 | +) |
| 27 | + |
| 28 | + |
| 29 | +def snapshot(alias, conn): |
| 30 | + connections.connect(alias=alias, **conn) |
| 31 | + out = {} |
| 32 | + for name in utility.list_collections(using=alias): |
| 33 | + c = Collection(name, using=alias) |
| 34 | + c.load() |
| 35 | + out[name] = { |
| 36 | + "entities": c.num_entities, |
| 37 | + "partitions": len(c.partitions), |
| 38 | + "indexes": sorted(i.index_name for i in c.indexes), |
| 39 | + "fields": sorted(f.name for f in c.schema.fields), |
| 40 | + } |
| 41 | + return out |
| 42 | + |
| 43 | + |
| 44 | +def main(): |
| 45 | + src = snapshot("src", SRC) |
| 46 | + dst = snapshot("dst", DST) |
| 47 | + |
| 48 | + print("=== 源 Milvus ===") |
| 49 | + for k, v in src.items(): |
| 50 | + print(f" {k}: {v}") |
| 51 | + print("=== 目标 Milvus ===") |
| 52 | + for k, v in dst.items(): |
| 53 | + print(f" {k}: {v}") |
| 54 | + |
| 55 | + ok = True |
| 56 | + # 1) collection 集合一致 |
| 57 | + if set(src) != set(dst): |
| 58 | + print(f"[FAIL] collection 列表不一致: 仅源 {set(src)-set(dst)} / 仅目标 {set(dst)-set(src)}") |
| 59 | + ok = False |
| 60 | + |
| 61 | + # 2) 每个 collection 行数/分区/索引/字段一致 |
| 62 | + for name in set(src) & set(dst): |
| 63 | + s, d = src[name], dst[name] |
| 64 | + for key in ("entities", "partitions", "indexes", "fields"): |
| 65 | + if s[key] != d[key]: |
| 66 | + print(f"[FAIL] {name}.{key}: 源={s[key]} 目标={d[key]}") |
| 67 | + ok = False |
| 68 | + |
| 69 | + # 3) 目标检索冒烟 |
| 70 | + if TARGET in dst: |
| 71 | + c = Collection(TARGET, using="dst") |
| 72 | + c.load() |
| 73 | + res = c.search( |
| 74 | + data=[[0.0] * DIM], |
| 75 | + anns_field="embedding", |
| 76 | + param={"metric_type": "L2", "params": {"nprobe": 10}}, |
| 77 | + limit=5, |
| 78 | + ) |
| 79 | + hits = len(res[0]) |
| 80 | + print(f"[search] {TARGET} 返回 {hits} 条") |
| 81 | + if hits == 0: |
| 82 | + print(f"[FAIL] {TARGET} 检索返回 0 条") |
| 83 | + ok = False |
| 84 | + |
| 85 | + print("\n结果:", "PASS ✅" if ok else "FAIL ❌") |
| 86 | + sys.exit(0 if ok else 1) |
| 87 | + |
| 88 | + |
| 89 | +if __name__ == "__main__": |
| 90 | + main() |
0 commit comments