2团
Published on 2024-09-26 / 25 Visits
0
0

MongoDB创建分片键(shard key)的隐式操作引发的索引名冲突问题

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()。

如果索引是唯一支持分片键的非隐藏索引,则无法将其删除或隐藏。


Comment