<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="../../assets/xml/rss.xsl" media="all"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>@FPSD (Articoli su Python Celery Vimcar)</title><link>https://francesco.pischedda.info/</link><description></description><atom:link href="https://francesco.pischedda.info/it/categories/cat_python-celery-vimcar.xml" rel="self" type="application/rss+xml"></atom:link><language>it</language><lastBuildDate>Sun, 14 Jun 2020 15:58:45 GMT</lastBuildDate><generator>Nikola (getnikola.com)</generator><docs>http://blogs.law.harvard.edu/tech/rss</docs><item><title>Routing Celery task for simple prioritization</title><link>https://francesco.pischedda.info/it/posts/routing-celery-task-for-simple-prioritization/</link><dc:creator>Francesco "fpsd" Pischedda</dc:creator><description>&lt;div class="section" id="problem"&gt;
&lt;h2&gt;Problem&lt;/h2&gt;
&lt;p&gt;Like most businesses, where I &lt;a class="reference external" href="https://www.vimcar.com"&gt;work&lt;/a&gt; we need to send lots of notifications to our
users, mainly emails and push notifications; the setup is quite simple:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;a service accepts requests to send notifications to users&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;the notification service prepare the message and put it in a queue&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;a pool of workers fetches messages from the queue and perform the actual delivery&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;This works reasonably well and we can scale the service increasing the
instances of the notification service and the delivery workers.&lt;/p&gt;
&lt;p&gt;This setup is used also when a user requests an export of her/his historical
data; since this process can take a while, a background job fetches the data,
generates a pdf and sends it via email using the notifications service.
At the same time we generate hundreds of thousands of notifications, usually in
the morning, and this fill up the notifiation queue so if a user requests an
export during this time frame its email will have to wait a lot before it can
be processed.&lt;/p&gt;
&lt;div class="section" id="solution"&gt;
&lt;h3&gt;Solution&lt;/h3&gt;
&lt;dl class="simple"&gt;
&lt;dt&gt;We have evaluated a couple of solutions:&lt;/dt&gt;
&lt;dd&gt;&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;per message priority&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;dedicated low priority queue for high volume automaticly generated
notifications using task routing&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;p&gt;The first solution is a generic one but as far as we have seen it is not easy
to have the guarantie that a high priority message will be delivered in our
desired time frame and we opted for the second solution because we don't need
a fine grained prioritization system (maybe in the future) but just a way to
continue to deliver user generated notifications when we are sending our
automated high volume notifications during the morning.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="implementation"&gt;
&lt;h3&gt;Implementation&lt;/h3&gt;
&lt;dl class="simple"&gt;
&lt;dt&gt;Our stack is based on &lt;a class="reference external" href="http://www.celeryproject.org/"&gt;Celery&lt;/a&gt; and it is composed mainly by two parts:&lt;/dt&gt;
&lt;dd&gt;&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;the notifications service thats send messages to a queue&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;a pool of workers that fetch messages from the queue and deliver the
notifications&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;p&gt;To achieve our goal we only had to change the way that notifications service
sends messages to the queue by specifing the low or default priority queue based
on the message type and by running a specific pool of workers bound to each
priority queue.&lt;/p&gt;
&lt;p&gt;Example code with routing:&lt;/p&gt;
&lt;pre class="literal-block"&gt;from celery import Celery

celery = Celery()
celery.main = 'Test app'
celery.config_from_object('celeryconfig')
celery.autodiscover_tasks(
    ['tasks'],
    force=True
)


@celery.task
def task(prio, message):
    print(f'{prio}: {message}')

# calling the task specifying the queue
task.apply_async(args=('default', next(generator)),
                 queue='default')&lt;/pre&gt;
&lt;dl class="simple"&gt;
&lt;dt&gt;How to run a worker specifing the queue::&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;$ celery -A tasks worker --loglevel=INFO -Q default&lt;/p&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;p&gt;This solution works but there is an efficency problem since when the low
priority queue will be empty the low priority workers will be idle wasting
precious (and paid) resources; fortunately there is a simple solution for this
because it is possible to specify more than one queue using the -Q parameter.
This way we will have a dedicated pool of workers that will work on messages
generated by user activity and a second pool of workers that will handle the
automated messages and, when those will be finished, these workers can help
with default priority messages.&lt;/p&gt;
&lt;p&gt;An example implementation is provided in this &lt;a class="reference external" href="https://github.com/fpischedda/celery-routing-example"&gt;repo&lt;/a&gt; with instruction to run the
different use cases.&lt;/p&gt;
&lt;p&gt;P.S.
&lt;a class="reference external" href="https://www.vimcar.com"&gt;We&lt;/a&gt; are &lt;a class="reference external" href="https://vimcar.de/career/jobs"&gt;hiring&lt;/a&gt;!&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;</description><guid>https://francesco.pischedda.info/it/posts/routing-celery-task-for-simple-prioritization/</guid><pubDate>Sun, 15 Jul 2018 07:10:22 GMT</pubDate></item></channel></rss>