Upserting to sharded MongoDB using mgo Error "full shard key must be in update object for collection:…"

huangapple go评论80阅读模式
英文:

Upserting to sharded MongoDB using mgo Error "full shard key must be in update object for collection:..."

问题

使用Labix mgo API来操作MongoDB,我正在尝试在分片集合上执行递增操作。我可以在非分片集合上使用通常的mgo.Change结构来实现这一点,但是当我尝试在分片集合上执行时,我会收到错误消息:full shard key must be in update object for collection: db_name.collection_name

在非分片集合上正常工作的原始代码如下:

change := mgo.Change{
    ReturnNew: true,
    Upsert: true,
    Update: bson.M{
        "$setOnInsert": bson.M{
            "ci": r.Ci,
            "dt": r.Dt,
            "zi": r.Zi,
        },
        "$inc": &data,
    },
}

_, err := collection.Upsert(bson.M{"_id": id, "ci": r.Ci, "dt": r.Dt, "zi": r.Zi}, change)
if err != nil {
    log.Println("FAILURE", err)
}

然而,当我切换到以{ci: 1, dt: 1, zi: 1}为键进行分片的集合时,我会收到上述错误。

为了调试,我尝试了解mgo的内部工作原理,并尝试直接在Mongo终端中进行插入操作。

db.collection.update({ "_id" : "98364_2013-12-11", "ci" : "16326", "dt" : "2013-12-11", "zi" : "98364"}, {$setOnInsert: { "ci" : "16326", "dt" : "2013-12-11", "zi" : "98364"} , $inc: {test :1}}, { upsert: true });

然而,这给我带来了另一个错误:Can't modify shard key's value. field: ci: "16326" collection: db.collection。我认为这是我在解决初始错误之前必须解决的问题,但是对于$setOnInsert命令来说,它在不修改值的情况下设置初始插入值,所以它抛出这个错误对我来说似乎有些奇怪。当我去掉命令中的$setOnInsert部分时,所有错误都消失了,但是我需要一种确保这些值被设置的方法,因为它们在我编写的查询中将非常重要。

回到我的主要问题:我发现当我在与MongoDB终端交互时重新排列更新和插入文档的顺序时,我得到了与通过mgo时相同的错误。因此,我尝试通过切换到bson.D来严格控制传递给mgo.Change结构的文档顺序:

change := bson.D{
    {
        "Update",
        bson.D{
            {"$setOnInsert", bson.D{
                {"_id", id},
                {"ci", r.Ci},
                {"dt", r.Dt},
                {"zi", r.Zi},
            }},
            {"$inc", &data},
        },
    },
    {
        "Upsert",
        true,
    },
}
log.Println(change)
err := collection.Update(bson.D{{"_id", id}, {"ci", r.Ci}, {"dt", r.Dt}, {"zi", r.Zi}}, change)
if err != nil {
    log.Println("FAILURE", err)
}

此时,打印change对象的结果为:[{Update [{$setOnInsert [{_id 11635_2013-12-11} {ci 3599} {dt 2013-12-11} {zi 11635}]} {$inc 0xc21dd9d8d0}]} {Upsert true}],我认为这正是我应该按照Mongo的文档中的顺序传递给change对象的内容,但是我仍然收到相同的full shard key must be in update object for collection: db.collection错误。

我意识到使用collection.Find({_id: ... }).Apply(change, ...)是一个可能的替代方法,并且在我使用它时它可以正常工作,但是在对非分片集合进行测试时,使用Upsert(或Update)函数的性能要高得多(大约快20倍),而速度绝对是我的优先考虑因素,因为我每秒处理数万个事件。

我已经尝试了我能想到的所有方法,现在感觉到了无法解决的地步,希望能得到帮助。非常感谢!

英文:

Using the Labix mgo API for MongoDB, I am trying to perform an increment operation on a sharded collection. I am able to do this just fine on an unsharded collection using the usual mgo.Change struct, but when I try to do this on a sharded collection, I get the error: full shard key must be in update object for collection: db_name.collection_name

The original code that works on an un-sharded collection looks like this:

            change := mgo.Change{
                ReturnNew: true,
                Upsert: true,
                Update: bson.M{
                    "$setOnInsert": bson.M{
                        "ci": r.Ci,
                        "dt": r.Dt,
                        "zi": r.Zi,
                    },
                    "$inc": &data,
                },
            }

            _, err := collection.Upsert(bson.M{"_id": id, "ci": r.Ci, "dt": r.Dt, "zi": r.Zi}, change); if err != nil {
                log.Println("FAILURE", err)
            }

However, when I switch to a sharded collection, sharded on the key {ci: 1, dt: 1, zi: 1} I get the error mentioned above.

In an attempt to debug, I've tried to figure out what is going on behind the scenes with mgo, and tried to do an insert directly into the mongo terminal.

db.collection.update({ "_id" : "98364_2013-12-11", "ci" : "16326", "dt" : "2013-12-11", "zi" : "98364"}, {$setOnInsert: { "ci" : "16326", "dt" : "2013-12-11", "zi" : "98364"} , $inc: {test :1}}, { upsert: true });

