Skip to main content

Backpressure

Cajun provides a robust backpressure system to help actors manage high load scenarios effectively. Backpressure is an opt-in feature that controls message flow when mailboxes approach capacity.

Overview

Backpressure prevents actors from being overwhelmed by too many messages. When enabled, it monitors mailbox capacity and applies strategies to handle overload situations.

Configuration

System-Wide Configuration

Configure backpressure for all actors in an ActorSystem:

// Define backpressure settings
BackpressureConfig config = new BackpressureConfig()
.setStrategy(BackpressureStrategy.BLOCK)
.setWarningThreshold(0.7f) // 70% capacity
.setCriticalThreshold(0.9f) // 90% capacity
.setRecoveryThreshold(0.5f); // 50% capacity

// Create ActorSystem with backpressure
ActorSystem system = new ActorSystem(new ThreadPoolFactory(), config);

Note: If no BackpressureConfig is provided, backpressure is disabled by default.

Actor-Specific Configuration

Override system defaults or enable backpressure for specific actors using BackpressureBuilder:

// Configure specific actor
BackpressureBuilder<Message> builder = new BackpressureBuilder<>(myActor)
.withStrategy(BackpressureStrategy.DROP_OLDEST)
.withWarningThreshold(0.7f)
.withCriticalThreshold(0.9f)
.withRecoveryThreshold(0.5f);

builder.apply();

// Or configure via PID through ActorSystem
system.getBackpressureMonitor()
.configureBackpressure(actorPid)
.withStrategy(BackpressureStrategy.DROP_OLDEST)
.withWarningThreshold(0.7f)
.withCriticalThreshold(0.9f)
.apply();

Backpressure States

The system operates with four states:

StateDescription
NORMALOperating with sufficient capacity
WARNINGApproaching capacity limits (above warning threshold)
CRITICALAt or above critical threshold, backpressure active
RECOVERYRecovering from critical state (below critical but above recovery threshold)

Backpressure Strategies

Choose how the system responds when mailbox capacity is exceeded:

BLOCK (Default)

Block the sender until space is available in the mailbox.

new BackpressureBuilder<>(actor)
.withStrategy(BackpressureStrategy.BLOCK)
.apply();

Use when: All messages must be processed and senders can wait.

DROP_NEW

Reject new messages when mailbox is full, keeping older messages.

new BackpressureBuilder<>(actor)
.withStrategy(BackpressureStrategy.DROP_NEW)
.apply();

Use when: Older messages have higher priority than newer ones.

DROP_OLDEST

Remove oldest messages to make room for new ones.

new BackpressureBuilder<>(actor)
.withStrategy(BackpressureStrategy.DROP_OLDEST)
.apply();

Use when: Newer messages are more important (e.g., real-time data).

CUSTOM

Implement custom backpressure logic:

CustomBackpressureHandler<Message> handler = new CustomBackpressureHandler<>() {
@Override
public boolean handleMessage(Actor<Message> actor, Message message,
BackpressureSendOptions options) {
// Custom logic to decide whether to accept the message
if (message.isPriority()) {
return true; // Always accept priority messages
}
return actor.getCurrentSize() < actor.getCapacity() * 0.9;
}

@Override
public boolean makeRoom(Actor<Message> actor) {
// Custom logic to make room in the mailbox
return actor.dropOldestMessage();
}
};

new BackpressureBuilder<>(actor)
.withStrategy(BackpressureStrategy.CUSTOM)
.withCustomHandler(handler)
.apply();

Preset Configurations

Use preset configurations for common scenarios:

// Time-critical: DROP_OLDEST strategy, prioritizes newer messages
new BackpressureBuilder<>(actor)
.presetTimeCritical()
.apply();

// Reliable: BLOCK strategy, never drops messages
new BackpressureBuilder<>(actor)
.presetReliable()
.apply();

// High-throughput: Optimized for maximum processing capacity
new BackpressureBuilder<>(actor)
.presetHighThroughput()
.apply();

Sending Messages with Options

Control backpressure behavior when sending messages:

// High priority message (bypasses backpressure)
BackpressureSendOptions highPriority = new BackpressureSendOptions()
.setHighPriority(true)
.setTimeout(Duration.ofSeconds(5));

actor.tell(urgentMessage, highPriority);

