Backpressure slows producers down to the consumer's
pace. That works when the producer can wait. An HTTP
client on the other side of the internet can't be told
to slow down: if the node accepts every request, the
queue grows, every request waits longer, and soon all
of them time out. Serving nobody is worse than serving
some.
Load shedding is refusing work on purpose so that
the work you accept still finishes in time. A quick
"busy, try again" (HTTP 503) beats a slow timeout.
This page shows three places to shed.
1. Cap the work in flight. Callers bump a shared
counter before they call and drop it afterwards. With
the counter at the limit, a caller is refused at once,
without even sending a message. :atomics.add_get/3
is atomic, so concurrent callers can't both take the
last slot.
{:ok,worker}=Worker.start()counter=Admission.new()tasks=forn<-1..20doTask.async(fn->receivedo:go->Admission.run(counter,5,fn->GenServer.call(worker,{:job,n})end)endend)endfortask<-tasks,do:send(task.pid,:go)tasks|>Task.await_many()|>Enum.frequencies_by(fn{:ok,_}->:served{:error,reason}->reasonend)|>IO.inspect(label:"20 at once, limit 5")
20 at once, limit 5: %{overloaded: 15, served: 5}
2. Look at the queue before adding to it.Process.info(pid, :message_queue_len) tells you how
far behind a process is. Here the worker is paused
with :sys.suspend/1 so the backlog builds up without
a race, and the sender stops adding at 100.
3. Skip work nobody is waiting for. By the time a
queued job reaches the worker, the client may have
given up. Each job carries a deadline, and the worker
drops jobs that are past it instead of doing them for
no one. (Numbers sort before atoms in Erlang's term
order, so no time is ever greater than :infinity:
the jobs above never expire.)
Pitfall: the queue length is a snapshot. Many
senders checking at once can all see 99 and all
send. As a soft limit that's fine; for a hard limit
use a counter like Admission above.
Pitfall: the counter must come down even when
the work crashes, or every crash leaks a slot until
nothing is admitted. That's what the after is for.
Pitfall: shed as early as possible. Refusing in
the HTTP layer, before parsing the body or touching
the database, costs almost nothing. Refusing after
the work is half done saves almost nothing.