However, this got me a separate error: Can't modify shard key's value. field: ci: "16326" collection: db.collection This is something I think I'll have to figure out once I figure out my inital error, but it seems strange to me that its throwing this error with the $setOnInsert command since its not supposed to modify the value, just set it on the initial insert. All errors go away when I cut out the $setOnInsert portion of the command, but I need a way to make sure these values get set because they will be important in the queries I write to get the data back out.

Back to my main problem: I did find that when I rearranged the order of the update and upsert documents when interacting with the MongoDB terminal, I got the error I'm getting when I go through mgo, so I tried to very strictly control the order of the documents being passed in the mgo.Change struct by switching to bson.D:

            change := bson.D{
                {
                     "Update",
                     bson.D{
                         {"$setOnInsert", bson.D{
                                {"_id", id},
                                {"ci", r.Ci},
                                {"dt", r.Dt},
                                {"zi", r.Zi},
                                },
                         },
                         {"$inc", &data},
                     },
                },
                {
                    "Upsert",
                    true,
                },
            }
            log.Println(change)
            err := collection.Update(bson.D{{"_id", id},{ "ci", r.Ci},{ "dt", r.Dt}, {"zi", r.Zi}},change); if err != nil {
                log.Println("FAILURE", err)
            }

At this point, printing the change object yields: [{Update [{$setOnInsert [{_id 11635_2013-12-11} {ci 3599} {dt 2013-12-11} {zi 11635}]} {$inc 0xc21dd9d8d0}]} {Upsert true}] which I believe to be exactly what I should be passing in as the change object in exactly the right order according to Mongo's documentation, but I still get the same full shard key must be in update object for collection: db.collection error.

I realize that using collection.Find({_id: ... }).Apply(change, ...) is a possible alternative and it works properly when I've used it, but in my testing on unsharded collections, I've seen way higher performance (as in ~20x faster) using the Upsert (or Update) functions and speed is absolutely a priority since I'm dealing with tens of thousands of events per second.

I'm getting to the point where I feel like I've tried every think I can think of and would appreciate a fresh set of eyes trying to help me figure out what is happening, so any help would be appreciated.

答案1

得分: 3

mgo.Change 类型是特定于 Query.Apply 方法的,该方法运行 MongoDB 的 findAndModify 命令并一次执行任何支持的修改。另一方面,Upsert 方法接受一个修改文档,该文档将直接提供给 mgo/bson 进行编组。这些修改文档的 格式 是相同的,无论您是通过 Query.Apply(在 mgo.ChangeUpdate 字段中)提供它们,还是通过 Collection.UpsertCollection.Update 方法提供它们。

因此,观察到的错误是由于它试图将 mgo.Change 作为一个普通的结构体用于插入(换句话说,一个具有 "returnnew" 等键的文档),这绝对不是您想要的。例如,您提供的 shell 命令等同于使用 mgo 的直接转换:

type M map[string]interface{}
err := collection.Upsert(
    M{
        "id": "98364_2013-12-11",
        "ci":  "16326",
        "dt":  "2013-12-11",
        "zi":  "98364",
    },
    M{
        "$setOnInsert": M{"ci": "16326", "dt": "2013-12-11", "zi": "98364"},
        "$inc":         M{"test": 1},
    },
)

然而,这仍然是错误的,但原因不同。正如服务器在错误消息中提到的,这是在尝试第二次设置分片键。请注意,upsert 操作将使用查询文档中提供的字段,并将查询文档和修改文档合并以创建最终的插入文档。这意味着 $setOnInsert 文档中的分片键字段与查询文档中的分片键字段是多余的。

我将改进该领域的文档,以减少人们对使用 mgo.Change 的困惑。对此给您带来的麻烦我表示抱歉。

英文:

The mgo.Change type is specific to the Query.Apply method, which runs the MongoDB findAndModify command and does any of the supported modifications at once. The Upsert method, on the other hand, takes a modification document that will be directly provided to mgo/bson for marshalling. These modification documents have the same format whether you provide them via Query.Apply (in the Update field of mgo.Change), or via the Collection.Upsert or Collection.Update methods.

So, the observed error is being caused because it's attempting to use mgo.Change as a plain struct for inserting (in other words, a document with keys "returnnew", etc), which is definitely not what you want. The shell command you provided, for example, is equivalent to the straightforward translation with mgo:

type M map[string]interface{}
err := collection.Upsert(
    M{
        "_id": "98364_2013-12-11",
        "ci":  "16326",
        "dt":  "2013-12-11",
        "zi":  "98364",
    },
    M{
        "$setOnInsert": M{"ci": "16326", "dt": "2013-12-11", "zi": "98364"},
        "$inc":         M{"test": 1},
    },
)

This is still broken, though, but for a different reason. As the server mentioned in the error message, this is attempting to set the shard key a second time. Note that an upsert operation will make use of the fields provided in the query document, and merge both the query document and the modification document to create the final document for insertion. This means that the shard key fields in the $setOnInsert document are redundant with the shard key fields in the query document.

I'll improve the documentation on that area to reduce the chances of people getting confused by the use of mgo.Change. Sorry for the trouble.

huangapple
  • 本文由 发表于 2014年1月22日 03:09:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/21266978.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定