How to implement backpressure and load shedding?

Backpressure and load shedding are methods you can use to mitigate queue overload.

These are methods that kick in automatically therefore your system needs to have enough instrumentation to know if a queue is overloaded. For example, we can have a queue object which responds to the overloaded? message, (i.e. queue.overloaded?).

The definition of queue overload (i.e. overloaded?) is up to you to define.

A queue can be overloaded if the queue latency is above a certain threshold.

class Queue
  LATENCY_THRESHOLD = 10.seconds

  def overloaded?
    @latency > LATENCY_THRESHOLD
  end
end

A queue can be overloaded if queue size is above a certain threshold.

class Queue
  SIZE_THRESHOLD = 1000

  def overloaded?
    @size > SIZE_THRESHOLD
  end
end

A queue can be overloaded if produce rate is above a certain threshold.

class Queue
  PRODUCE_RATE_THRESHOLD = 200000 # messages per second

  def overloaded?
    @produce_rate > PRODUCE_RATE_THRESHOLD
  end
end

You need to pick a definition of overload for which you are able to instrument.

The difference between backpressure and load shedding becomes the action that is taken by the consumers/producers in the event of overload.

Load shedding

Load shedding is about dropping/rejecting messages in the event of overload. You can decide to drop them at the consumer and/or at the producer.

class Consumer
  def consume
    queue.each_message do |message|
      next if queue.overloaded? # ignore received message during overload

      process(message)
    end
  end

  def process(message)
    # process message
  end
end


class Producer
  def produce(message)
    return if queue.overloaded? # don't produce message during overload

    queue.add(message)
  end
end

Backpressure

Backpressure is about reducing produce rate by slowing down the producers. Backpressure allows the client of the producer to decide how to deal with the increased delay (i.e. abort the operation, wait until the message can be produced, retry the operation).

You can implement backpressure in the producers.

class Producer
  def produce(message)

    while queue.overloaded? do
      # slow down producer by blocking it until the message can be added
      sleep 1
    end

    queue.add(message)
  end
end

class Producer
  def produce(message)
    # reject the message but properly notify the client of produce so it
    # can choose to retry or not
    raise "Queue is overloaded" if queue.overloaded?

    queue.add(message)
  end
end

You can find other posts of my series on queues here. I can send you my posts straight to your e-mail inbox if you sign up for my newsletter.