Python MongoDB(PyMongo)多处理游标
我正在尝试制作一个多处理MongoDB实用程序,它可以完美地工作,但我认为我有一个性能问题。。。即使有20名员工,它每秒处理的文档也不超过2800个。。。我想我可以快5倍。。。这是我的代码,它没有做任何异常,只是将剩余时间打印到光标的末尾 也许有更好的方法在MongoDB游标上执行多处理,因为我需要在每个具有1740万条记录集合的文档上运行一些东西,所以性能和更少的时间是必须的Python MongoDB(PyMongo)多处理游标,python,multithreading,mongodb,multiprocessing,Python,Multithreading,Mongodb,Multiprocessing,我正在尝试制作一个多处理MongoDB实用程序,它可以完美地工作,但我认为我有一个性能问题。。。即使有20名员工,它每秒处理的文档也不超过2800个。。。我想我可以快5倍。。。这是我的代码,它没有做任何异常,只是将剩余时间打印到光标的末尾 也许有更好的方法在MongoDB游标上执行多处理,因为我需要在每个具有1740万条记录集合的文档上运行一些东西,所以性能和更少的时间是必须的 START = time.time() def remaining_time(a, b): if START:
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文档,脚本就会暂停大约.5-1秒,这显然是一个糟糕的问题。这是一个MongoDB问题,因为如果我执行完全相同的循环,但使用范围(0,1000000)
脚本根本不会暂停,并以每秒57000次迭代的速度运行,总共需要20秒来结束脚本。。。与每秒2800个MongoDB文档有巨大差异
这是运行1000000迭代循环的代码
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')
更新
正如我所看到的,问题不在于多处理本身,而是光标填充了未在多处理模式下运行的队列
,这是一个填充队列
的简单过程(populateJobs
方法)也许如果我可以使游标多线程/多进程并并行填充队列
,它的填充速度会更快,那么多处理方法dowork
会更快,因为我认为有一个瓶颈,我每秒只在队列中填充2800个项目,在dowork
多进程中检索更多项目,但我不知道如何并行化MongoDB
游标
也许,问题在于我的计算机和服务器的MongoDB之间的延迟。在我请求下一个游标和MongoDB告诉我下一个游标之间的延迟会将我的性能降低2000%(从61000 str/s降低到2800 doc/s)
没有我在本地主机MongoDB上尝试过,性能完全相同。。。这让我抓狂为什么要使用多处理?您似乎没有在使用队列的其他线程中执行实际工作。Python有一个新的特性,它使多线程代码的性能低于您预期的性能。这可能会让这个程序变慢,而不是变快
以下是一些表演技巧:
尝试在find()调用中将批大小设置为某个大数字(例如20000)。这是在客户端获取更多文档之前一次返回的最大文档数,默认值为101
尝试将cursor\u type
设置为,这可能会减少您看到的延迟
以下是如何使用池
喂养孩子:
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()
对于1000000个项目,chunksize为12500。您可以尝试更大和更小的尺寸,看看它如何影响性能
如果瓶颈实际上只是从MongoDB中提取数据,我不确定这会有多大帮助。“我想我可以快5倍。”你为什么这么认为?因为我可以每秒向MongoDB执行8000次插入,这比简单的查找需要更多的努力,而且它每秒要执行2800个文档,所以我想我可以在find上获得更高的性能。您通过队列发送了多少个作业?如果是一个较大的数字,如果使用多处理.Pool
,可以在将作业发送到子进程之前对其进行批处理(假设您使用Pool.map
将工作发送到子进程),则可以提高性能。如果要避免将整个任务列表存储在内存中,还可以将Pool.imap
与相当大的chunksize
一起使用。该数字是可变的,但肯定会超过1000000,查看我的上一次更新,因为速度问题在某种程度上point@dano我刚刚使用了一个multiprocesing.Pool
和async
,你能举一个例子来引用我问题中的代码吗?我对如何用这种方式给childs喂食有点迷茫..正如我所说,我在每个进程上都做了一些工作,但只是为了测试迭代的速度,我只是打印了一些统计数据,但真正的代码做了真正的工作。我试试你的建议。只是为了更新,我不是在做多线程,而是在做多处理,因为如果我want@noaGIL不适用于——如果我没有弄错你的说法,那就是你在尝试你的技巧后所暗示的,表现是一样的。我不明白为什么一个简单的范围循环是~61000个项目/秒,而游标是~2800个项目/秒……如果不迭代结果,您只从数据库加载第一批记录。显然,实际加载所有数据需要更长的时间。也就是说,为了得到一个更清晰的基准和更简单的测试用例,删除多处理的东西可能是值得的。使用你的方法,我可以获得每秒60000的速度,非常好。事实上,如果这个数字正确与否,我将进行更多的测试,但是有一个问题,使用你的方法,例如,我怎么能,在dowork
内部更新一个数组,然后当i%n==0
拉数组时(实际上我要在MongoDB上进行大容量插入),因为如果我使用多处理.Pool没有错,就不会调用n个进程,因此,dowork
方法没有迭代任何东西,因此我可以推/拉到外部数组(迭代本身的外部)我希望我已经正确地解释了它,而且,这样,进程条信息不会显示有序的结果,它可以说392093/1000000=39%
和