MongoDB迁移实战:先画数据地图,再决定怎么搬
文章目录先把问题画出来第一步给集合做一次体检第二步先选择迁移路线第三步全量和增量要分开验证第四步把切换门槛写出来最后什么时候不值得迁移一次失败演练比一次成功导入更有价值发布前再做一次依赖扫描我以前做数据库迁移最容易犯的错误是过早进入“搬数据”阶段。看到源库里有几个集合先写导出脚本看到目标端能建表马上开始导入。等第一批数据落地后才发现同一个字段有三种类型旧索引根本没有覆盖线上查询某个定时任务还连着旧库甚至还有一个隐藏的报表接口没有出现在项目文档里。所以这次 MongoDB迁移我给自己定了一个规矩第一天不迁数据只画数据地图。所谓数据地图不是画一张漂亮的架构图而是回答几个很具体的问题哪些数据必须原样保留哪些数据适合重组哪些查询必须兼容哪些数据未来要和关系、GIS、向量信息关联切换失败时从哪里回退。先把问题画出来典型的企业系统往往是这样开始的订单在关系库设备事件在 MongoDB缓存放在 Redis文件元数据又有一套服务。每个选择单独看都合理但业务一旦需要“查某个设备最近一周的异常、关联它的维修记录再按园区位置筛选”应用就要跨多个系统拼接结果。业务接口订单/主数据关系库MongoDB设备文档Redis缓存ETL同步报表/AI应用业务接口KingbaseES融合数据底座关系数据文档数据GIS数据向量与时序数据库内关联分析这里的结论不是“多套数据库一定不能用”。如果数据边界清晰、同步延迟可以接受、团队也能稳定维护多库架构完全可以成立。问题在于MongoDB 迁移时不能只计算“搬走多少文档”还要判断迁移后能不能减少跨库同步、重复权限和重复备份。第一步给集合做一次体检文档数据库的灵活模式很适合业务快速变化但它也会把数据质量问题藏得比较深。一个字段今天是数字明天可能变成字符串一条文档有payload另一条文档的同位置字段叫data。应用代码可能已经适应了这些差异迁移脚本却未必能。我会先用mongosh做只读盘点。下面的脚本故意不打印原始业务值只统计字段类型、索引和数量适合先在脱敏环境运行。constcollectionNamedevice_events;constsampleSize2000;constcollectiondb.getCollection(collectionName);functiontypeCount(field){returncollection.aggregate([{$sample:{size:sampleSize}},{$project:{valueType:{$type:$field}}},{$group:{_id:$valueType,count:{$sum:1}}},{$sort:{count:-1}}]).toArray();}printjson({collection:collectionName,estimatedCount:collection.estimatedDocumentCount(),indexes:collection.getIndexes().map(index({name:index.name,key:index.key,unique:Boolean(index.unique)})),deviceIdTypes:typeCount(device_id),eventTimeTypes:typeCount(event_time),payloadTypes:typeCount(payload)});我会把脚本结果放进迁移清单而不是只在终端看一眼。清单至少应该有下面这些列对象要记录的内容后续动作字段类型分布、空值率、最大长度建立映射或异常清洗规则查询过滤、排序、分页、更新形成回放用例索引字段、唯一性、使用频率保留、重建或淘汰任务定时作业、报表、接口切换前逐项改连接权限账号、角色、敏感字段重新做最小权限配置生命周期增长量、保留期、备份设计冷热和归档策略第二步先选择迁移路线MongoDB 迁移并不只有一种路线。我会把方案分成三类然后按业务特征选择KingbaseES 的兼容能力和融合数据架构为前两种路线提供了选择空间迁移不一定要把所有文档拆成关系表也不一定要让文档永远脱离交易数据。对项目来说“0代码修改”更应该看作兼容性验证目标而不是跳过驱动、事务、分页和异常处理测试的承诺。如果选择“文档保留、统一治理”的方式我会先设计一张最小的目标结构。下面是示意 SQL字段类型和向量扩展写法需要以具体 KES 版本为准。CREATETABLEdevice_event_docs(event_idBIGINTPRIMARYKEY,device_idVARCHAR(64)NOTNULL,event_timeTIMESTAMPNOTNULL,event_typeVARCHAR(64)NOTNULL,event_payload JSON,site_codeVARCHAR(64),source_versionVARCHAR(32)NOTNULL,created_atTIMESTAMPNOTNULLDEFAULTCURRENT_TIMESTAMP);CREATEINDEXdevice_event_docs_device_time_idxONdevice_event_docs(device_id,event_timeDESC);SELECTevent_id,event_time,event_payloadFROMdevice_event_docsWHEREdevice_id:device_idANDevent_time:from_timeORDERBYevent_timeDESCFETCHFIRST100ROWSONLY;这个结构没有试图把event_payload里的每个键都固定成列因为我还没有足够证据证明这些键稳定。先保留文档弹性同时把设备、时间、事件类型和来源版本这些查询与治理需要的字段提到外层通常比一上来做“大拆表”更稳。第三步全量和增量要分开验证迁移真正危险的地方不是导入一百万条数据时出错而是全量导入完成后线上又发生了十万次更新而增量链路漏了其中一部分。否是否是冻结对象清单全量导出目标端批量写入数量/字段/抽样哈希通过?定位批次与映射规则记录增量起点持续同步新增/更新/删除回放真实接口与报表切换门槛通过?低峰切换并观察保留旧库回退窗口我会把数据校验写成三层而不是只比较总数量数量校验按集合、日期、事件类型分组内容校验对业务主键和关键字段做抽样哈希行为校验回放真实查询比较排序、分页、空结果、重复更新和删除后的结果。可以用下面的伪代码表达批处理的基本要求forbatchinread_source_batches(order_by_id,batch_size5000):batch_idmake_batch_id(batch)ifcheckpoint.exists(batch_id):continuenormalized[normalize_document(doc)fordocinbatch]target.write_idempotently(normalized,keyevent_id)source_digestdigest(batch,fieldsCHECK_FIELDS)target_digesttarget.digest(normalized,fieldsCHECK_FIELDS)ifsource_digest!target_digest:raiseMigrationError(fbatch{batch_id}validation failed)checkpoint.commit(batch_id)关键点有三个批次可重跑、写入幂等、校验成功后才推进断点。没有这三个条件迁移程序很容易在网络抖动后产生重复数据或无法定位的半批次状态。第四步把切换门槛写出来平时大家总说“运行稳定”。但其实这四个字根本没法落地去执行。那我会怎么做呢我会在真正做切换之前把这些门槛全都罗列成一张表。接着呢就是拉着写应用的人、管数据库的人还有搞运维的同事大家一起碰一下确认没问题了才算完。检查项通过条件示例失败后的动作数据追平增量位点已追平继续同步不切换关键集合数量和抽样校验通过定位批次并重跑核心接口结果集、排序、分页一致修正映射或驱动配置性能P95 在项目基线范围内调整索引和查询备份可恢复到演练时间点重新做备份演练回退旧库仍可读回退步骤有人执行延长观察窗口等你把数据都迁到 KingbaseES 之后呢像关系型的数据、文档型的数据还有向量数据、GIS数据以及时序数据这些东西就可以放在同一个管理范围里面去管了。如果你的系统刚好需要把几种不同类型的数据混在一起查这个情况就挺有用的。不过你要知道这并不是说你就不用管数据建模了。权限怎么分、备份怎么演练这些活一样都落不下。按我的实际经验来看的话往往是你越跟老板强调这次迁移“少改代码”那你背后写的那些验证脚本就越得抠细节一点马虎不得。最后什么时候不值得迁移咱们先说一种情况。如果你的系统其实仅仅只是拿来临时采个日志数据存不了多久就被清理了。而且现在跑 MongoDB 的这套运维流程已经很熟了业务那边呢也压根不需要搞什么跨数据模型的联合分析。这种情况你要是为了所谓的“技术统一”去硬迁那纯粹是给自己找麻烦。那什么时候才值得去搞这个项目呢我一般会看这几个信号。比如现在的数据同步管道已经卡顿了拖慢了实时性。又比如你库太多了权限乱七八糟的备份记录查起来都费劲。再有一种情况同一份数据你总是要反反复复去跟主数据或者空间数据做关联查询。还有就是你们团队就是想弄一套多集群的架构把不同业务的可用性和扩展需求都兜在里边。就我个人的感觉而言做 MongoDB 迁移它的终点绝对不是把那个旧库直接给删掉就完事了。说白了它其实就是一次得能拿出证据来证明的数据底层调整。调整成什么样呢就是数据这堆东西得清楚它自己到底归谁管。应用那边也得明白自己到底靠着哪些东西在跑。运维同事得知道万一出事了怎么恢复。以后要是再做关系型查询、文档查询或者智能检索也就不用再搞一长串恶心的同步任务去硬拼在一起了。一次失败演练比一次成功导入更有价值我会在正式切换前专门安排一次“故意失败”的演练。做法并不复杂先让增量同步在半途停止再恢复任务再人为制造一条字段类型不符合映射规则的数据最后模拟一个应用仍然使用旧连接字符串。每个故障都要回答三个问题监控多久能发现谁负责处理恢复后怎样证明没有重复或遗漏。这类演练经常暴露出迁移方案里最真实的空白。例如批处理日志只写了“失败”没有记录主键范围团队就无法判断重跑会不会覆盖已经成功的数据又如增量同步根据修改时间取数据却没有处理两条记录时间相同的边界恢复后可能漏掉最后一个时间点的数据。解决办法通常不是增加更多人工操作而是把水位线、主键、批次号和重试次数写进状态表。我也会关注删除操作。新增和更新比较容易被同步任务捕获删除往往被忽略最后目标端残留大量“逻辑上不存在”的文档。比较稳妥的做法是保留一个删除标记或变更事件再由目标端按明确规则处理如果业务不允许物理删除则应该把留存和审计要求写进方案而不是让同步脚本自行决定。另一个容易低估的因素是索引建立时机。全量写入过程中就建立所有二级索引可能让导入速度明显下降全部导入后再建索引又要确认增量写入窗口如何处理。我会按查询优先级安排唯一性和幂等所需索引先建非核心分析索引在全量完成后建立再对增量链路和典型接口做一次回归。这样做不一定最快但故障定位会清楚得多。迁移文档最后还应有一个“谁不在迁移范围内”的列表。缓存、文件对象、外部消息队列、临时脚本和第三方报表经常不属于数据库数据本身却可能依赖旧库地址。把这些边界列出来才能避免切换当晚才发现某个小服务仍然在写旧系统。发布前再做一次依赖扫描在真正宣布迁移完成前我还会扫描应用配置、CI 变量、定时任务和运维脚本中的旧库地址与旧账号。这个动作看似和数据无关却经常能找到遗漏的影子服务。扫描结果必须有人确认哪些可以删除、哪些需要改造、哪些必须保留到观察期结束。只有数据链路和调用链路都完成切换迁移才算真正闭环。