不能直接从 MongoDB 4.2 升到 5.0
。官方要求路径为 4.2 → 4.4 → 5.0。
基于 MongoDB 官方升级文档与当前 Java/Maven 代码静态扫描,梳理升级路径、兼容变化、项目代码改造点、运维步骤、测试清单和风险评估。
flowchart LR A[MongoDB 4.2] --> B[MongoDB 4.4] B --> C[FCV 4.4] C --> D[MongoDB 5.0] D --> E[FCV 5.0] E --> F[Burn-in 与回归]
不能直接从 MongoDB 4.2 升到 5.0;短期升级应采用 4.2 → 4.4 → 5.0,先控制 FCV 与 legacy UUID 风险,再规划中长期 STANDARD UUID 数据治理。
系统计划将 MongoDB 从 4.2 升级到 5.0。本方案基于 MongoDB 官方升级文档与当前 Java/Maven 代码静态扫描,梳理升级路径、breaking/compatibility changes、项目代码改造点、运维升级步骤、测试验证清单和风险评估。
核心结论:
。官方要求路径为 4.2 → 4.4 → 5.0。
4.2 → 4.4 → 5.0,每次设置 FCV 前应先 burn-in 验证。
spring-data-mongodb:4.4.3mongodb-driver-sync:5.2.1mongodb-driver-core:5.2.1bson:5.2.1MongoClientSettings官方要求:
MongoDB 4.2 → MongoDB 4.4 → MongoDB 5.0
FCV 4.2 → FCV 4.4 → FCV 5.0
推荐节奏:
4.2。4.2 burn-in 一段时间。db.adminCommand({ setFeatureCompatibilityVersion: "4.4" })
4.4 burn-in 一段时间。db.adminCommand({ setFeatureCompatibilityVersion: "5.0" })
| 类别 | MongoDB 5.0 变化 | 对系统影响 |
|---|---|---|
| Shell | legacy mongo shell 在 5.0 deprecated,推荐 mongosh | 外部运维脚本、CI、手工 runbook 如果用 mongo,需要迁移或验证 |
| 命令参数 | 很多命令遇到未知参数从“忽略”变成“报错” | 低层 driver command / 运维脚本要排查 |
| TTL 索引 | expireAfterSeconds: NaN 在 5.0.0-5.0.13 可能被当作 0,导致数据立即过期 | 必须查线上索引元数据,源码里没看到 TTL 定义不代表库里没有 |
| Write Concern | 5.0 默认 write concern 大多变为 { w: "majority" },有 arbiter 时有例外 | 可能影响写入延迟/可靠性预期;需要 DBA 确认拓扑 |
| Update 顺序 | update operator 处理字段顺序变化:字符串字段按字典序,数字字段按数字序 | 如果有同一次 update 内 order-sensitive / 冲突字段,需排查 |
| Geo | geoHaystack 移除 | 如果线上存在该索引,FCV 5.0 后有风险 |
| Replica Set 配置 | replSetInitiate / replSetReconfig 对 IP host 配置更严格,推荐 hostname | 不能仅看应用 URI,要查 rs.conf() |
| mapReduce | 5.0 deprecated mapReduce,官方建议改 aggregation | 本项目源码没发现 mapReduce 调用,但外部脚本仍需查 |
| Aggregation | pipeline 最多 1000 stages,部分 stage/事务/readConcern 限制 | 本项目有聚合,建议 5.0 staging 回归 |
因为 4.2 必须先升到 4.4,所以也需要关注 4.4 的兼容变化:
| 类别 | MongoDB 4.4 变化 | 对系统影响 |
|---|---|---|
| Removed commands | cloneCollection、部分 plan cache 命令移除 | 外部脚本如使用需改造 |
| Projection | find() / findAndModify() projection 规则变化,path collision 更严格 | 复杂 projection 需要回归 |
| Sort | cursor.sort() 与 aggregation $sort 行为更一致,重复值排序稳定性可能变化 | 排序结果如依赖重复值顺序,需加 _id tie-breaker |
| mapReduce | reduce 调用、输出字段等行为变化 | 源码未发现 mapReduce,但外部脚本仍需查 |
| Structured logging | mongod/mongos 日志变为结构化 JSON | 日志采集/解析规则可能要调整 |
| listIndexes | 不再返回 ns 字段 | 外部索引检查脚本如依赖 ns 需改造 |
项目父 POM:
pom.xml:5 使用 parent com.qdum.dmtx:dmt-maven-parent:3.4.3.8-jdk17-SNAPSHOTMongoDB starter:
biz-common/pom.xml:128 声明 spring-boot-starter-data-mongodb已验证依赖树:
org.springframework.boot:spring-boot-starter-data-mongodb:3.4.3
org.springframework.data:spring-data-mongodb:4.4.3
org.mongodb:mongodb-driver-sync:5.2.1
org.mongodb:mongodb-driver-core:5.2.1
org.mongodb:bson:5.2.1
判断:
主 MongoTemplate 配置:
biz-common/src/main/java/com/qdum/dmtx/biz/common/dao/mongodb/JourneyMongoConfig.java:17统一 MongoClientSettings:
biz-common/src/main/java/com/qdum/dmtx/biz/common/dao/mongodb/AbstractMongoConfig.java:25关键配置:
MongoClientSettings settings = MongoClientSettings.builder()
.uuidRepresentation(UuidRepresentation.STANDARD)
.applyConnectionString(new ConnectionString(getUri()))
.build();
同时配置了:
_class 字段写入主 Mongo:
biz-common/resources/config/application.yml:45spring:
data:
mongodb:
uri: ${mongodb.uri}
SMS Mongo:
biz-common/resources/config/application.yml:60mongodb-sms:
dbname: journey_sms
uri: ${mongodb-sms.uri}
另有 request-record 相关 Mongo 配置位于 application 配置后部。
判断:
spring.data.mongodb.uri,还要覆盖 mongodb-sms、request-record 等 side database。统一配置使用:
UuidRepresentation.STANDARDbiz-common/src/main/java/com/qdum/dmtx/biz/common/dao/mongodb/AbstractMongoConfig.java:27但 questionnaire 模块存在手工 legacy UUID 编解码:
questionnaire/src/main/java/com/qdum/dmtx/biz/questionnaire/service/FormReportService.java:1038questionnaire/src/main/java/com/qdum/dmtx/biz/questionnaire/service/QuestionAnswerService.java:680questionnaire/src/main/java/com/qdum/dmtx/biz/questionnaire/web/controller/FormReportResource.java:138示例:
byte[] bytes = UuidHelper.encodeUuidToBinary(UUID.fromString(strId + ""), UuidRepresentation.JAVA_LEGACY);
Binary binary = new Binary(UUID_LEGACY, bytes);
风险:
_id 查询、删除、回显可能出现查不到旧数据或删除不完整。建议方案:
适用条件:
改造内容:
LegacyUuidBinaryUtils。UuidHelper + UUID_LEGACY 逻辑集中到工具类。优点:
缺点:
适用条件:
改造内容:
_id 和外键字段 BSON binary subtype。JAVA_LEGACY / UUID_LEGACY。优点:
AbstractMongoConfig 一致。缺点:
推荐:
发现部分代码绕过统一配置,直接:
MongoClients.create(uri)
典型位置:
etl/src/main/java/com/qdum/dmtx/biz/etl/service/HwSyncService.java:109scrm/src/main/java/com/qdum/dmtx/biz/scrm/service/iemail/EmailRecycleService.java:1065etl/src/main/java/com/qdum/dmtx/biz/etl/service/HwSyncSendService.javamigration/src/main/java/com/qdum/dmtx/biz/migration/service/nike/NikeSmsReportService.java风险:
UuidRepresentation.STANDARD。建议改造:
MongoClientFactory 或配置 Bean。MongoClients.create(String) 统一收敛。uuidRepresentationserverSelectionTimeoutMSconnectTimeoutMSsocketTimeoutMSretryWritesappNameauthSource典型位置:
whatsapp/src/main/java/com/qdum/dmtx/biz/whatsapp/service/WhatsappTemplateLogToMongoService.java:78whatsapp/src/main/java/com/qdum/dmtx/biz/whatsapp/service/WhatsappTemplateLogToMongoService.java:82whatsapp/src/main/java/com/qdum/dmtx/biz/whatsapp/service/WhatsappTemplateLogToMongoService.java:87当前模式:
long count = mongoCollection.countDocuments();
if (count == 0) {
IndexOptions options = new IndexOptions();
options.background(true);
mongoCollection.createIndex(...);
}
问题:
countDocuments() == 0 不能可靠代表索引不存在。background(true) 在新版本 MongoDB 中已经不是旧语义,不建议继续依赖。建议改造:
listIndexes() 检查索引是否存在。background(true) 语义。发现以下模式:
BasicDBObjectDBObjectMongoOperations.getCollection(...)MongoDatabase.getCollection(...)典型位置:
whatsapp/src/main/java/com/qdum/dmtx/biz/whatsapp/service/WhatsappTemplateLogToMongoService.java:87etl/src/main/java/com/qdum/dmtx/biz/etl/service/HwSyncService.javacontact/src/main/java/com/qdum/dmtx/biz/contact/common/utils/MongoDbUtils.javartjourney/src/main/java/com/qdum/dmtx/biz/rtjourney/service/DataReportService.javawhatsapp/src/main/java/com/qdum/dmtx/biz/whatsapp/service/ZendeskInfoService.java判断:
建议:
DocumentFiltersIndexesAggregates建议在 Mongo URI 或统一 settings 中明确以下参数:
authSourcereplicaSetretryWritesreadPreferenceserverSelectionTimeoutMSconnectTimeoutMSsocketTimeoutMSappName注意:
源码静态扫描没有发现以下问题,但线上必须检查。
db.adminCommand({ getParameter: 1, featureCompatibilityVersion: 1 })
预期:
4.24.4db.getCollectionNames().forEach(function (coll) {
db.getCollection(coll).getIndexes().forEach(function (idx) {
if (Object.is(idx.expireAfterSeconds, NaN)) {
print(db.getName() + "." + coll + " has NaN TTL index: " + tojson(idx));
}
});
});
处理策略:
db.getCollectionNames().forEach(function (coll) {
db.getCollection(coll).getIndexes().forEach(function (idx) {
if (idx.key && Object.values(idx.key).includes("geoHaystack")) {
print(db.getName() + "." + coll + " has geoHaystack index: " + tojson(idx));
}
});
});
处理策略:
2d / 2dsphere 相关索引与查询方式。rs.conf()
重点检查:
members[n].host 是否为 IP。db.adminCommand({ getDefaultRWConcern: 1 })
重点检查:
源码未发现 mapReduce,但外部脚本和线上任务仍需检查:
mapReduce$function$accumulator$wheredb.evalCodeWithScopedb.version()
db.adminCommand({ getParameter: 1, featureCompatibilityVersion: 1 })
mongo shell。ROLLBACK / RECOVERING。rs.stepDown()
db.adminCommand({ setFeatureCompatibilityVersion: "4.4" })
db.adminCommand({ setFeatureCompatibilityVersion: "5.0" })
如为 sharded cluster,顺序为:
注意:
mongos 执行。mvn -f /Users/jack/IdeaProjects/dmtx/biz-projects/pom.xml \
-pl biz-common \
-DskipTests \
dependency:tree \
-Dincludes=org.springframework.data:spring-data-mongodb,org.mongodb:mongodb-driver-sync,org.mongodb:mongodb-driver-core,org.mongodb:bson
预期:
rg -n "UUID_LEGACY|UuidHelper|UuidRepresentation|JAVA_LEGACY" /Users/jack/IdeaProjects/dmtx/biz-projects
rg -n "MongoClients\.create|getCollection\(|countDocuments\(|insertOne\(|updateOne\(|createIndex\(" /Users/jack/IdeaProjects/dmtx/biz-projects
rg -n "aggregate\(|newAggregation|Aggregation\.|allowDiskUse\(" /Users/jack/IdeaProjects/dmtx/biz-projects
rg -n "mapReduce|expireAfterSeconds|geoHaystack|ensureIndex|copyTo\(|db\.eval" /Users/jack/IdeaProjects/dmtx/biz-projects
| 模块 | 回归内容 | 重点风险 |
|---|---|---|
| questionnaire | 表单提交记录查询、删除、导出 | legacy UUID 读写兼容 |
| template log 写入、索引创建、统计查询 | 动态表、动态索引、字符串日期 | |
| etl | Huawei sync / request-record 聚合 | side MongoClient、手写 pipeline |
| rtjourney | 日志写入、报表聚合 | aggregation 行为和性能 |
| journey | MongoRepository CRUD | Spring Data 映射 |
| TaskLog repository CRUD | Date / String id 映射 | |
| scrm | iemail recycle / export 相关 Mongo 逻辑 | side database 连接配置 |
升级前后对比:
建议对以下路径做压测或至少批量回放:
关注指标:
| 风险 | 严重级别 | 可能性 | 说明 | 应对策略 |
|---|---|---|---|---|
| 直接 4.2 → 5.0 | 高 | 中 | 官方不支持直接跳升 | 严格执行 4.2 → 4.4 → 5.0 |
| FCV 过早设置 5.0 | 高 | 中 | 会启用不可向后兼容特性,影响 rollback | binary 升级后先 burn-in,再设置 FCV |
| questionnaire UUID 读写不一致 | 高 | 高 | STANDARD 与 JAVA_LEGACY 混用 | 明确 UUID 策略,封装或迁移 |
| TTL NaN 导致数据过期 | 高 | 低到中 | 源码没看到,但线上索引可能存在 | 升级前查所有索引元数据 |
| side MongoClient 配置不一致 | 中高 | 中 | 直接 MongoClients.create 绕过统一配置 | 统一 MongoClientFactory |
| 动态索引创建失败或重复 | 中 | 中 | countDocuments 判断不可靠 | 改 listIndexes 或迁移脚本 |
| 聚合结果/性能变化 | 中 | 中 | Server planner 和行为变化 | staging 回放、explain 对比 |
| legacy mongo shell 脚本失败 | 中 | 中 | 5.0 推荐 mongosh | 外部脚本迁移和验证 |
| write concern 改变导致延迟变化 | 中 | 低到中 | 取决于拓扑和 arbiter | DBA 查 topology 和 default RW concern |
| repo 内凭据泄露 | 高 | 中 | 与升级无直接关系,但影响切换窗口安全 | 迁移到环境变量/密钥管理并轮换 |
| 模块 | 工作项 | 预估人天 |
|---|---|---|
| 后端 | UUID 编码策略确认、封装 legacy UUID 工具或准备迁移方案 | 1.5 - 3 |
| 后端 | 统一 MongoClients.create(...) 为公共 factory/config | 1 - 2 |
| 后端 | 动态建索引逻辑改造为 listIndexes 或迁移脚本 | 1 - 2 |
| 后端 | 聚合/原生 BSON 查询核心路径回归修复 | 1 - 3 |
| 配置 | 梳理所有环境 Mongo URI、补充必要连接参数 | 0.5 - 1 |
| DBA/运维 | 线上元数据检查、备份、升级 runbook、rollback runbook | 2 - 4 |
| 测试 | MongoDB 5.0 staging 环境业务回归 | 2 - 4 |
| 联调 | 应用 + DBA 联合验证、灰度观察 | 1 - 2 |
| 合计 | 视是否做 UUID 数据迁移而定 | 10 - 21 人天 |
如果选择做 legacy UUID 到 STANDARD UUID 的数据迁移,需要额外增加:
| 模块 | 工作项 | 预估人天 |
|---|---|---|
| 后端/DBA | 数据扫描脚本、迁移脚本、校验脚本、rollback 脚本 | 3 - 5 |
| 测试 | 迁移前后抽样和全量校验 | 2 - 3 |
BasicDBObject / DBObject。| 检查项 | 分数 | 说明 |
|---|---|---|
| 需求理解 | 4/4 | 明确升级目标、验收关注点和约束 |
| 竞品/行业实践 | 2/4 | 用户要求只保存上面方案,未继续扩展竞品调研;本方案采用 MongoDB 官方最佳实践作为主要依据 |
| 用户/运维体验 | 5/6 | 给出阶段化执行流程、默认 burn-in 策略和检查清单 |
| 技术完整度 | 6/6 | 覆盖依赖、配置、代码点、数据兼容、API/driver 行为 |
| 可行性 | 4/4 | 给出工作量、风险、执行顺序 |
| 附加分 | 2/2 | 对 UUID 保留 legacy 与迁移 STANDARD 给出方案对比 |
总分: 23 / 26
结论:方案可提交评审。