实验性功能声明
索引迁移器目前为实验性特性,其 API、CLI 命令以及磁盘存储格式(计划、检查点、备份)在后续版本中可能发生变更。在生产环境应用迁移前,请务必仔细审查迁移计划。
本文将介绍如何使用 RedisVL 的迁移工具安全地修改已有索引的结构,无论是增加字段、删除字段、重命名,还是进行向量量化优化。
快速上手:4 条命令为索引添加新字段
bash
# 1. 查看当前存在的所有索引
rvl index listall --url redis://localhost:6379
# 2. 使用交互式向导生成迁移计划
rvl migrate wizard --index myindex --url redis://localhost:6379
# 3. 执行迁移
rvl migrate apply --plan migration_plan.yaml --backup-dir ./migration_backups --url redis://localhost:6379
# 4. 验证迁移结果
rvl migrate validate --plan migration_plan.yaml --url redis://localhost:6379
前置条件
- 已安装 RedisVL :
pip install redisvl - 运行中的 Redis 实例(推荐 Redis 8.0+,支持全部功能),且已加载 Search 模块(Redis Stack、Redis Cloud 或 Redis Software 均可)
- 待迁移的已有索引
本地开发环境(快速启动 Redis 8.0):
bashdocker run -d --name redis -p 6379:6379 redis:8.0注意:INT8/UINT8 向量数据类型需要 Redis 8.0+;SVS-VAMANA 算法需要 Redis 8.2+ 及 Intel AVX-512 硬件支持。
迁移工作原理(三阶段流程)
每一次迁移都遵循相同的三阶段流程:描述变更 → 生成计划 → 执行计划。下图清晰展示了单索引迁移的完整链路:
#mermaid-svg-92yMVGA8cK3yzYwl{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-92yMVGA8cK3yzYwl .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-92yMVGA8cK3yzYwl .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-92yMVGA8cK3yzYwl .error-icon{fill:#552222;}#mermaid-svg-92yMVGA8cK3yzYwl .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-92yMVGA8cK3yzYwl .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-92yMVGA8cK3yzYwl .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-92yMVGA8cK3yzYwl .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-92yMVGA8cK3yzYwl .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-92yMVGA8cK3yzYwl .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-92yMVGA8cK3yzYwl .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-92yMVGA8cK3yzYwl .marker{fill:#333333;stroke:#333333;}#mermaid-svg-92yMVGA8cK3yzYwl .marker.cross{stroke:#333333;}#mermaid-svg-92yMVGA8cK3yzYwl svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-92yMVGA8cK3yzYwl p{margin:0;}#mermaid-svg-92yMVGA8cK3yzYwl .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-92yMVGA8cK3yzYwl .cluster-label text{fill:#333;}#mermaid-svg-92yMVGA8cK3yzYwl .cluster-label span{color:#333;}#mermaid-svg-92yMVGA8cK3yzYwl .cluster-label span p{background-color:transparent;}#mermaid-svg-92yMVGA8cK3yzYwl .label text,#mermaid-svg-92yMVGA8cK3yzYwl span{fill:#333;color:#333;}#mermaid-svg-92yMVGA8cK3yzYwl .node rect,#mermaid-svg-92yMVGA8cK3yzYwl .node circle,#mermaid-svg-92yMVGA8cK3yzYwl .node ellipse,#mermaid-svg-92yMVGA8cK3yzYwl .node polygon,#mermaid-svg-92yMVGA8cK3yzYwl .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-92yMVGA8cK3yzYwl .rough-node .label text,#mermaid-svg-92yMVGA8cK3yzYwl .node .label text,#mermaid-svg-92yMVGA8cK3yzYwl .image-shape .label,#mermaid-svg-92yMVGA8cK3yzYwl .icon-shape .label{text-anchor:middle;}#mermaid-svg-92yMVGA8cK3yzYwl .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-92yMVGA8cK3yzYwl .rough-node .label,#mermaid-svg-92yMVGA8cK3yzYwl .node .label,#mermaid-svg-92yMVGA8cK3yzYwl .image-shape .label,#mermaid-svg-92yMVGA8cK3yzYwl .icon-shape .label{text-align:center;}#mermaid-svg-92yMVGA8cK3yzYwl .node.clickable{cursor:pointer;}#mermaid-svg-92yMVGA8cK3yzYwl .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-92yMVGA8cK3yzYwl .arrowheadPath{fill:#333333;}#mermaid-svg-92yMVGA8cK3yzYwl .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-92yMVGA8cK3yzYwl .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-92yMVGA8cK3yzYwl .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-92yMVGA8cK3yzYwl .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-92yMVGA8cK3yzYwl .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-92yMVGA8cK3yzYwl .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-92yMVGA8cK3yzYwl .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-92yMVGA8cK3yzYwl .cluster text{fill:#333;}#mermaid-svg-92yMVGA8cK3yzYwl .cluster span{color:#333;}#mermaid-svg-92yMVGA8cK3yzYwl div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-92yMVGA8cK3yzYwl .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-92yMVGA8cK3yzYwl rect.text{fill:none;stroke-width:0;}#mermaid-svg-92yMVGA8cK3yzYwl .icon-shape,#mermaid-svg-92yMVGA8cK3yzYwl .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-92yMVGA8cK3yzYwl .icon-shape p,#mermaid-svg-92yMVGA8cK3yzYwl .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-92yMVGA8cK3yzYwl .icon-shape .label rect,#mermaid-svg-92yMVGA8cK3yzYwl .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-92yMVGA8cK3yzYwl .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-92yMVGA8cK3yzYwl .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-92yMVGA8cK3yzYwl :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 阶段3: 执行迁移
阶段2: 生成迁移计划
阶段1: 构建变更描述
交互式向导
SchemaPatch YAML
手动编写
Planner.create_plan
MigrationPlan YAML
Executor.apply
MigrationReport YAML
阶段 1:构建 SchemaPatch(变更描述)
SchemaPatch 是一个 YAML 文件,它声明您想要进行的变更,而非完整的最终模式。您可以通过交互式向导生成,也可以手动编写。
Patch 包含五个可选的变更节:
| 节区 | 作用 |
|---|---|
add_fields |
向索引添加新字段定义 |
remove_fields |
从索引中移除字段(文档数据仍然保留,只是不再索引) |
rename_fields |
重命名字段,同时更新索引模式和所有文档(执行 HGET→HSET→HDEL 或 JSON 路径更新) |
update_fields |
修改字段属性:算法、数据类型、距离度量、是否可排序、分隔符等 |
index |
修改索引名称或键前缀 |
例如,一个手动编写的 schema_patch.yaml:
yaml
version: 1
changes:
add_fields:
- name: category
type: tag
path: $.category
attrs:
separator: "|"
remove_fields:
- legacy_field
update_fields:
- name: embedding
attrs:
datatype: float16 # 向量量化
algorithm: HNSW
阶段 2:生成 MigrationPlan(迁移计划)
Planner 组件连接 Redis,对当前在线索引进行快照 (包括 schema、统计信息、键样本和前缀),然后将 Patch 合并到源 schema 中,生成 merged_target_schema。同时,它会分类每个变更是否为支持 或阻塞,并提取重命名操作。
生成的计划 YAML 包含:
- source:计划生成时线上索引的冻结快照(schema、stats、key sample、prefixes)
- requested_changes:应用的 patch
- merged_target_schema:源 schema + patch → 迁移后的最终 schema
- diff_classification:是否支持迁移以及阻塞原因(如有)
- rename_operations:提取出的索引重命名、前缀变更和字段重命名
- warnings:重要提示(如是否需要停机、是否有损量化等)
关键点:同一个 Patch 对不同索引会生成不同的计划,因为每个索引的源 schema 不同。
阶段 3:执行迁移(Apply)
执行器读取计划并依次执行以下步骤:
#mermaid-svg-uFAkYVBv1gfLElIy{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-uFAkYVBv1gfLElIy .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-uFAkYVBv1gfLElIy .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-uFAkYVBv1gfLElIy .error-icon{fill:#552222;}#mermaid-svg-uFAkYVBv1gfLElIy .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-uFAkYVBv1gfLElIy .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-uFAkYVBv1gfLElIy .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-uFAkYVBv1gfLElIy .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-uFAkYVBv1gfLElIy .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-uFAkYVBv1gfLElIy .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-uFAkYVBv1gfLElIy .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-uFAkYVBv1gfLElIy .marker{fill:#333333;stroke:#333333;}#mermaid-svg-uFAkYVBv1gfLElIy .marker.cross{stroke:#333333;}#mermaid-svg-uFAkYVBv1gfLElIy svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-uFAkYVBv1gfLElIy p{margin:0;}#mermaid-svg-uFAkYVBv1gfLElIy .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-uFAkYVBv1gfLElIy .cluster-label text{fill:#333;}#mermaid-svg-uFAkYVBv1gfLElIy .cluster-label span{color:#333;}#mermaid-svg-uFAkYVBv1gfLElIy .cluster-label span p{background-color:transparent;}#mermaid-svg-uFAkYVBv1gfLElIy .label text,#mermaid-svg-uFAkYVBv1gfLElIy span{fill:#333;color:#333;}#mermaid-svg-uFAkYVBv1gfLElIy .node rect,#mermaid-svg-uFAkYVBv1gfLElIy .node circle,#mermaid-svg-uFAkYVBv1gfLElIy .node ellipse,#mermaid-svg-uFAkYVBv1gfLElIy .node polygon,#mermaid-svg-uFAkYVBv1gfLElIy .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-uFAkYVBv1gfLElIy .rough-node .label text,#mermaid-svg-uFAkYVBv1gfLElIy .node .label text,#mermaid-svg-uFAkYVBv1gfLElIy .image-shape .label,#mermaid-svg-uFAkYVBv1gfLElIy .icon-shape .label{text-anchor:middle;}#mermaid-svg-uFAkYVBv1gfLElIy .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-uFAkYVBv1gfLElIy .rough-node .label,#mermaid-svg-uFAkYVBv1gfLElIy .node .label,#mermaid-svg-uFAkYVBv1gfLElIy .image-shape .label,#mermaid-svg-uFAkYVBv1gfLElIy .icon-shape .label{text-align:center;}#mermaid-svg-uFAkYVBv1gfLElIy .node.clickable{cursor:pointer;}#mermaid-svg-uFAkYVBv1gfLElIy .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-uFAkYVBv1gfLElIy .arrowheadPath{fill:#333333;}#mermaid-svg-uFAkYVBv1gfLElIy .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-uFAkYVBv1gfLElIy .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-uFAkYVBv1gfLElIy .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-uFAkYVBv1gfLElIy .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-uFAkYVBv1gfLElIy .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-uFAkYVBv1gfLElIy .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-uFAkYVBv1gfLElIy .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-uFAkYVBv1gfLElIy .cluster text{fill:#333;}#mermaid-svg-uFAkYVBv1gfLElIy .cluster span{color:#333;}#mermaid-svg-uFAkYVBv1gfLElIy div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-uFAkYVBv1gfLElIy .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-uFAkYVBv1gfLElIy rect.text{fill:none;stroke-width:0;}#mermaid-svg-uFAkYVBv1gfLElIy .icon-shape,#mermaid-svg-uFAkYVBv1gfLElIy .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-uFAkYVBv1gfLElIy .icon-shape p,#mermaid-svg-uFAkYVBv1gfLElIy .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-uFAkYVBv1gfLElIy .icon-shape .label rect,#mermaid-svg-uFAkYVBv1gfLElIy .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-uFAkYVBv1gfLElIy .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-uFAkYVBv1gfLElIy .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-uFAkYVBv1gfLElIy :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
是
否
开始 Apply
枚举所有文档键
执行字段重命名(如有)
是否改变哈希向量类型?
备份原始向量字节到磁盘
跳过向量备份
删除源索引(FT.DROPINDEX)
执行键前缀重命名(如有)
是否改变哈希向量类型?
逐批读取、转换、写回向量
跳过向量量化
创建目标索引(FT.CREATE)
等待后台重建完成
验证结果
生成迁移报告
详细说明:
- 枚举键 (修改前):使用
FT.AGGREGATE WITHCURSOR高效枚举所有文档键;若索引有失败记录则回退到SCAN。 - 字段重命名 :若计划中有
rename_fields,则在删除原索引前执行文档字段的重命名(Hash 用管道 HGET/HSET/HDEL,JSON 用路径更新)。 - 备份原始向量 (仅当哈希向量类型变更时):将原始向量字节写入
<backup-dir>,用于崩溃恢复和回滚。 - 删除源索引 :执行
FT.DROPINDEX,仅删除索引结构,底层文档不受影响。此后索引暂时不可用。 - 键前缀重命名 (如有前缀变更):执行
RENAME或(集群环境)DUMP/RESTORE。 - 量化向量(仅当哈希向量类型变更时):对每个文档读取旧向量,转换数据类型(如 float32→float16),写回原文档。批量处理(默认 500 条/批)。
- 创建目标索引 :用
merged_target_schema执行FT.CREATE,Redis 开始后台索引已有文档。 - 等待索引就绪 :轮询
FT.INFO直到索引完成重建,此时索引可正常查询。
停机要求(关键!)
在 drop_recreate 迁移模式下,索引会暂时不可用。您的应用必须:
- 暂停所有读操作:因为索引被删除期间任何查询都会失败。
- 暂停所有写操作:因为写入的文档不会被索引(索引已删除);此外,若正在量化向量,并发写入会与迁移冲突。
| 停机类型 | 读操作 | 写操作 | 是否安全 |
|---|---|---|---|
| 完全静默(推荐) | 停止 | 停止 | ✅ 安全 |
| 只读暂停 | 停止 | 继续 | ❌ 不安全 |
| 活动状态 | 继续 | 继续 | ❌ 不安全 |
恢复建议 :若迁移中断,重新执行同一命令即可自动从中断点续传(要求使用 --backup-dir)。
向量量化的备份与崩溃安全恢复
当您将哈希向量从高精度类型(如 float32)转换为低精度类型(如 float16 或 int8)时,迁移器会在修改前将原始向量保存到磁盘,以实现崩溃安全恢复和手动回滚。
备份文件结构
<backup-dir>/
migration_backup_<index_name>.header # JSON: 进度状态、批次计数、字段元数据
migration_backup_<index_name>.data # 二进制: 按批次存储的原始向量(长度前缀+pickle)
migration_backup_<index_name>.manifest # JSON: 多worker分片恢复元数据(workers>1时)
- 磁盘占用 ≈ 文档数 × 向量维度 × 每个元素字节数
例如:100万文档 × 768维 × float32(4字节) ≈ 2.9 GB
状态机与续传
.header 文件记录了一个状态机,原子更新每个批次后写入:
dump → ready → index_dropped → active → completed → target_created → validated
dump:正在读取并备份原始向量ready:备份完成,原索引仍在线index_dropped:原索引已删,但向量尚未全部转换active:正在逐批转换向量completed:所有向量转换完成,等待创建目标索引target_created:目标索引已创建,正在重建或已就绪validated:迁移验证通过
若进程崩溃,重新运行相同命令,迁移器会读取 header,跳过已完成批次,从下一个未完成批次继续。
手动回滚
如需撤销量化迁移并恢复原始向量:
bash
rvl migrate rollback --backup-dir /tmp/backups --url redis://localhost:6379
该命令会将备份中的原始向量写回 Redis,但不会恢复索引定义,您需要手动重建原索引。
支持与阻塞的变更一览
| 变更类型 | 是否支持 | 说明 |
|---|---|---|
| 添加 text/tag/numeric/geo 字段 | ✅ 支持 | |
| 移除字段 | ✅ 支持 | |
| 重命名字段 | ✅ 支持 | 同时更新所有文档 |
| 更改键前缀 | ✅ 支持 | 执行 RENAME |
| 重命名索引 | ✅ 支持 | 仅索引级别 |
| 字段设为可排序 | ✅ 支持 | |
| 更改字段选项(分隔符、词干提取等) | ✅ 支持 | |
| 更改向量算法(FLAT↔HNSW↔SVS-VAMANA) | ✅ 支持 | 仅索引,无需改数据 |
| 更改距离度量(COSINE↔L2↔IP) | ✅ 支持 | 仅索引 |
| 调整 HNSW 参数(M、EF_CONSTRUCTION) | ✅ 支持 | 仅索引 |
| 量化向量(float32→float16/bfloat16/int8/uint8) | ✅ 支持 | 自动重新编码;若同一键被其他索引共享则不支持 |
| 更改向量维度 | ❌ 阻塞 | 需要重新嵌入,应使用新模型重新生成数据再迁移 |
| 更改存储类型(hash ↔ json) | ❌ 阻塞 | 数据格式不同,需导出转换后重新加载 |
| 添加新向量字段 | ❌ 阻塞 | 要求所有文档已有向量,应先填充向量再迁移 |
单索引迁移 CLI 参考
| 命令 | 描述 |
|---|---|
rvl migrate wizard |
交互式构建迁移(推荐) |
rvl migrate plan |
根据 patch 或目标 schema 生成计划 |
rvl migrate apply |
执行迁移 |
rvl migrate estimate |
估算迁移所需磁盘空间(干运行) |
rvl migrate validate |
验证迁移结果 |
rvl migrate rollback |
回滚向量数据(需备份目录) |
常用参数
--url:Redis 连接 URL--index:待迁移索引名--plan/--plan-out:计划文件路径--backup-dir:必需的备份目录,用于存放恢复数据--batch-size:每批处理的键数(默认 500)--workers:并行量化工作线程数(默认 1)--async:使用异步执行(适用于大规模迁移)--report-out:输出验证报告--benchmark-out:输出性能指标
批量迁移:一次 patch 应用于多个索引
当需要为多个索引应用相同变更(如统一量化所有索引的向量)时,使用批量迁移。
快速开始
bash
# 1. 创建共享 patch
cat > quantize_patch.yaml << 'EOF'
version: 1
changes:
update_fields:
- name: embedding
attrs:
datatype: float16
EOF
# 2. 生成批量计划(匹配所有后缀为 _idx 的索引)
rvl migrate batch-plan \
--pattern "*_idx" \
--schema-patch quantize_patch.yaml \
--plan-out batch_plan.yaml \
--url redis://localhost:6379
# 3. 执行批量迁移
rvl migrate batch-apply \
--plan batch_plan.yaml \
--backup-dir ./migration_backups \
--accept-data-loss \
--url redis://localhost:6379
# 4. 查看状态
rvl migrate batch-status --state batch_state.yaml
批量计划生成逻辑
批量计划器接受一个共享 patch,对每个目标索引测试其适用性:
- 若 patch 可应用(如字段存在、变更支持),则为该索引生成独立的
MigrationPlan并标记applicable: true - 若 patch 不适用(如字段缺失),标记
applicable: false并记录skip_reason,跳过该索引 - 前缀冲突检查:若两个索引的键前缀有重叠(一个为另一个的前缀),批量计划器将拒绝生成计划,以避免同一键被多次量化导致数据损坏。需将冲突索引分组分别处理。
批量执行与恢复
batch-apply按顺序逐个执行索引迁移,进度保存在batch_state.yaml中- 若中途中断,使用
batch-resume继续:rvl migrate batch-resume --state batch_state.yaml - 失败策略(
fail_fast或continue_on_error)在batch-plan时设置
Python API 示例
同步单索引迁移
python
from redisvl.migration import MigrationPlanner, MigrationExecutor
planner = MigrationPlanner()
plan = planner.create_plan(
"myindex",
redis_url="redis://localhost:6379",
schema_patch_path="schema_patch.yaml",
)
executor = MigrationExecutor()
report = executor.apply(
plan,
redis_url="redis://localhost:6379",
backup_dir="/tmp/migration_backups",
batch_size=500,
num_workers=4,
)
print(f"迁移结果: {report.result}")
异步 API
python
import asyncio
from redisvl.migration import AsyncMigrationPlanner, AsyncMigrationExecutor
async def migrate():
planner = AsyncMigrationPlanner()
plan = await planner.create_plan(...)
executor = AsyncMigrationExecutor()
report = await executor.apply(...)
print(report.result)
asyncio.run(migrate())
批量迁移 Python API
python
from redisvl.migration import BatchMigrationPlanner, BatchMigrationExecutor
planner = BatchMigrationPlanner()
batch_plan = planner.create_batch_plan(
redis_url="redis://localhost:6379",
pattern="*_idx",
schema_patch_path="quantize_patch.yaml",
)
executor = BatchMigrationExecutor()
report = executor.apply(
batch_plan,
redis_url="redis://localhost:6379",
backup_dir="/tmp/migration_backups",
state_path="batch_state.yaml",
)
print(f"成功: {report.summary.successful}/{report.summary.total_indexes}")
性能调优建议
批次大小(--batch-size)
- 默认 500 是良好平衡点
- 增大到 1000+ 可减少网络往返,但增加单次内存占用和延迟
- 视网络和 Redis 性能可调整到 200~1000 之间
并行工作线程(--workers)
- 用于向量量化阶段,每个 worker 独立连接 Redis
- 增加 worker 可加速量化,但会提高 Redis 连接数和 CPU 负载
- 建议从 2~4 开始测试
备份磁盘空间估算
使用估计命令提前计算:
bash
rvl migrate estimate --plan migration_plan.yaml
若开启 AOF,加上 --aof-enabled 以获得更精确的磁盘需求。
常见问题排查
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| 计划生成失败,提示"unsupported change" | 变更需要数据转换(如改变维度) | 按提示调整策略,或采用应用层迁移 |
| Apply 失败:"source schema mismatch" | 线上索引在计划生成后发生了改变 | 重新生成计划 |
| Apply 超时:"timeout waiting for index ready" | 数据量大,重建慢 | 增加超时时间或在低峰期执行 |
| 验证失败:"document count mismatch" | 计划生成后有文档增删 | 应用写操作暂停期间重新生成计划并执行 |
| 量化后另一索引中的文档消失 | 共享键导致类型冲突(不支持) | 回滚向量,使用应用层迁移创建新键或新字段,协调所有索引 |
| 批量计划生成报错"overlapping indexes" | 两个索引前缀重叠 | 将冲突索引分开到不同批次,或手动处理 |
| 恢复中断时提示备份不匹配 | 备份目录与当前计划不一致 | 删除旧备份目录,重新开始 |
总结
RedisVL 的索引迁移器提供了一套安全、可续传、可回滚的机制来演进您的索引结构。核心要点:
- 三阶段流程:Patch → Plan → Apply
- 必须停机:迁移期间索引不可用,需暂停读写
- 备份必备 :
--backup-dir为所有迁移强制要求,确保可恢复 - 向量量化:自动备份原始向量,支持崩溃续传和手动回滚
- 批量处理:同一 patch 可应用于多个索引,但需注意前缀冲突
在生产环境操作前,建议先在测试索引上演练,确认计划无误后再应用。迁移计划中的 diff_classification.supported 和 warnings 字段是决策的关键依据。