Python 使用芹菜处理巨大的文本文件 背景

Python 使用芹菜处理巨大的文本文件 背景,python,performance,rabbitmq,celery,hpc,Python,Performance,Rabbitmq,Celery,Hpc,我正在研究使用芹菜(3.1.8)来处理巨大的文本文件(约30GB)。这些文件的格式为,包含约118M个测序“读取”,基本上每个都是头、DNA序列和质量字符串的组合。此外,这些序列来自成对的末端序列运行,因此我同时迭代两个文件(通过itertools.izip)。我希望能够做的是获取每一对读取,将它们发送到队列,并在集群中的一台机器上处理它们(不管是哪台机器),以便在需要进行清理(例如,基于质量)的情况下返回已清理的读取版本 我已经建立了芹菜和rabbitmq,我的员工如下: celery wor

我正在研究使用芹菜(3.1.8)来处理巨大的文本文件(约30GB)。这些文件的格式为,包含约118M个测序“读取”,基本上每个都是头、DNA序列和质量字符串的组合。此外,这些序列来自成对的末端序列运行,因此我同时迭代两个文件(通过itertools.izip)。我希望能够做的是获取每一对读取,将它们发送到队列,并在集群中的一台机器上处理它们(不管是哪台机器),以便在需要进行清理(例如,基于质量)的情况下返回已清理的读取版本

我已经建立了芹菜和rabbitmq,我的员工如下:

celery worker -A tasks --autoreload -Q transient 
配置如下:

from kombu import Queue

BROKER_URL = 'amqp://guest@godel97'
CELERY_RESULT_BACKEND = 'rpc'
CELERY_TASK_SERIALIZER = 'pickle'
CELERY_RESULT_SERIALIZER = 'pickle'
CELERY_ACCEPT_CONTENT=['pickle', 'json']
CELERY_TIMEZONE = 'America/New York'
CELERY_ENABLE_UTC = True
CELERYD_PREFETCH_MULTIPLIER = 500

CELERY_QUEUES = (
    Queue('celery', routing_key='celery'),
    Queue('transient', routing_key='transient',delivery_mode=1),
)
我选择了使用rpc后端和pickle序列化来提高性能,也选择了不使用 将任何内容写入“暂时”队列中的磁盘(通过传递模式)

芹菜创业 为了设置芹菜框架,我首先在64路机箱上启动rabbitmq服务器(3.2.3,Erlang R16B03-1),将日志文件写入fast/tmp磁盘。工作进程(如上所述)在集群上的每个节点上启动(大约34个),范围从8路到64路SMP,总共688个核心。因此,我有大量可用的CPU供工作人员用于处理队列

工作提交/绩效 芹菜启动并运行后,我通过ipython笔记本提交作业,如下所示:

files = [foo, bar]
f1 = open(files[0])
f2 = open(files[1])
res = []
count = 0
for r1, r2 in izip(FastqGeneralIterator(f1), FastqGeneralIterator(f2)):
    count += 1
    res.append(tasks.process_read_pair.s(r1, r2))
        if count == 10000:
        break
t.stop()
g = group(res)
for task in g.tasks:
    task.set(queue="transient")
result = g.delay()
res.append(tasks.speed.apply_async(args=("FML",), queue="transient"))
这大约需要1.5秒来读取10000对数据。然后,我给delay打电话,要求小组向工人们提交,大约需要20秒,如下所示:

files = [foo, bar]
f1 = open(files[0])
f2 = open(files[1])
res = []
count = 0
for r1, r2 in izip(FastqGeneralIterator(f1), FastqGeneralIterator(f2)):
    count += 1
    res.append(tasks.process_read_pair.s(r1, r2))
        if count == 10000:
        break
t.stop()
g = group(res)
for task in g.tasks:
    task.set(queue="transient")
result = g.delay()
res.append(tasks.speed.apply_async(args=("FML",), queue="transient"))
通过rabbitmq控制台进行监控,我发现我做得还不错,但速度还不够快

问题: 那么,有没有办法加快速度?我的意思是,我希望看到每秒处理至少50000个读对,而不是500个。我的芹菜配置中有什么明显的缺失吗?我的工人和兔子日志基本上是空的。我想知道如何提高我的表现。每个读对的处理速度也相当快:

[2014-01-29 13:13:06,352: INFO/Worker-1] tasks.process_read_pair[95ec7f2f-0143-455a-a23b-c032998951b8]: HWI-ST425:143:C04A5ACXX:3:1101:13938:2894 1:N:0:ACAGTG HWI-ST425:143:C04A5ACXX:3:1101:13938:2894 2:N:0:ACAGTG 0.00840497016907 sec
到目前为止 到目前为止,我已经用谷歌搜索了芹菜、性能、路由、rabbitmq等我能想到的一切。我已经浏览了芹菜网站和文档。如果我不能获得更高的性能,我将不得不放弃这种方法,转而采用另一种解决方案(基本上是将工作划分为许多较小的物理文件,并直接在每个计算节点上使用多处理或其他方法处理它们)。但是,如果不能将此负载分散到集群上,那将是一种耻辱。此外,这似乎是一个非常优雅的解决方案


提前感谢您的帮助