// Or use system to send with options
boolean accepted = system.tellWithOptions(actorPid, message, highPriority);

// Block until message is accepted or timeout
BackpressureSendOptions blockingOptions = new BackpressureSendOptions()
.setBlockUntilAccepted(true)
.setTimeout(Duration.ofSeconds(3));

system.tellWithOptions(actorPid, message, blockingOptions);

Monitoring

Register Callbacks

Get notified of backpressure state changes:

new BackpressureBuilder<>(actor)
.withStrategy(BackpressureStrategy.DROP_OLDEST)
.withWarningThreshold(0.7f)
.withCriticalThreshold(0.9f)
.withCallback(event -> {
logger.info("Backpressure event: {} state, fill ratio: {}",
event.getState(), event.getFillRatio());

if (event.isBackpressureActive()) {
// Take action: notify monitoring, scale resources, etc.
}
})
.apply();

Check Status

Query current backpressure status:

BackpressureStatus status = actor.getBackpressureStatus();
BackpressureState currentState = status.getCurrentState();
float fillRatio = status.getFillRatio();

// Access event history
List<BackpressureEvent> recentEvents = status.getRecentEvents();
List<StateTransition> transitions = status.getStateTransitions();

// Monitor state transitions
for (StateTransition transition : transitions) {
logger.debug("Transition from {} to {} at {} due to: {}",
transition.getFromState(),
transition.getToState(),
transition.getTimestamp(),
transition.getReason());
}

Best Practices

Choose the Right Strategy

  • BLOCK: When all messages must be processed and senders can wait
  • DROP_NEW: When older messages have higher priority
  • DROP_OLDEST: When newer messages are more important (real-time data)
  • CUSTOM: For specialized requirements

Set Appropriate Thresholds

new BackpressureBuilder<>(actor)
.withWarningThreshold(0.7f) // Start monitoring at 70%
.withCriticalThreshold(0.9f) // Activate backpressure at 90%
.withRecoveryThreshold(0.5f) // Deactivate at 50%
.apply();

Guidelines:

  • Warning: 0.5-0.7 (start monitoring)
  • Critical: 0.8-0.9 (activate backpressure)
  • Recovery: 0.3-0.5 (deactivate backpressure)

Use Monitoring

Always monitor backpressure events in production:

.withCallback(event -> {
if (event.getState() == BackpressureState.CRITICAL) {
metrics.incrementCounter("backpressure.critical");
alerting.sendAlert("Actor " + actor.getId() + " under backpressure");
}
})

High Priority Messages

Use high priority for critical messages that should bypass backpressure:

BackpressureSendOptions priority = new BackpressureSendOptions()
.setHighPriority(true);

actor.tell(criticalMessage, priority);

Example: Complete Setup

// System-wide configuration
BackpressureConfig systemConfig = new BackpressureConfig()
.setStrategy(BackpressureStrategy.BLOCK)
.setWarningThreshold(0.7f)
.setCriticalThreshold(0.9f)
.setRecoveryThreshold(0.5f);

ActorSystem system = new ActorSystem(new ThreadPoolFactory(), systemConfig);

// Create actor (inherits system config)
Pid actor = system.actorOf(MyHandler.class).spawn();

// Override for specific actor with monitoring
system.getBackpressureMonitor()
.configureBackpressure(actor)
.withStrategy(BackpressureStrategy.DROP_OLDEST)
.withWarningThreshold(0.6f)
.withCriticalThreshold(0.85f)
.withCallback(event -> {
logger.warn("Backpressure: {} at {}% capacity",
event.getState(),
event.getFillRatio() * 100);
})
.apply();

// Send messages with options
BackpressureSendOptions options = new BackpressureSendOptions()
.setHighPriority(false)
.setTimeout(Duration.ofSeconds(5));

boolean accepted = system.tellWithOptions(actor, message, options);
if (!accepted) {
logger.error("Message rejected due to backpressure");
}

Summary

  • Opt-in feature: Configure via BackpressureConfig or BackpressureBuilder
  • Four states: NORMAL, WARNING, CRITICAL, RECOVERY
  • Four strategies: BLOCK, DROP_NEW, DROP_OLDEST, CUSTOM
  • Preset configurations: timeCritical, reliable, highThroughput
  • Monitoring: Callbacks and status queries for observability
  • High priority: Bypass backpressure for critical messages