Dynein: Building an Open-Source Distributed Delayed Job Queueing System
Airbnb has build a needlessly complex solution that uses more technologies and layers than needed.  Postgres as a job queue would have met all the requirements as described in this article without going to the extent of building SQS plus DynamodDB systems and jumping through weird hoops to do delayed job processing - SQL gets you all that for virtually nothing.<p>I needed a global distributed global queueing and scheduling system.<p>Like Airbnb I also thought SQS looked ideal.<p>But I needed near instantaneous response time and I found the jobs given to SQS might take tens of seconds until clients started reading them - put something into SQS and there was no way to guarantee clients would start processing the job instantly.<p>I also wanted sorting and priority queueing and the ability to do scheduling.<p>But all of the other queueing systems were very heavyweight and complex and needed dedicated libraries that were often patchy and out of date and unreliable and a problem.<p>I also absolutely required HTTP access to the queueing system, and I liked the efficiency of SQS&#x27;s long polling over HTTP and I wanted that too.<p>So I wrote my own global queueing and scheduling system in a few handfuls of lines of code using Python, Postgres SKIP LOCKED and also an async web server.<p>I can&#x27;t easily show you the async server code, but it&#x27;s really trivial - anyone can write a simple web server to sit in front of code that does Postgres SKIP LOCKED.  I&#x27;ve posted my Python SKIP LOCKED queueing code here on HN before and I&#x27;ll do it again now for anyone wanting to do the same thing.<p>Put the following code into a function in a web server that supports long polling and you&#x27;ve solved all of the goals that Airbnb faced without needing to use SQS or the other systems that they built to make a global distributed queueing system.  This solution is also lightning fast.<p>Here is a complete implementation - nothing needed but Postgres, Python and psycopg2 driver:<p><pre><code>    import psycopg2
    import psycopg2.extras
    import random

    db_params = {
        &#x27;database&#x27;: &#x27;jobs&#x27;,
        &#x27;user&#x27;: &#x27;jobsuser&#x27;,
        &#x27;password&#x27;: &#x27;superSecret&#x27;,
        &#x27;host&#x27;: &#x27;127.0.0.1&#x27;,
        &#x27;port&#x27;: &#x27;5432&#x27;,
    }
    
    conn = psycopg2.connect(**db_params)
    cur = conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
    
    def do_some_work(job_data):
        if random.choice([True, False]):
            print(&#x27;do_some_work FAILED&#x27;)
            raise Exception
        else:
            print(&#x27;do_some_work SUCCESS&#x27;)
    
    def process_job():
    
        sql = &quot;&quot;&quot;DELETE FROM message_queue 
    WHERE id = (
      SELECT id
      FROM message_queue
      WHERE status = &#x27;new&#x27;
      ORDER BY created ASC 
      FOR UPDATE SKIP LOCKED
      LIMIT 1
    )
    RETURNING *;
    &quot;&quot;&quot;
        cur.execute(sql)
        queue_item = cur.fetchone()
        print(&#x27;message_queue says to process job id: &#x27;, queue_item[&#x27;target_id&#x27;])
        sql = &quot;&quot;&quot;SELECT * FROM jobs WHERE id =%s AND status=&#x27;new_waiting&#x27; AND attempts &lt;= 3 FOR UPDATE;&quot;&quot;&quot;
        cur.execute(sql, (queue_item[&#x27;target_id&#x27;],))
        job_data = cur.fetchone()
        if job_data:
            try:
                do_some_work(job_data)
                sql = &quot;&quot;&quot;UPDATE jobs SET status = &#x27;complete&#x27; WHERE id =%s;&quot;&quot;&quot;
                cur.execute(sql, (queue_item[&#x27;target_id&#x27;],))
            except Exception as e:
                sql = &quot;&quot;&quot;UPDATE jobs SET status = &#x27;failed&#x27;, attempts = attempts + 1 WHERE id =%s;&quot;&quot;&quot;
                # if we want the job to run again, insert a new item to the message queue with this job id
                cur.execute(sql, (queue_item[&#x27;target_id&#x27;],))
        else:
            print(&#x27;no job found, did not get job id: &#x27;, queue_item[&#x27;target_id&#x27;])
        conn.commit()
    
    process_job()
    cur.close()
    conn.close()</code></pre>