---
# System prepended metadata

title: Celery_Optimizing
tags: [celery 5.2, python, celery]

---

# Celery_Optimizing
###### tags: `celery` `celery 5.2` `python`

[官方連結_Optimizing](https://docs.celeryq.dev/en/master/userguide/optimizing.html)
## Optimizing
### Introduction
預設的配置做了很多的折衷考量。對任何一個案例來說它都不是最好的，但對大多數的情況都是夠用的。

你可以自己根據特定案例實現最佳化。

最佳化可以應用在不同環境屬性，無論是任務的執行時間、記憶體的使用量、或高負載情況下的響應。
### Ensuring Operations
在[Programming Pearls](http://www.cs.bell-labs.com/cm/cs/pearls/)乙書中，Jon Bentley通過問題提出了『back-of-the envelope calculations可以被用來作為提前計劃』(簡便計算方法)

『How much water flows out of the Mississippi River in a day?』

這練習的重點是在於說明系統可以每天及時處理的資料量有限。粗略計算(Back of the envelope calculations)可以被用來作為提前計劃的方法。

在Celery中，如果任務需要花費十分鐘完成，每分鐘產生十個新的任務，那隊列將永遠無法清空。這就是為什麼監控隊列長度是非常重要的一件事。

一個方法是使用[Munin](http://docs.celeryproject.org/en/master/userguide/monitoring.html#monitoring-munin)。你應該設置警示，當任何隊列到達了無法負荷狀態的時候都可以及時的發出通知。這樣你就可以採取適合的調整，像是增加Worker節點，或是撤消不必要的任務。
### General Settings
#### Broker Connection Pools
`broker connection pool`在2.5版之後預設啟用。

你可以調整[broker_pool_limit](https://docs.celeryq.dev/en/master/userguide/configuration.html#std-setting-broker_pool_limit)來設置最小化資源競爭，這數值應該基於使用的Broker連接的`threads/green-threads`
#### Using Transient Queues
Celery建立的隊列預設是持久化的。這意味著Broker會將訊息寫到磁碟來確保當Broker重新啟動之後任務依然會被執行。

但某些情況下訊息遺失是沒問題的，因此並非所有的任務都需要持久化訊息，你可以建立一個臨時的隊列來處理這些可以遺失的任務以提高效能：
```python
from kombu import Exchange, Queue

task_queues = (
    Queue('celery', routing_key='celery'),
    Queue('transient', Exchange('transient', delivery_mode=1),
          routing_key='transient', durable=False),
)
```
或利用[task_routes](https://docs.celeryq.dev/en/master/userguide/configuration.html#std-setting-task_routes)
```python
task_routes = {
    'proj.tasks.add': {'queue': 'celery', 'delivery_mode': 'transient'}
}
```
`delivery_mode`改變了訊息到隊列的傳遞方式，`delivery_mode=1`代表不會將訊息寫入磁碟，`delivery_mode=2`代表資料將寫入磁碟(預設)。

可以利用參數`queue`(或[task_routes](https://docs.celeryq.dev/en/master/userguide/configuration.html#std-setting-task_routes)設置)將任務指向所定義的臨時隊列：
```python
task.apply_async(args, queue='transient')
```

更多可參閱[routing guide](https://docs.celeryq.dev/en/master/userguide/routing.html#guide-routing)


### Worker Settings
#### Prefetch Limits
`Prefetch`是從AMQP繼承來的術語，時常被使用者誤解。

預取限制是Worker可以保留在自己身上的任務數量限制。假如為0，那Worker將持續的使用(consuming)訊息，而不是認為可能有其他可用的Worker節點能夠更快地處理它們，或者消息可能甚至不適合載入記憶體。

Worker預取數量設置依[worker_prefetch_multiplier](https://docs.celeryq.dev/en/master/userguide/configuration.html#std-setting-worker_prefetch_multiplier)的設置再乘上並行槽數量(concurrency slots)(processes/trheads/green-threads)

假如你有很多執行時間很長的任務，你希望乘數的數值為1：這代表一次只會有一個任務保留在Worker的進程(process)中。

然後，假如你有很多執行時間很短的任務，吞吐量與往返的延遲對你很重要，這數字應該很大。如果訊息都被預取並且載入記憶體中，那Worker每秒是可以處理更多任務的。你也許需要試著去找到最佳化數值。50或150在這些情況中也許是合理的。假設64或128。

如果你有長、短時間組合的任務，最好的選擇就是使用兩個Worker節點來分別設置，並根據執行時間來路由這些任務(參考[Routing Tasks](https://docs.celeryq.dev/en/master/userguide/routing.html#guide-routing))。

#### Reserve one task at a time
任務只有在被確認[acknowledged](https://docs.celeryq.dev/en/master/glossary.html#term-acknowledged)之後，該任務才會被從隊列中刪除，因此如果確認任務之前執行它的那個Worker掛了，它會被重新派送到另一個Worker去(或是當這Worker重啟之後重新派送給它)。

在使用預設的提早確認時(early acknowledgment)，預取(prefetch)的乘數設置為1，這代表Worker將為每一個進程(process)保留最多一個額外的任務。換言之，如果Worker啟動參數為[`-c 10`](https://docs.celeryq.dev/en/master/reference/cli.html#cmdoption-celery-worker-c)，那就代表著這Worer在任何時間也許保留最多20個任務(10個確認任務執行，10個未確認保留任務)

通常使用者會詢問是否可以禁用`prefetching of tasks`，但他們真正的意思是，是否可以讓Worker僅保留跟進程相同數量的任務。(10個未確認保留任務[`-c 10`](https://docs.celeryq.dev/en/master/reference/cli.html#cmdoption-celery-worker-c))。

這是可能的，但也不能不啟用延遲確認[`latea ckonwledgment`](https://docs.celeryq.dev/en/master/glossary.html#term-late-acknowledgment)。預設情況下使用這個選項代表如果執行中的任務發生電源故障或Worker實例突然被刪除，該任務將被重新啟動，這也代表這個任務必需是[idempotent](https://docs.celeryq.dev/en/master/glossary.html#term-idempotent)(冪等，也就是不管你是初一還是十五執行的結果都必需是要一樣的)

:::info
**See also:**
參閱[Should I use retry or acks_late?](https://docs.celeryq.dev/en/master/faq.html#faq-acks-late-vs-retry)
:::


你可以透過下面配置啟用：
```python
task_acks_late = True
worker_prefetch_multiplier = 1
```
### Memory Usage
如果你在prefork worker上遇到高記憶體使用的狀況，首先你必需要確定，這個問題是不是也發生在Celery master process上。通常Celery master process的記憶體用量是不會在啟動之後持續的急劇增加。如果你觀察到有這種情況發生，也許就說明著存在著[記憶體漏失](http://terms.naer.edu.tw/detail/3228824/)的bug，也許要提交給Celery issue tracker。

如果只是單純你的chile process([子處理](http://terms.naer.edu.tw/detail/18604957/))有著這種高記憶使用量的狀況，那就說明你的任務是有問題的。


要記得，Python的進程記憶體用量(process memory usage)有一個高水位，而且直到它的子處理停止之前是不會放掉記憶體回去給作業系統的。這意味著，單一個高記憶體用量的任務可能會永久性的增加其子處理的記憶體用量，一直到它重新啟動。要解決這個問題也許需要幫你的任務加一些chunking logic，以減少其peak memory的使用。

由於子處理中的高水位(high watermark)與記憶體漏失的問題，Celery Workers有兩種主要方法可以幫助我們減少記憶體用量：[worker_max_tasks_per_child](https://docs.celeryq.dev/en/master/userguide/configuration.html#std-setting-worker_max_tasks_per_child)與[worker_max_memory_per_child](https://docs.celeryq.dev/en/master/userguide/configuration.html#std-setting-worker_max_memory_per_child)這兩個設置。

你要注意一下，不要把這些設置設的太低，不然你的Workers絕大部份的時間會花在重新啟動子處理，而不是處理你的任務。舉例來說，如果你用一個[worker_max_tasks_per_child](https://docs.celeryq.dev/en/master/userguide/configuration.html#std-setting-worker_max_tasks_per_child)=1的設置，而且你的子處理要一秒才能啟動，那這個子處理(chile process)一分鐘最多就是處理六十個任務(假設任務立即處理)。當你的任務總是超過[worker_max_memory_per_child](https://docs.celeryq.dev/en/master/userguide/configuration.html#std-setting-worker_max_memory_per_child)的時候，可能也會出現類似的問題。
## History
20190729_依據4.3版本說明調整
20220520_依據5.2版本說明調整