1.前言
近期项目需要在新的环境进行全量部署,发现Spring Boot微服务无法正常启动,报MongoDB索引冲突错误(IndexOptionsConflict
),具体报错信息如下:
Caused by: com.mongodb.MongoCommandException: Command failed with error 85 (IndexOptionsConflict): 'Index with name: imei already exists with a different name' on server mongos3.xsk.top:30000. The full response is {"raw": {"shard3/192.168.39.52:27003,192.168.39.53:27003,192.168.39.54:27003": {"ok": 0.0, "errmsg": "Index with name: imei already exists with a different name", "code": 85, "codeName": "IndexOptionsConflict"}}, "code": 85, "codeName": "IndexOptionsConflict", "ok": 0.0, "errmsg": "Index with name: imei already exists with a different name", "operationTime": {"$timestamp": {"t": 1727323639, "i": 2}}, "$clusterTime": {"clusterTime": {"$timestamp": {"t": 1727323657, "i": 2}}, "signature": {"hash": {"$binary": {"base64": "YIqR+ju+L/7ljMSvAP2sw0PpvtU=", "subType": "00"}}, "keyId": 7417716151519543318}}}
检查代码,发现:
报错所在的Collection中存在
imei_1
的索引;项目中imei字段上有@Indexed(name = "imei")注解;
自动化部署脚本中有添加分片键操作,分片键字段为imei。
为何前期各存量环境不存在索引冲突问题,但是新全量部署环境会出现此问题呢?
MongoDB版本为4.4.20。
2. 原因
经过分析,发现是因为给MongoDB Collection创建分片键时,会同步隐式创建索引导致的问题。
下面将逐步复现:
2.1 先创建索引,再创建分片键
mongos> db.createCollection("test")
{
"ok" : 1,
"operationTime" : Timestamp(1727326581, 1),
"$clusterTime" : {
"clusterTime" : Timestamp(1727326581, 1),
"signature" : {
"hash" : BinData(0,"TKPBp0BqPeNy06/3EbEwVoNAYDk="),
"keyId" : NumberLong("7417716151519543318")
}
}
}
mongos> db.test.getIndexes()
[ { "v" : 2, "key" : { "_id" : 1 }, "name" : "_id_" } ]
首先进入mongos,创建测试Collection,名称为test
。查询test的索引,可见仅存在名称为_id_
(对应字段_id)的索引。
mongos> db.test.createIndex({"imei":1}, {name:"imei"})
{
"raw" : {
"shard3/192.168.39.52:27003,192.168.39.53:27003,192.168.39.54:27003" : {
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 1,
"numIndexesAfter" : 2,
"commitQuorum" : "votingMembers",
"ok" : 1
}
},
"ok" : 1,
"operationTime" : Timestamp(1727330661, 1),
"$clusterTime" : {
"clusterTime" : Timestamp(1727330661, 1),
"signature" : {
"hash" : BinData(0,"MqNnWpB1EB27YLTF3lmOTiRWjcA="),
"keyId" : NumberLong("7417716151519543318")
}
}
}
mongos> db.test.getIndexes()
[
{
"v" : 2,
"key" : {
"_id" : 1
},
"name" : "_id_"
},
{
"v" : 2,
"key" : {
"imei" : 1
},
"name" : "imei"
}
]
手动创建imei字段索引,且索引名命名为"imei",创建后查询test的索引情况,发现已正常添加名为"imei"索引。
mongos> sh.shardCollection("db_test.test", { "imei" : 1 })
{
"collectionsharded" : "db_test.test",
"collectionUUID" : UUID("fa558fed-7084-4889-9cfb-c07f30303b49"),
"ok" : 1,
"operationTime" : Timestamp(1727330940, 5),
"$clusterTime" : {
"clusterTime" : Timestamp(1727330940, 5),
"signature" : {
"hash" : BinData(0,"2wZTdBxHDGAKmAeXuhtUWdGO6DY="),
"keyId" : NumberLong("7417716151519543318")
}
}
}
mongos> db.test.getIndexes()
[
{
"v" : 2,
"key" : {
"_id" : 1
},
"name" : "_id_"
},
{
"v" : 2,
"key" : {
"imei" : 1
},
"name" : "imei"
}
]
给test创建分片键(选择字段"imei"),执行成功后发现test的索引无变化。
此时,启动Spring Boot微服务,MongoTemplate可正常完成初始化设置
2.2 先创建分片键,再创建索引
因为MongoDB 4.4.20版本不支持重新设置分片键(Shard Key
),因此可参照如下方式移除分片键:
mongos> db.test.aggregate([ { $out: "test_temp" } ]) // 将数据从分片集合复制到新的未分片集合
mongos> db.test.drop() // 删除原始分片集合
true
mongos> db.test_temp.renameCollection("test") // 重新命名新的未分片集合
{
"ok" : 1,
"operationTime" : Timestamp(1727331627, 2),
"$clusterTime" : {
"clusterTime" : Timestamp(1727331627, 2),
"signature" : {
"hash" : BinData(0,"8ZENBA9BIqz22IXYFjMcPZTSN/o="),
"keyId" : NumberLong("7417716151519543318")
}
}
}
mongos> db.test.getIndexes()
[ { "v" : 2, "key" : { "_id" : 1 }, "name" : "_id_" } ]
以上操作仅建议在开发环境使用,强烈不建议在生产环境使用。
mongos> sh.shardCollection("db_test.test", { "imei" : 1 })
{
"collectionsharded" : "db_test.test",
"collectionUUID" : UUID("6ec7c9b9-ae63-43f6-8999-954b2a015336"),
"ok" : 1,
"operationTime" : Timestamp(1727332926, 10),
"$clusterTime" : {
"clusterTime" : Timestamp(1727332926, 10),
"signature" : {
"hash" : BinData(0,"LoQdDiEuVQcMb9pBmkNA0NkrD6E="),
"keyId" : NumberLong("7417716151519543318")
}
}
}
mongos> db.test.getIndexes()
[
{
"v" : 2,
"key" : {
"_id" : 1
},
"name" : "_id_"
},
{
"v" : 2,
"key" : {
"imei" : 1
},
"name" : "imei_1"
}
]
按照如上步骤给test添加分片键,创建完成后查询索引,可发现多出了名为"imei_1"的索引。
因此可以确定,在全量新部署的环境中,创建分片键的时候会同步针对imei字段建立普通索引。因为此普通索引名称与应用中创建索引的指定名称不匹配,从而导致了应用报索引名冲突的错误,进而无法正常启动。
版本迭代过程中,是先有索引需求,后续版本添加了分片配置,因此适配2.1章节步骤,没有报错。
3. 总结
出现本文错误的原因在于,对于MongoDB分片键的认知不够清晰,官网分片键索引的具体描述如下:
所有分片集合都必须具有支持分片键的索引。索引可以是分片键上的索引,也可以是复合索引,其中分片键是索引的前缀。
如果集合为空,sh.shardCollection() 会在分片键上创建索引(如果此类索引尚不存在)。
如果集合不为空,则必须先创建索引,然后才能使用sh.shardCollection()。
如果索引是唯一支持分片键的非隐藏索引,则无法将其删除或隐藏。