Python MongoDB(PyMongo)Mutliprocessing游标

时间:2015-04-20 10:49:23

标签: python multithreading mongodb multiprocessing

我正在尝试制作一个多处理MongoDB实用程序,它完全正常工作,但我认为我遇到了性能问题......即使有20名工作人员,它每秒处理的文档也不超过2800个...我想我可以快5倍......这是我的代码,它没有做任何特殊的事情,只是将剩余的时间打印到光标的末尾。

也许有更好的方法在MongoDB游标上执行多处理,因为我需要在每个拥有17.4M记录集的doc上运行一些东西,因此性能和时间都是必须的。

START = time.time()
def remaining_time(a, b):
    if START:
        y = (time.time() - START)
        z = ((a * y) / b) - y
        d = time.strftime('%H:%M:%S', time.gmtime(z))
        e = round(b / y)
        progress("{0}/{1} | Tiempo restante {2} ({3}p/s)".format(b, a, d, e), b, a)


def progress(p, c, t):
    pc = (c * 100) / t
    sys.stdout.write("%s [%-20s] %d%%\r" % (p, '█' * (pc / 5), pc))
    sys.stdout.flush()

def dowork(queue):
    for p, i, pcount in iter(queue.get, 'STOP'):
        remaining_time(pcount, i)


def populate_jobs(queue):
    mongo_query = {}
    products = MONGO.mydb.items.find(mongo_query, no_cursor_timeout=True)
    if products:
        pcount = products.count()
        i = 1
        print "Procesando %s productos..." % pcount
        for p in products:
            try:
                queue.put((p, i, pcount))
                i += 1
            except Exception, e:
                utils.log(e)
                continue
    queue.put('STOP')


def main():
    queue = multiprocessing.Queue()

    procs = [multiprocessing.Process(target=dowork, args=(queue,)) for _ in range(CONFIG_POOL_SIZE)]

    for p in procs:
        p.start()

    populate_jobs(queue)

    for p in procs:
        p.join()

另外,我注意到大约每2500个aprox文件,脚本暂停约0.5-1秒,这显然是一个不好的问题。这是MongoDB问题,因为如果我执行完全相同的循环但使用range(0, 1000000)脚本根本不会暂停并以每秒57,000次迭代运行,总共20秒来结束脚本。 。与每秒2,800个MongoDB文档的巨大差异...

这是运行1,000,000次迭代循环而不是docs的代码。

def populate_jobs(queue):
    mongo_query = {}
    products = MONGO.mydb.items.find(mongo_query, no_cursor_timeout=True)
    if products:
        pcount = 1000000
        i = 1
        print "Procesando %s productos..." % pcount
        for p in range(0, 1000000):
            queue.put((p, i, pcount))
            i += 1
    queue.put('STOP')

更新 正如我所看到的,问题不在于多处理本身,光标是否填充了未在多处理模式下运行的Queue,这是一个填充QueuepopulateJobs方法的简单过程)也许如果我可以使光标多线程/多镜像并且并行填充Queue它将更快填充,那么多处理方法dowork会更快,因为我认为有一个瓶颈我只在Queue中每秒填写大约2,800项,并在dowork多进程中检索更多内容,但我不知道如何并行化MongoDB光标。

也许,问题是我的计算机和服务器的MongoDB之间的延迟。在我要求下一个游标和MongoDB告诉我哪个游标之间的延迟使我的性能降低了2000%(从61,000 str / s到2,800 doc / s) NOPE 我已尝试过本地主机MongoDB并且性能完全一样......这让我感到疯狂

2 个答案:

答案 0 :(得分:3)

以下是如何使用Pool为孩子们提供的服务:

START = time.time()
def remaining_time(a, b):
    if START:
        y = (time.time() - START)
        z = ((a * y) / b) - y
        d = time.strftime('%H:%M:%S', time.gmtime(z))
        e = round(b / y)
        progress("{0}/{1} | Tiempo restante {2} ({3}p/s)".format(b, a, d, e), b, a)


def progress(p, c, t):
    pc = (c * 100) / t
    sys.stdout.write("%s [%-20s] %d%%\r" % (p, '█' * (pc / 5), pc))
    sys.stdout.flush()

def dowork(args):
    p, i, pcount  = args
    remaining_time(pcount, i)

def main():
    queue = multiprocessing.Queue()

    procs = [multiprocessing.Process(target=dowork, args=(queue,)) for _ in range(CONFIG_POOL_SIZE)]
    pool = multiprocessing.Pool(CONFIG_POOL_SIZE)
    mongo_query = {}
    products = MONGO.mydb.items.find(mongo_query, no_cursor_timeout=True)
    pcount = products.count()
    pool.map(dowork, ((p, idx, pcount) for idx,p in enumerate(products)))
    pool.close()
    pool.join()

请注意,使用pool.map需要立即将光标中的所有内容加载到内存中,这可能是一个问题,因为它有多大。您可以使用imap来避免一次性使用整个内容,但是您需要指定chunksize来最小化IPC开销:

# Calculate chunksize using same algorithm used internally by pool.map
chunksize, extra = divmod(pcount, CONFIG_POOL_SIZE * 4)
if extra:
   chunksize += 1

pool.imap(dowork, ((p, idx, pcount) for idx,p in enumerate(products)), chunksize=chunksize)
pool.close()
pool.join()

对于1,000,000件物品,它提供了12,500个大块。您可以尝试比这更大和更小的尺寸,并查看它如何影响性能。

我不确定这会有多大帮助,如果瓶颈实际上只是将数据从MongoDB中拉出来。

答案 1 :(得分:1)

为什么使用多处理?您似乎没有使用队列在其他线程中进行实际工作。 Python有一个global interpreter lock,这使得多线程代码的性能低于您的预期。它可能使这个程序变慢,而不是更快。

一些表现提示:

  1. 尝试将find()调用中的batch_size设置为某个大号(例如20000)。这是在客户端获取更多内容之前一次返回的最大文档数,默认值为101.

  2. 尝试将cursor_type设置为pymongo.cursor.CursorType.EXHAUST,这可能会减少您所看到的延迟。