blackode

blackode

Issue: Response count mismatch in decode function under concurrent load

Setup: 4-node cluster, 500 concurrent HTTP requests dispatched via Task.async. All requests return within 2ms max latency.

Problem: Instrumented the decode function with entry/exit log markers. Entry log shows all 500 invocations as expected, but exit log shows only ~179 completions — roughly 64% of decode calls are not reaching the exit log point.

I am using Task.yield_many(spec.kill_cutoff_time_ms) with 4.5 seconds as timeout here. Each task will make http request and decode call Jason.decode

I have tried with Nimble ParserPool but I could reach only 232 at maximum on 64 Core system with pool size of
200

Here is the ParserPool.

defmodule GrncHub.ParserPool do
  @behaviour NimblePool
  require Logger
  
  @pool_size 500
  
  def child_spec(opts) do
    %{
      id: **MODULE**,
      start: {**MODULE**, :start_link, \[opts\]},
      type: :worker,
      restart: :permanent,
      shutdown: 500
    }
      end
  
  def start_link(\_) do
    NimblePool.start_link(worker: {**MODULE**, nil}, pool_size: @pool_size, name: **MODULE**)
  end
  
  @impl true
  def init_worker(pool_state), do: {:ok, nil, pool_state}
  
  @impl true
  def handle_checkout(:parse, \_from, worker_state, pool_state) do
    {:ok, worker_state, worker_state, pool_state}
  end
  
  @impl true
  def terminate_worker(\_reason, _worker_state, pool_state) do
    {:ok, pool_state}
  end
  
  @doc """
  Returns {:ok, decoded} | {:error, :invalid_json} | {:error, :worker_crashed} | {:error, :invalid_input}
  """
  def parse(payload, content_type, parser) when is_binary(payload) do
    parser = default_parser(content_type, parser)
    
    try do
      NimblePool.checkout!(
        __MODULE__,
        :parse,
        fn _from, worker_state ->
          {GrncHub.Parser.decode(payload, content_type, parser), worker_state}
        end,
        :infinity
      )
    catch
      :exit, reason ->
        Logger.error("ParsePool worker crashed: #{inspect(reason)}")
        {:error, %{"error" => "worker_crashed"}}
    end
  end
  
  def parse(\_invalid), do: {:error, %{"error" => "worker_crashed"}}
  
  defp default_parser(:json, nil), do: Jason
  defp default_parser(\_content_type, parser), do: parser
end

Showing Posts 1 to 10

Schultzer

Schultzer

Are you doing actual HTTP request or just benchmarking 500 concurrent processing decoding JSON, I could imagine that the bottleneck being the pool or another place in the framework. There is quite a few places that you can create scheduler starvation and mailbox contention if you don’t use the receive optimization.

But without complete reproducible example no one can help you.

andyleclair

andyleclair

Since JSON parsing is a CPU-bound task, it’s better to parse the JSON in whatever task process is making the HTTP request, rather than in another set of tasks, since all the data would have to be copied from the HTTP task to the JSON task. Does that make sense? Unfortunately you can’t just throw tasks at things and expect it will magically get faster, and processes have a cost. They’re cheap but not free.

blackode

blackode OP

Thanks for the prompt response.

I have seen that my cpu can reach maximum of 42 % with 200 pool connections for decoding.
I am doing parsing inside the main task which is making http request and as soon as response received, I am calling ParserPool.parse() My payloads are around > 1MB. I am unable to figure this out. Is there any issue with approach? Glad if you can suggest alternative.

blackode

blackode OP

Yes, I’m doing actual HTTP requests. I don’t have an example one to share. It is happening in live and I’m looking for suggestions like how can we achieve the decoding faster. My payloads are around > 1MB.

I am unable to figure this out. Is there any issue with my approach? Glad if you can suggest alternative.
Thanks :slight_smile:

beepbeepbopbop

beepbeepbopbop

From nimblepool README in their Github:

The downside of NimblePool is that, because all resources are under a single process, any resource management operation will happen on this single process, which is more likely to become a bottleneck. This can be addressed, however, by starting one NimblePool per scheduler and by doing scheduler-based dispatches.

I assume you have a single worker pool as you’ve provided an atom name for this process.

blackode

blackode OP

@beepbeepbopbop thanks for prompt reply. I will create one per scheduler and will update you.

andyleclair

andyleclair

You simply do not need a pool of parser processes. You don’t gain anything by parallelizing the parsing, simply call JSON.decode!. A pool of processes can help you if the thing you’re doing is IO bound, however, as I stated, JSON parsing is CPU bound and the work cannot be parallelized in that way. You will actually make performance worse by doing this.

Asd

Asd

How are you making these HTTP requests? Do you have an example of your request, decode task loop?

dimitarvp

dimitarvp

With a C/C++/Rust NIF anything below 50MB should be in the ballpark of milliseconds. Don’t optimise for a problem you don’t have. Just parse them in place and don’t worry about it.

Also you might be hitting a default timeout somewhere – in your original post.

saleyn

saleyn

Take a look at glazer, which is several times more efficient at parsing large JSON payloads compared to Jason.

— All posts loaded —

Where Next? Top

Trending in Discussions Top

cblavier
Hey there, It’s been more than a year since we started using LiveView as our main UI library and building a whole library of UI componen...
New
mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
mhanberg
Hi everyone! The first release candidate for the Expert language server project is now available! We’ve published a press release detai...
New
budgie
A little off-topic, but I feel like people here have a good head on their shoulders. I used to be quite good at making software. Was luc...
New
achempion
I’ve been using Emacs as my main code editor for more than a two years. It’s a custom build version although I’ve tried doom emacs and sp...
New
axelson
Hi there! :wave: @frigidcode and I (but mostly him) have been running an Elixir Book club, we’re almost done with Designing Elixir Syste...
New
budgie
I love Elixir. It’s one of 2 programming languages I’ve ever fallen in love with. But I don’t use it anymore. Serverless was the promis...
New

Other Trending Topics Top

GenericJam
Edit: 2026 May 15 - This post is archived. Mob is alive!! Main docs: mob v0.7.11 — Documentation A bit of explanation for the slightly c...
New
JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
georgeguimaraes
Just published claude-code-elixir, a plugin marketplace for Claude Code with Elixir support. These are the plugins I’ve been using for my...
New
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews