1

我有一个文档结构,用于存储每个用户设备的应用程序相关数据。该公司有一些可用的应用程序,这些应用程序是有限的,并且不会经常更改。因此,我使用嵌入式子文档数组的方法设计了文档,以减少查找并使其适合聚合管道。

让一个文档定义:

{
    _id: "device_unique_identification_code",
    device_model: "iPhone5,2",
    applications: [
        {
            app_id: "a_game",
            push_token: "edbca9078d6c0c3a9f17166bbcf5be440c8ed2c6",
            last_user_id: 132522
        },
        {
            app_id: "an_app",
            push_token: "fed949982ceac84279f22a29bdd66cc13b7750e1",
            last_user_id: 132522
        },
        {
            app_id: "yet_another_game",
            push_token: "5cbf5a2bf0db7d6d55bd454222844d37d4f351b6",
            last_user_id: 842452
        },
        {
            app_id: "yet_another_app",
            push_token: "d1b60db7d54246d55bd37f4f35d45c2284b5a2bf",
            last_user_id: 842452
        }
    ]
}

此集合仅存储特定于设备应用程序的数据,所有与会话/用户相关的数据都保存在另一个集合中。

由于这些应用程序非常繁忙,我必须使用 atomic 命令来做任何事情来降低竞争条件的风险。

这是问题。

给定设备“a”和应用程序“b”,将一些设备-应用程序值(例如,保存 push_token)存储在单个原子命令中。

以下是测试用例。

  • 如果没有设备“a”的文档,则使用 device-app 数据创建一个。
  • 如果设备“a”的文档已经存在,但没有应用程序“b”。推送一个新的设备应用程序。
  • 如果设备“a”的文档已经存在并且应用“b”已经存在。更新现有的。

我浪费了几天时间尝试使用 upsert/addToSet/setOnInsert/etc 进行各种查询。但仍然没有线索。

PS。我还考虑了另外 2 个选项。

  • 使用分离的集合,这会起作用,但会以寻求性能为代价,我觉得它是一种 RDBMS。
  • 使用 app_id 作为子文档而不是数组的映射键,这也可以,但我可能会失去聚合管道的能力并回退到(较慢的)map-reduce。

答案描述

解决方案是使用带有版本字段的乐观锁定。

previousVersion = 0

while (true) {
    // find target document with current version number,
    // also create a new document with version 1 initially
    // also find whether there is an existing app_id
    // so we don't have to loop through the array
    doc = db.devices.findAndModify({
        query:{_id:"given_device_id"},
        update:{$setOnInsert:{version:1}},
        fields:{version:1,applications:{$elemMatch:{app_id:"given_app_id"}}},
        upsert:true,
        new:true})

    // prevent unexpected infinite loop
    if (previousVersion == doc['version']) {
        throw new InfiniteLoopExpectedException()
    }

    previousVersion = doc['version']

    if (doc contains applications) {
        // if document contains the target application
        // update it using $ positioning because I am too lazy to find the index
        result = db.devices.update(
            {
                _id:"given_device_id",
                version:doc['version'],
                "applications.app_id":"given_app_id"
            }, 
            {
                $inc:{version:1},
                $set:{"applications.$.push_token":"given_value"}
            })
    } else {
        // no app_id found ? simply push
        result = db.devices.update(
            {_id:"given_device_id",version:doc['version']},
            {
                $inc:{version:1},
                $push:{applications:{app_id:"given_app_id",push_token:"given_value"}}
            })
    }

    // if the update command failed, retry the process again
    if (result['nModified'] == 1) {
        break
    }
}
4

1 回答 1

1

您是否尝试查找和修改

如果您添加一个字段version,您可以确保更新仅针对您从查询中获得的记录版本(版本)进行。如果没有通过,您可以重新阅读文档(获取版本 n+m),增加版本(n+m+1),重新应用您的更改并重试findandmodify匹配您刚刚阅读的版本号(n+m) . 最终你会成功,当你知道在你的读和写之间没有其他线程或进程干预时。

于 2014-10-11T15:33:01.570 回答