axelclark

axelclark

How to handle max concurrency for ChromicPDF?

I’m using ChromicPDF in my application to allow a user to download a pdf. In my application, I have a button they can click that sends a request to my controller which generates the pdf with ChromicPDF and sends it back to the user with send_download from the controller.

ChomicPDF docs state:

To increase or limit the number of concurrent workers, you can pass pool configuration to the supervisor. Please note that this is a non-queueing session pool. If you intend to max it out, you will need a job queue as well.

I was able to generate the error in testing:

** (ChromicPDF.Browser.ExecutionError) Caught EXIT signal from NimblePool.checkout!/4

      ** (EXIT) time out

This means that your operation was unable to acquire a worker from the pool
within 5000ms, as all workers are currently occupied.
You're experiencing this error randomly under load. This would indicate that
   the number of concurrent print jobs exceeds the total number of workers in
   the pool, so that all workers are occupied.

   To fix this, you need to increase your resources, e.g. by increasing the number
   of workers with the `session_pool: [size: ...]` option.

   However, please be aware that while ChromicPDF (by virtue of the underlying
   NimblePool worker pool) does perform simple queueing of worker checkouts,
   it is not suitable as a proper job queue. If you expect to peaks in your load
   leading to a high level of concurrent use of your PDF printing component,
   a job queue like Oban will provide a better experience.

The error message suggests Oban. However, I’m not sure Oban is the best option for a couple reasons:

  1. Oban isn’t designed for the caller to block awaiting the result of the job. However, it does have the concept of a Notifier which can be used to return the pdf to the controller process to send back to the user.
  2. I would prefer not to rebuild the conn and assigns within the job because I’m reusing some functions in my controller. But I also don’t want to pass the results of ChromicPDF.Template.source_and_options/1 to the job directly.

The other options I’ve looked at are:

Any suggestions for the best option to queue the pdf printing work when I don’t want the work to be done in the background?

Marked As Solved

axelclark

axelclark

I upgraded to ChromicPDF version 1.14 and updated the :session_pool :timeout and :checkout_timeout to match what I had in poolboy. ChromicPDF and NimblePool handle my concurrency test just like poolboy.

For anyone interested in the updated ChromicPDF config options, check out the session pool docs.

Also Liked

akoutmos

akoutmos

Author of Build a Weather Station with Elixir and Nerves

Currently on mobile, so I can add more info when I am in front of a computer. I make heavy use of ChromicPDF in eaglemms.com so that customers can generate invoices. I think I used to have issues similar to what you are describing so I put poolboy in front of ChromicPDF and haven’t had any issues. I also do something similar to what you are describing where a controller generates the PDF on demand versus generating and storing it in S3 or something and poolboy+ChromicPDF has no problem keeping up (at least for my current customer load).

sorentwo

sorentwo

Oban Core Team

Using a dedicated pooler is a great option.

As an alternative for others in the future, there is a blocking option in Oban called Relay, like a transparently distributed task with concurrency limits (Pro only though).

maltoe

maltoe

Hi, author of ChromicPDF here.

I’m currently scratching my head a bit as the accepted solution in this thread does not make sense to me (putting a worker pool in front of a worker pool), yet two people independently report good experience with it :slight_smile: - I wonder if this points to an obscure bug in Chromic, or merely to an opportunity to improve its documentation.


@axelclark Let me first address your original post. I believe the docs you cited led to some confusion, rather than being helpful. Especially this line:

Please note that this is a non-queueing session pool.

This is definitely misleading. I should have written that it is not a persistently queueing session pool. The NimblePool library used in Chromic’s SessionPool does in fact queue “worker checkout” commands (by virtue of OTP message boxes & genserver). But its queue isn’t persistent, as in, if you overwhelm your system, worker checkouts will timeout and requests will be dropped; which leads to the exception you have posted. That’s also perfectly fine depending on your use-case, if you expect only few concurrent requests, or if you’re fine with the occassional failure when you exceed the pool concurrency, there’s nothing you need to do about it at all :slightly_smiling_face:.

