pymongo find_one_and_update对数百万次插入/更新的性能

时间:2019-02-04 15:43:50

标签: python-3.x mongodb-query pymongo

  

更新:如果您阅读此书以提高插入/更新速度,请首先通过从python控制台运行pymongo.has_c()来检查系统上是否启用了pymongo C扩展。如果解析为False,则需要使用C扩展名编译pymongo或执行pip install --upgrade pymongo

     

它将我的工作流程从10K行上的17秒提高到了大约0.57秒。

我有成千上万个txt文件,其中包含数百万行要尝试导入mongodb集合的数据。

我当前正在使用以下 def

import re, pymongo
coll = pymongo.MongoClient().database.collection
rx = re.compile(r'[:; ]')
rx_email = re.compile(r'\S+@\S+\.\S+$')

def parser(path):
    with open(path, "rb") as f:
        for line in f:
            try:
                fields = rx.split(line.decode('utf-8'))
                email = ''
                username = ''
                for field in fields:
                    if rx_email.match(field):
                        email = field
                    elif field != fields[-1]:
                        username = field
                password = fields[-1]
                if email:
                    coll.find_one_and_update({'email': email}, {'$addToSet': {'passwords': password}}, upsert=True)
                elif username:
                    coll.find_one_and_update({'username': username}, {'$addToSet': {'passwords': password}}, upsert=True)
                else:
                    pass
            except UnicodeDecodeError:
                pass

if __name__ == "__main__":
    parser('path/to/file.txt')

当我尝试在具有10K行的文件上运行脚本时,花了74.58974479999999秒。我认为这是由于插入时MongoDB必须匹配的项目数量导致的? 在没有数据库交互的情况下运行相同的循环花费了0.022998秒。

EDIT :如Fast or Bulk Upsert in pymongo中所建议,我还尝试将UpdateOnebulk_write一起使用,如下所示:

def parser(path):
    ops = []
    with open(path, "rb") as f:
        for line in f:
            if (len(ops) == 1000):
                LOCAL_DB.bulk_write(ops, ordered=False)
                ops = []
            try:
                fields = rx.split(line.decode('utf-8'))
                email = ''
                username = ''
                for field in fields:
                    if rx_email.match(field):
                        email = field
                    elif field != fields[-1]:
                        username = field
                password = fields[-1]
                if email:
                    pass
                    ops.append((UpdateOne({'identifier': email}, {'$addToSet': {'passwords': password}}, upsert=True)))
                elif username:
                    pass
                    ops.append((UpdateOne({'identifier': username}, {'$addToSet': {'passwords': password}}, upsert=True)))
                else:
                    pass
            except UnicodeDecodeError:
                pass

完成1万行的时间为17秒,但是这会减慢我尝试更新的文件和行的数量。

有没有更好(并且希望更快)的方法?

一些要求:

  1. 电子邮件和/或用户名应该唯一。
  2. 包含密码的数组仅应列出每个密码一次(也是唯一的)。
  3. 如果可能,一百万行的插入时间应少于1分钟。

1 个答案:

答案 0 :(得分:1)

在@JohnnyHK的评论指导下,我似乎可以通过对初始代码执行以下操作来使我的upsert初始时间从10秒的约74秒缩短至〜0.5秒:

import re, pymongo
rx = re.compile(r'[:; ]')
rx_email = re.compile(r'\S+@\S+\.\S+$')

def parse(path):
    ops = []
    with open(path, "rb") as f:
        for line in f:
            if (len(ops) == 1000):
                pymongo.MongoClient().database.collection.bulk_write(ops, ordered=False)
                ops = []
            try:
                fields = rx.split(line.decode('utf-8'))
                email = ''
                username = ''
                for field in fields:
                    if rx_email.match(field):
                        email = field
                    elif field != fields[-1]:
                        username = field
                password = fields[-1]
                if email:
                    pass
                    ops.append((pymongo.UpdateOne({'_id': email}, {'$addToSet': {'passwords': password}}, upsert=True)))
                elif username:
                    pass
                    ops.append((pymongo.UpdateOne({'_id': username}, {'$addToSet': {'passwords': password}}, upsert=True)))
                else:
                    pass # logic removed
            except UnicodeDecodeError:
                pass # logic removed

if __name__ == "__main__":
    parse(path/to/file.txt)

我发现系统上缺少pymongo C extensions

>>> import pymongo
>>> pymongo.has_c()
>>> False

从那里我做了pip install --upgrade pymongo(对我来说很幸运) 然后解析为True

对于唯一字段,我还使用_id代替了identifier,从而进一步提高了速度。

希望这可以帮助人们前进。我将根据自己的经验更新更多的发现。