Skip to main content

Batching

Cajun processes messages in batches to improve throughput for high-volume scenarios. By default, actors process up to 10 messages per batch, reducing context switching and mailbox overhead.

How It Works​

Actors drain multiple messages from their mailbox and process them sequentially before polling again:

  • Without batching (size=1): Process 1 message → poll → process 1 → poll
  • With batching (size=50): Process 50 messages → poll → process 50 → poll

Benefits:

  • Reduced context switching (fewer poll operations)
  • Better CPU cache locality
  • Lower mailbox synchronization overhead

Configuration​

Configure batch size using withBatchSize() on the actor builder:

// Default batch size is 10
Pid actor = system.actorOf(MyHandler.class).spawn();

// Custom batch size for high throughput
Pid highThroughput = system.actorOf(MyHandler.class)
.withBatchSize(50)
.spawn();

// For stateful actors
Pid stateful = system.statefulActorOf(MyHandler.class, initialState)
.withBatchSize(100)
.spawn();

Choosing Batch Size​

Batch SizeUse CaseTrade-off
1-10Interactive, low-latencyLower throughput, minimal latency
10 (default)General purposeBalanced
50-100High-throughput processingHigher latency, maximum throughput

Guidelines:

  • Latency-sensitive: 1-10
  • Throughput-optimized: 50-100
  • Bulk processing: 100+

Performance Impact​

Typical improvements with batching:

  • 2-3x throughput increase for CPU-light workloads
  • 25-50x reduction in mailbox overhead
  • Most effective when processing >1000 messages/sec

Use Cases​

Event Stream Processing​

// High-throughput event processor
Pid eventProcessor = system.actorOf(EventHandler.class)
.withBatchSize(100)
.spawn();

for (Event event : eventStream) {
eventProcessor.tell(new ProcessEvent(event));
}

Database Batch Writes​

public class DatabaseWriterHandler implements Handler<WriteCommand> {
private final List<WriteCommand> batch = new ArrayList<>();

@Override
public void receive(WriteCommand cmd, ActorContext context) {
batch.add(cmd);
if (batch.size() >= 10) {
database.batchWrite(batch);
batch.clear();
}
}
}

Pid writer = system.actorOf(DatabaseWriterHandler.class)
.withBatchSize(10)
.spawn();

Message Queue Consumer​

// Match batch size to message queue batch size
Pid consumer = system.actorOf(MessageConsumerHandler.class)
.withBatchSize(200)
.spawn();

When to Use Batching​

Batching helps most when:

  • High message volume (>1000 messages/sec)
  • CPU-light processing
  • Bursty traffic patterns
  • Sequential processing is acceptable

Use smaller batches when:

  • Low message volume (<100 messages/sec)
  • CPU-heavy processing
  • Strict latency requirements (<1ms)
  • Interactive request-reply scenarios

Best Practices​

Start with Defaults​

// Start with default batch size (10)
Pid actor = system.actorOf(Handler.class).spawn();

// Measure and adjust based on results

Match to Workload​

// Interactive (low latency)
Pid interactive = system.actorOf(Handler.class)
.withBatchSize(1)
.spawn();

// Background processing (high throughput)
Pid background = system.actorOf(Handler.class)
.withBatchSize(100)
.spawn();

Use Actor Pools for Parallelism​

// Multiple actors with moderate batch size
int numActors = Runtime.getRuntime().availableProcessors();
Pid[] pool = new Pid[numActors];

for (int i = 0; i < numActors; i++) {
pool[i] = system.actorOf(Handler.class)
.withBatchSize(50)
.spawn();
}

// Round-robin distribution
int next = 0;
for (Message msg : messages) {
pool[next++ % numActors].tell(msg);
}

Tuning​

Process:

  1. Start with default (10)
  2. Run load test and measure
  3. Low throughput + low CPU → increase batch size
  4. High latency → decrease batch size
  5. Mailbox fills up → increase batch size or add actors

Key metrics to monitor:

  • Throughput (messages/sec)
  • Latency (time from send to process)
  • Mailbox depth
  • CPU utilization