您有一个解决方案,即读取是高度可压缩的,因此替换以下内容

res.append(tasks.process_read_pair.s(r1, r2))

在另一端调用pickle.load(zlib.decompress(obj))

如果DNA序列不够长的话,它应该能在足够长的DNA序列中赢得一个围绕大因子的因子。你可以在一个数组中按块对它们进行分组,然后进行转储和压缩

如果您还没有使用zeroMQ进行传输,那么另一个胜利就是使用zeroMQ进行传输


我不确定什么是进程字节,不是答案,但是太长了,不适合评论

让我们把问题缩小一点

首先,尝试跳过所有常规的逻辑/消息准备,只需对当前库进行尽可能紧密的发布循环。看看你能拿到多少钱。这将确定您的非队列相关代码是否有问题

如果仍然很慢,请设置一个新的python脚本,但使用它而不是芹菜。我在中端桌面上进行有用的工作(和json编码)时,成功地以超过6000/s的速度发布了它,所以我知道它的性能很好。这将确定问题是否与芹菜库有关。(为了节省您的时间,我从我的一个项目中截取了以下内容,希望在简化时不要破坏它…)


在上述两种方法之间,您应该能够将问题追溯到队列、库或代码中的一种。

同样,不是答案,但注释太长。根据下面的评论/回答,我使用与我的应用程序相同的交换和路由设置了以下测试:

from amqplib import client_0_8 as amqp
try:
    lConnection = amqp.Connection()
    lChannel = lConnection.channel()
    Exchange = 'celery'

    for i in xrange(1000000):
        lMessage = amqp.Message("~130 bytes of test data..........................................................................................................")
        lMessage.properties["delivery_mode"] = 1
        lChannel.basic_publish(lMessage, exchange=Exchange, routing_key='transient')

    lChannel.close()
    lConnection.close()

except Exception as e:
    print e
你可以看到它一直在摇摆


我想现在该找出这与内部发生的事情之间的区别了。

我将amqp添加到我的逻辑中,而且速度很快。FML

from amqplib import client_0_8 as amqp
try:
    import stopwatch
    lConnection = amqp.Connection()
    lChannel = lConnection.channel()
    Exchange = 'celery'

    t = stopwatch.Timer()
    files = [foo, bar]
    f1 = open(files[0])
    f2 = open(files[1])
    res = []
    count = 0
    for r1, r2 in izip(FastqGeneralIterator(f1), FastqGeneralIterator(f2)):
        count += 1
        #res.append(tasks.process_read_pair.s(args=(r1, r2)))
        #lMessage = amqp.Message("~130 bytes of test data..........................................................................................................")
        lMessage = amqp.Message(" ".join(r1) + " ".join(r2))
        res.append(lMessage)
        lMessage.properties["delivery_mode"] = 1
        lChannel.basic_publish(lMessage, exchange=Exchange, routing_key='transient')
        if count == 1000000:
            break
    t.stop()
    print "added %d tasks in %s" % (count, t)

    lChannel.close()
    lConnection.close()

except Exception as e:
    print e

因此,我做了一个更改,将异步任务提交给Cellery in the loop,如下所示:

files = [foo, bar]
f1 = open(files[0])
f2 = open(files[1])
res = []
count = 0
for r1, r2 in izip(FastqGeneralIterator(f1), FastqGeneralIterator(f2)):
    count += 1
    res.append(tasks.process_read_pair.s(r1, r2))
        if count == 10000:
        break
t.stop()
g = group(res)
for task in g.tasks:
    task.set(queue="transient")
result = g.delay()
res.append(tasks.speed.apply_async(args=("FML",), queue="transient"))
速度方法就是这样:

@app.task()
def speed(s):
    return s
再次提交任务,我慢了

因此,这似乎与以下问题无关:

  • 如何迭代以提交到队列
  • 我提交的消息

  • 但是,它与函数的排队有关?!?!我很困惑。

    再一次,这不是答案,更多的是观察。通过简单地将后端从rpc更改为redis,我的吞吐量增加了三倍多:


    重用producer实例可以提高性能:

    with app.producer_or_acquire() as producer:
        task.apply_async(producer=producer)
    
    此外,任务可能是代理对象,如果是,则必须对每次调用进行评估:

    task = task._get_current_object()
    
    使用
    group
    将自动重用制作者,这通常是您想要的 在循环中执行以下操作:

    process_read_pair = tasks.process_read_pair.s
    g = group(
        process_read_pair(r1, r2)
        for r1, r2 in islice(
            izip(FastGeneralIterator(f1), FastGeneralIterator(f2)), 0, 1000)
    )
    result = g.delay()
    

    你也可以考虑安装C代码> LababByMQ模块,用C语言编写。 如果可用,

    amqp://
    传输将自动使用它(或者可以使用
    librabbitmq://
    手动指定):

    pip install librabbitmq
    
    直接使用基础库发布消息可能会更快 因为它会绕过芹菜路由助手等,但我不会 我觉得速度要慢得多。如果是这样的话,芹菜肯定有优化的空间, 因为到目前为止,我主要集中在优化消费者方面

    还请注意,您可能希望在同一个t中处理多个DNA对