sheharyarn

sheharyarn

Que - Elixir Job Processing with Mnesia

A few months back I created this thread asking everyone’s opinion on different background job processing libraries available for Elixir and was encouraged to go with a simple GenServer at the time.

I eventually needed to persist job state and was reminded of this table from @sasajuric’s Elixir in Action (and his tweet):

So I decided to stay in BEAM-land and used Mnesia in my project for a while before eventually releasing it as (yet another) background job processing library.

I would really appreciate if I could get the community’s feedback on the project implementation and some pointers, especially on:

  • The way child processes are currently handled
  • Testing the Supervisor and GenServer
    • What’s the right way of testing something like this?
  • Adding Delayed Jobs
    • Should I create another GenServer for Delayed Jobs or modify the current one?

Thank you! :smile:


You can check out the project on Github:

Que

Most Liked

benwilson512

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

This definitely seems interesting. Just a few comments:

  1. It seems like the workers are configured under the Que application and not the user application. This can be an issue if the tasks use the user’s database for example.

Que starts, grabs a persisted job, and tries to run it. If the job talks to the database immediately the user’s Repo process may not yet be up.

  1. Unfortunately your Queue module isn’t a queue, it’s a stack. https://github.com/sheharyarn/que/blob/master/lib/que/queue.ex#L72-L89. You’re placing new jobs on the front of a list, and then pulling jobs off also from the front of a list, treating it as a stack not a queue.

  2. There seems to be no real isolation between queues. This is problematic for a variety of reasons. From a performance perspective everything is serialized through your Que.Server genserver, which won’t scale as you have more queues or as particular queues become very busy.

It’s also an issue from a fault tolerance perspective. Any issue with one queue will nuke every other queue.

  1. Isolation between workers is limited. https://github.com/sheharyarn/que/blob/master/lib/que/queue_set.ex#L102 goes through each queue completely synchronously and then just runs https://github.com/sheharyarn/que/blob/master/lib/que/queue.ex#L43 some N jobs in each queue under the main single task supervisor.

Each job is executed in its own process which is good, but by putting them under all the same supervisor with the same settings all you need is 5 job failures in 3 seconds to take down all the other jobs. You can of course just adjust the supervisor settings but even still, different supervisors for different queues would be better.

The root issue is that you do 99% of the stuff with a single genserver. There also appear to be race conditions. For example, two nodes starting at the same time will both query mnesia for incomplete jobs. You do that in a transaction, so one will run after another, but since you don’t update the state in that transaction they’re both still gonna get the same list of incomplete jobs and both try to run them.

I apologize that all of this is so negative. The issue is that while Erlang and Elixir are indeed good fits for the items on that list you have, the reason that they’re good is because Erlang and Elixir offer excellent primitives with which to solve the problem. Those primitives still need to be used correctly, and doing so can actually be pretty hard because the problems themselves are pretty hard.

sheharyarn

sheharyarn

How would I go about implementing it like that?

It is a queue. I’m placing jobs at the end and pulling them from the front. I guess the push and pop method names here are misleading. I should change them to something like in and out.

This is an interesting issue. I don’t know much about OTP to understand how to approach this. Should I create multiple Supervisors under one application, one supervisor supervising multiple supervisors, each for one worker or one supervisor with multiple GenServers each for one Worker? Either way, how can I automatically start the supervisors / genservers for each defined worker? Any tips on doing this the right way?

Not at all. I really appreciate you taking the time out to go through the code and give feedback. My goal is to improve my Elixir and OTP skills. :smiley:

benwilson512

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

This is true, my apologies. Unfortunately however its implementation is such that adding N jobs distinctly produces N^2 work. If you do:

for image <- images do
  Que.add(App.Workers.ImageConverter, some_image)
end

you’re going to do N^2 work, when only linear is necessary if using something like :queue, or following its internal implementaiton. Doing ++ to the end of a growing list is generally not what you want to do.

You provide an API like poolboy for example, where you have a child_spec function and then people place it in their supervision tree in their own application.

There’s a lot to say here, I’ll have to reply tomorrow.

OvermindDL1

OvermindDL1

This is the use case for a zipper. :slight_smile:

sheharyarn

sheharyarn

Thank you for being patient with me and taking the time out to answer my questions. I went back, did some more research and started refactoring the application trying to implement your suggestions (the OTP-related ones for now):


Before I try to do the same for TaskSupervisor, a few questions:

  • Does this application structure make sense?

  • For now I’m using {:global, {Que.Server, SomeWorker}} as the GenServer names since it’s simple, but according to this post, using :global introduces extra overhead.

    • Should I keep using it?
    • Roll my own ServerRegistry GenServer like in this tutorial or go with something like gproc ?
    • Use Elixir’s own Registry?
    • Or take the simplest approach and use the worker module names as the server names?
  • When a new job is added, the ServerSupervisor checks if a GenServer for that worker already exists. If not, it spawns one. Is this the right approach?

  • Should I also keep the same structure for TaskSupervisor?

    • i.e. TaskSupervisor > TaskSupervisor.SomeWorker > Tasks

Thanks again.

Where Next?

Popular in Libraries Top

tmbb
I’ve been working on two packages (not on hex.pm yet) to build admin interfaces for phoenix apps: bureaucrat - which contains a bunch ...
New
sabiwara
Dune is a sandbox for Elixir and aims to safely evaluate user-provided code. You can try it out using this basic Elixir playground made ...
New
KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 35421 110
New
arkgil
Hi all! I’m happy to announce that Telemetry v0.3.0 is out! This release marks the conversion from Elixir to Erlang so that all the libr...
New
mcrumm
If you would like to migrate away from node/npm/webpack while still using sass, the dart_sass package provides a installer and runner for...
New
benlime
I created a new library GitHub - benvp/ex_cva: Class Variance Authority for Elixir which aims to make it very easy to define different va...
New
Crowdhailer
I have been updating a library that allows you to pipe between functions that use the erlang result tuple convention. Assuming you have...
New
Jskalc
Hi! Today, after a couple weeks of development I’ve released v0.1 of LiveVue. It’s a seamless integration of Vue and Phoenix LiveView, i...
New
OvermindDL1
I created a new library (rather I pulled out a couple files from my big project), it manages an operating system PID file for the BEAM. ...
New
maltoe
Hello! Came here to announce ChromicPDF, a pet project PDF generator I’ve been working on for the past few months. Why another PDF gener...
New

Other popular topics Top

srinivasu
How to handle excepions in elixir? Suppose i have A, B, C ,D, E modules. and each module has get() function. A.get() method will call th...
New
itssasanka
Hi all, Trying to get some more clarity over utc_datetime and naive_datetime for Ecto: https://hexdocs.pm/ecto/Ecto.Schema.html#module-...
New
sergio
I couldn’t find any guides that worked well with Phoenix 1.6.0 and esbuild. I hope this helps people test the waters and eases you into t...
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
vac
Hi, I'm quite new in Elixir and I'm trying to format a string to a PEM format. I have the certificate value like MIIDBTCCAe2...... and ...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers' Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
TunkShif
This post is an instruction guide to help you setup your Neovim for Elixir development from scratch. It includes general information on h...
273 38985 115
New
Nvim
Elixir appears to be a superior language to Python. I don’t see any advantage of Python over Elixir. Are there any?
New

Sub Categories:

We're in Beta

About us Mission Statement