Skip to content

数据迁移篇

定义版本升级时的数据迁移策略和向后兼容规范


版本化存储

存储结构

StorageSchema {
  version: number        // Schema 版本号
  data: object           // 业务数据
  migratedAt?: number    // 迁移时间
  previousVersion?: number  // 迁移前版本
}

版本命名规则

版本类型变更类型是否需迁移
Major (X.0)结构性变更
Minor (x.Y)新增字段可能
Patch (x.y.Z)修复/优化

迁移架构

迁移流程

应用启动


读取存储版本号

    ├── 版本相同 ──► 正常启动

    └── 版本不同


    获取迁移路径


    依次执行迁移函数

         ├── 成功 ──► 更新版本号 ──► 正常启动

         └── 失败 ──► 回滚 ──► 显示错误

迁移管理器接口

IMigrationManager {
  // 获取当前存储版本
  getCurrentVersion(): number
  
  // 获取目标版本
  getTargetVersion(): number
  
  // 执行迁移
  migrate(): Promise<MigrationResult>
  
  // 回滚到指定版本
  rollback(version: number): Promise<void>
  
  // 注册迁移函数
  registerMigration(migration: Migration): void
}

Migration {
  fromVersion: number
  toVersion: number
  up: (data: any) => Promise<any>    // 升级
  down: (data: any) => Promise<any>  // 降级
  description: string
}

MigrationResult {
  success: boolean
  fromVersion: number
  toVersion: number
  migrationsRun: number
  error?: Error
}

迁移策略

增量迁移

v1 ──► v2 ──► v3 ──► v4
     m1    m2    m3

从 v1 到 v4 需依次执行: m1 → m2 → m3

规范要求:

  • MUST 支持从任意旧版本迁移到最新版本
  • MUST 迁移函数幂等(重复执行结果相同)
  • SHOULD 每个迁移函数只做一件事

迁移超时

数据量级超时时间
< 100 条5s
100-1000 条30s
> 1000 条60s

迁移进度

大数据量迁移 SHOULD 显示进度:

┌─────────────────────────────────────┐
│         正在更新数据...              │
│                                     │
│  ████████████░░░░░░░░  60%         │
│                                     │
│  请勿关闭应用                        │
└─────────────────────────────────────┘

数据备份

备份时机

时机动作
迁移前自动备份当前数据
用户手动导出到文件
定期后台静默备份

备份数据结构

Backup {
  version: number
  timestamp: number
  data: EncryptedData
  checksum: string
}

备份存储

存储位置用途保留策略
本地存储迁移回滚保留最近 3 个版本
用户导出手动恢复用户管理

Schema 变更规范

向后兼容变更(无需迁移)

变更类型示例
新增可选字段添加 nickname?: string
放宽类型约束stringstring | null
新增枚举值添加新的链类型

需要迁移的变更

变更类型示例迁移方式
字段重命名addraddress复制并删除旧字段
字段类型变更stringnumber类型转换
结构重组扁平 → 嵌套重新组织
删除字段移除 deprecated清理旧字段
新增必填字段添加 createdAt设置默认值

迁移示例

示例 1: 字段重命名

// v1 → v2: wallets.addr → wallets.address

Migration {
  fromVersion: 1,
  toVersion: 2,
  description: '重命名 addr 为 address',
  
  up: async (data) => {
    data.wallets = data.wallets.map(w => ({
      ...w,
      address: w.addr,
      addr: undefined  // 删除旧字段
    }))
    return data
  },
  
  down: async (data) => {
    data.wallets = data.wallets.map(w => ({
      ...w,
      addr: w.address,
      address: undefined
    }))
    return data
  }
}

示例 2: 结构重组

// v2 → v3: 扁平结构 → 嵌套结构
// { walletId, ethAddress, btcAddress } 
// → { walletId, addresses: { eth, btc } }

Migration {
  fromVersion: 2,
  toVersion: 3,
  description: '地址改为嵌套结构',
  
  up: async (data) => {
    data.wallets = data.wallets.map(w => ({
      walletId: w.walletId,
      addresses: {
        eth: w.ethAddress,
        btc: w.btcAddress
      }
    }))
    return data
  }
}

示例 3: 新增必填字段

// v3 → v4: 新增 createdAt 必填字段

Migration {
  fromVersion: 3,
  toVersion: 4,
  description: '添加创建时间字段',
  
  up: async (data) => {
    const now = Date.now()
    data.wallets = data.wallets.map(w => ({
      ...w,
      createdAt: w.createdAt || now  // 默认值
    }))
    return data
  }
}

错误处理

迁移失败处理

迁移失败


记录错误日志


尝试回滚

    ├── 回滚成功 ──► 恢复旧版本数据
    │               │
    │               ▼
    │          显示"更新失败,请重试"

    └── 回滚失败 ──► 从备份恢复

                    ├── 恢复成功 ──► 显示"已恢复"

                    └── 恢复失败 ──► 显示"数据损坏"


                              引导重新导入钱包

错误类型

错误处理方式
迁移函数异常回滚到迁移前
数据格式不匹配尝试修复或跳过
存储空间不足提示清理空间
超时允许重试

测试规范

迁移测试要求

测试项说明
正向迁移v(n) → v(n+1)
跨版本迁移v1 → v(latest)
回滚测试v(n+1) → v(n)
边界数据空数据、极大数据
损坏数据格式错误、缺失字段

测试数据准备

  • MUST 为每个历史版本准备测试数据
  • MUST 包含边界情况数据
  • SHOULD 包含真实用户数据脱敏样本

版本兼容矩阵

支持的迁移路径

起始版本目标版本支持
v1v2+
v2v3+
...latest

不再支持的版本

版本废弃日期处理方式
v0.x2024-01提示重新导入

本章小结

  • 所有存储数据必须带版本号
  • 支持从任意旧版本增量迁移
  • 迁移前自动备份,失败可回滚
  • 每个迁移函数独立、幂等、可逆
  • 完整的迁移测试覆盖

Released under the MIT License.