In other words, what I meant to say in the docs is this: If you expect peaks in concurrent demand (= greater than max throughput of your system) and you are required to handle them gracefully, you must begin to write requests down for later and asynchronously process them (e.g. with a job queue like Oban, backed by a database table, and so on). This is actually not specific to ChromicPDF in any way, just generally job processing. And vice versa, if your scenario does not have peaks or you don’t care about them, that’s 100% cool, too, and you don’t need any of this.

I probably should just ditch that statement fromt the docs.


Now, with regards to putting poolboy in front of it: I’m honestly curious how that would change anything in your test setup? You should now get a poolboy exception instead of ChromicPDF’s when you overwhelm your system. Even the default checkout timeout (think, “max queue wait time”) is the same, I believe, at 5 seconds. Question goes to @akoutmos , too, of course.


Fun fact: ChromicPDF used to depend on poolboy before v0.6.0. Which is when I realized that a process pool in front of a single connection process is pointless. NimblePool eliminates the worker processes and only pools “resources”.

maltoe

maltoe

@axelclark pushed a PR adding the checkout_timeout option [#276] Make checkout timeout configurable by maltoe · Pull Request #278 · bitcrowd/chromic_pdf · GitHub

reviews welcome, especially wrt. the changes in the docs. Do you think it’s more clear now?

axelclark

axelclark

Check out the Elixir School article on poolboy for an overview on poolboy:

Why use Poolboy?

Let’s think of a specific example for a moment. You are tasked to build an application for saving user profile information to the database. If you’ve created a process for every user registration, you would create an unbounded number of connections. At some point the number of those connections can exceed the capacity of your database server. Eventually your application can get timeouts and various exceptions.

The solution is to use a set of workers (processes) to limit the number of connections instead of creating a process for every user registration. Then you can easily avoid running out of your system resources.

Then for my use case, in the worker, I swapped out :math.sqrt(x) in the article for ChromicPDF.print_to_pdf/2. ChromicPDF.print_to_pdf/2 is the function that uses chromium so I want to limit and queue the processes that can access chromium at any one time.

  def handle_call({:square_root, x}, _from, state) do
    IO.puts("process #{inspect(self())} calculating square root of #{x}")
    Process.sleep(1000)
    {:reply, :math.sqrt(x), state}
  end

Where Next?

Popular in Questions Top

Tee
can someone please explain to me how Enum.reduce works with maps
New
_russellb
I want to try my hand at web scraping. What tools/libraries do I need to use. I’m hoping to turn this into something professional so don’...
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
mgjohns61585
Could someone help me? I'm making my first elixir program, number guessing game. I can't figure out how to convert the user's guess from ...
New
rms.mrcs
Hi, I need to transform a list of numbers into a map where the keys are the indexes and the values are the original values of the list....
New
johnnyicon
Hi all, I've just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I'm trying to use Postg...
New
beno
I will often find my self writing things similar to: case some_value do nil -> something() "" -> something() _ -> someth...
New
belgoros
I’m not a pro in using Regex and can’t figure out why the following behaviour happens, especially if we take into account the difference ...
New
jc00ke
Expanding on this topic: https://forum.elixirforum.net/t/map-typespec-question/19217 Let’s say I have a map with required and optional k...
New

Other popular topics Top

Qqwy
Update: How to use the Blogs & Podcasts section You can post links to your blog posts or podcasts either in one of the Official Blog...
3268 119930 1237
New
Tee
can someone please explain to me how Enum.reduce works with maps
New
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
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
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
mgjohns61585
Could someone help me? I'm making my first elixir program, number guessing game. I can't figure out how to convert the user's guess from ...
New
9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New
lucidguppy
I have a super simple question about elixir - how would I take a file like this foo bar baz and output a new file that enumerates th...
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New

We're in Beta

About us Mission Statement