【问题标题】:Duplicate Key Error on Upsert with multi processes(Mongo>=3.0.4 WiredTiger)多进程的 Upsert 重复键错误(Mongo>=3.0.4 WiredTiger)
【发布时间】:2020-05-15 18:33:42
【问题描述】:

全部

我刚刚从我们的应用程序中收到一个奇怪的错误:

当我用两个进程更新时,它抱怨一个带有唯一索引的集合出现重复键错误,但有问题的操作是一个 upsert。

案例代码:

import time
from bson import Binary
from pymongo import MongoClient, DESCENDING

bucket = MongoClient('127.0.0.1', 27017)['test']['foo']
bucket.drop()
bucket.update({'timestamp': 0}, {'$addToSet': {'_exists_caps': 'cap15'}}, upsert=True, safe=True, w=1, wtimeout=10)
bucket.create_index([('timestamp', DESCENDING)], unique=True)
while True:
    timestamp =  str(int(1000000 * time.time()))
    bucket.update({'timestamp': timestamp}, {'$addToSet': {'_exists_foos': 'fooxxxxx'}}, upsert=True, safe=True, w=1, wtimeout=10)

当我使用两个进程运行脚本时,Pymongo 异常:

Traceback (most recent call last):
  File "test_mongo_update.py", line 11, in <module>
    bucket.update({'timestamp': timestamp}, {'$addToSet': {'_exists_foos': 'fooxxxxx'}}, upsert=True, safe=True, w=1, wtimeout=10)
  File "build/bdist.linux-x86_64/egg/pymongo/collection.py", line 552, in update
  File "build/bdist.linux-x86_64/egg/pymongo/helpers.py", line 202, in _check_write_command_response
pymongo.errors.DuplicateKeyError: E11000 duplicate key error collection: test.foo index: timestamp_-1 dup key: { : "1439374020348044" }

环境:

  • mongodb 3.0.5,WiredTiger

  • 单个 mongodb 实例

  • pymongo 2.8.1

mongo.conf

systemLog:
   destination: file
   logAppend: true
   logRotate: reopen
   path: /opt/lib/log/mongod.log

# Where and how to store data.
storage:
   dbPath: /opt/lib/mongo
   journal:
     enabled: true

   engine: "wiredTiger"
   directoryPerDB: true

# how the process runs
processManagement:
   fork: true  # fork and run in background
   pidFilePath: /opt/lib/mongo/mongod.pid

# network interfaces
net:
   port: 27017
   bindIp: 0.0.0.0  # Listen to local interface only, comment to listen on all interfaces.

setParameter:
   enableLocalhostAuthBypass: false

对这里可能出了什么问题有什么想法吗?

PS:

我在 MMAPV1 存储引擎中重试了同样的情况,它工作正常,为什么?

我在这里找到了一些相关的东西: https://jira.mongodb.org/browse/SERVER-18213

但是在这个bug修复之后,它会出现这个错误,所以看起来这个bug没有完全修复。

干杯

【问题讨论】:

  • 您已在“时间戳”上创建了唯一索引。我猜你的代码试图插入具有重复时间戳值的文档?
  • 是的,我同时用两个进程执行脚本,但是为什么wiredtiger每次更新都不锁定(upsert=True)?如果它在 mmapv1 上运行,一切都很好。

标签: mongodb


【解决方案1】:

我在以下位置发现了错误: https://jira.mongodb.org/browse/SERVER-14322

请随时为它投票并观看它以获取更多更新。

【讨论】:

    【解决方案2】:

    upsert 会检查现有文档以进行更新,或插入新文档。

    我最好的猜测是您遇到了时间问题:

    1. 进程 2 检查是否存在,但它不检查
    2. 进程 1 检查是否存在,但它不检查
    3. 处理 2 个插入,有效
    4. 进程 1 插入,这会引发重复密钥。

    首先检查您的 python 库在下面发送的本机查询。确认这是您在本地 mongo 方面所期望的。然后,如果您可以在wiredtiger 上定期重现此半,但永远不会在mmap 上重现,请使用mongo 提出一个错误以确认他们的预期行为是什么。有时很难选择他们保证是原子的。

    这是一个很好的例子,说明为什么Mongo ObjectIDs 将时间戳、机器 id、pid 和计数器组合在一起以实现唯一性。

    【讨论】:

    • 谢谢马特。这使得我们两个。我认为 update(upsert=true) 将是原子的,所以我认为这是一个错误。也许我需要为 mongo 买票。
    【解决方案3】:

    http://docs.mongodb.org/manual/core/storage/

    使用 WiredTiger,所有写操作都发生在文档级锁的上下文中。因此,多个客户端可以同时修改单个集合中的多个文档。

    您的多个客户端可以同时更新集合。 Wiredtiger 将锁定您正在更新的文档,而不是集合。

    【讨论】:

    • 那么为什么当我使用 upsert 更新时,wiredtiger 会引发 DuplicateKeyError?
    • 因为集合中已有另一个条目与您尝试添加的时间戳值相同。
    • 这就是 upsert 处理、更新现有或插入的意思
    • 嗨,robjwilkins 和马特,感谢您的申请。我执行更新(upsert = True .....),我认为当我同时修改文档时mongo会锁定,所以它会更新两次,不会引发重复键错误。哪里不对了?而且我在 MMAPv1 存储引擎中测试过,效果很好。
    【解决方案4】:

    我使用 MongoDB 来管理我的存储系统的目录树数据。我遇到了同样的问题:upsert=True 的更新命令有时会创建重复的文档。

    但我使用的是 MMAPv1,我不知道使用 WiredTiger 引擎时会发生什么。我认为这是 MongoDB 的竞争条件问题。最后,我决定用python-redis-lock 编写一个锁定机制(文档锁定级别)。它运行良好!

    【讨论】:

      猜你喜欢
      • 2013-08-09
      • 2016-09-14
      • 2018-03-04
      • 1970-01-01
      • 1970-01-01
      • 2013-06-11
      • 2012-01-10
      • 2018-12-30
      相关资源
      最近更新 更多