使用celery处理大量文本文件

时间:2014-01-29 17:32:45

标签: python performance rabbitmq celery hpc

背景

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

我已经建立了芹菜和兔子,我的工作人员按如下方式启动:

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序列化来提高性能,不是 在“瞬态”队列中将任何内容写入磁盘(通过delivery_mode)。

芹菜启动

要设置芹菜框架,我首先在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")

对于10000对读取,这需要大约1.5秒。然后,我打电话给小组的延迟提交给工人,大约需要20秒,如下所示:

result = g.delay()

使用rabbitmq控制台监控,我发现我做得很好,但不够快。

rabbitmq graph

问题

那么,有没有办法加快速度呢?我的意思是,我希望看到每秒至少处理50,000个读取对,而不是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等等。我已经浏览了芹菜网站和文档。如果我无法获得更高的性能,我将不得不放弃这种方法而转向另一种解决方案(基本上将工作分成许多较小的物理文件,并使用多处理或其他方式直接在每个计算节点上处理它们)。但是,如果无法在群集上传播此负载,那将是一种耻辱。此外,这似乎是一个非常优雅的解决方案。

提前感谢您的帮助!

6 个答案:

答案 0 :(得分:2)

不是答案,但评论时间太长。

让我们把问题缩小一点......

首先,尝试跳过所有正常的逻辑/消息准备工作,然后使用当前库执行最紧密的发布循环。看看你得到什么率。这将确定您的非队列相关代码是否存在问题。

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

from amqplib import client_0_8 as amqp
try:
    lConnection = amqp.Connection(
        host=###,
        userid=###,
        password=###,
        virtual_host=###,
        insist=False)
    lChannel = lConnection.channel()
    Exchange = ###

    for i in range(100000):
        lMessage = amqp.Message("~130 bytes of test data..........................................................................................................")
        lMessage.properties["delivery_mode"] = 2
        lChannel.basic_publish(lMessage, exchange=Exchange)

    lChannel.close()
    lConnection.close()

except Exception as e:
    #Fail

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

答案 1 :(得分:2)

重用生产者实例应该可以提高性能:

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编写的librabbitmq模块。 amqp://传输将自动使用(如果可用)(或者可以使用librabbitmq://手动指定:

pip install librabbitmq

使用底层库直接发布消息可能会更快 因为它会绕过芹菜路线助手等等,但我不会 认为它慢得多。如果是这样,Celery肯定有优化的空间, 因为到目前为止我主要关注的是优化消费者方面。

另请注意,您可能希望在同一任务中处理多个DNA对, 因为使用较粗糙的任务粒度可能对CPU /内存缓存等有益, 并且它通常会使并行化饱和,因为这是一种有限的资源。

注意:瞬态队列应为durable=False

答案 2 :(得分:1)

你有一个解决方案是读取是高度可压缩的,所以取代以下

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

通过

res.append(tasks.process_bytes(zlib.compress(pickle.dumps((r1, r2))),
                                      protocol = pickle.HIGHEST_PROTOCOL),
                         level=1))

并在另一边调用pickle.loads(zlib.decompress(obj))

如果DNA序列长度不够长,它应该赢得一个因素,因为它们不够长,你可以将它们分组,然后转储并压缩。

另一个胜利可能是使用zeroMQ进行传输,如果你还没有。

我不确定process_byte应该是什么

答案 3 :(得分:0)

同样,不是答案,但评论太长。根据下面的Basic'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

你可以看到它正在向前摇摆。

test

我想现在要找出这个与内部正在发生的事情之间的区别

答案 4 :(得分:0)

我将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

img

所以,我做了一个更改,在循环中向芹菜提交异步任务,如下所示:

res.append(tasks.speed.apply_async(args=("FML",), queue="transient"))

速度方法就是这样:

@app.task()
def speed(s):
    return s

再次提交我的任务!

img

所以,它似乎没有任何关系:

  1. 我如何迭代提交到队列
  2. 我提交的消息
  3. 但是,它与功能的排队有关吗?!?!我很困惑。

答案 5 :(得分:0)

同样,不是答案,而是更多的观察。通过简单地将我的后端从rpc更改为redis,我的吞吐量增加了三倍以上:

img