IgnisMQ¶
A distributed, persistent, high-throughput message queue for Java
IgnisMQ is a Java library that provides reliable asynchronous message processing built on top of Magazine and Aerospike, with first-class Dropwizard integration for microservice environments.
Key Features¶
-
High Throughput
Backed by Aerospike, with a consume path that costs a flat four storage operations per message however many consumers you add. Sharded queues spread load across the cluster, and throughput scales with consumers until the storage tier is the limit. See the benchmark.
-
Reliable Delivery
Failed messages are automatically sidelined and can be shoveled back for reprocessing. Stuck messages are swept and recovered.
-
Distributed Coordination
ZooKeeper-based leader election picks the instance that assigns the sweep partition; the assigned instance - leader or not - is the one that sweeps. Exactly one sweeps at a time.
-
Configurable Everything
Concurrency and shovel configuration are editable on a running queue. TTLs, shards, batching and sweep durations are fixed at creation.
How It Works¶
sequenceDiagram
participant App as Your Application
participant MQ as IgnisMQManager
participant Q as MagazineQueue
participant M as Magazine (Aerospike)
participant S as Sideline Magazine
App->>MQ: getQueue("order-queue")
MQ-->>App: IQueue
App->>Q: publish(order)
Q->>M: magazine.load(json)
Note over Q: A consumer task polls on the worker pool
Q->>M: magazine.fire()
M-->>Q: MagazineData
Q->>App: handler.handle(order)
alt Handler returns false
Q->>S: sidelineMagazine.load(json)
Note over S: Message saved for retry
alt Sideline accepted it
Q->>M: magazine.delete(data)
else Sideline refused it
Note over M: Left in place for the sweeper<br/>- deleting would lose the only copy
end
else Handler returns true
Q->>M: magazine.deleteAll(batch)
end
Note over S: A shovel task runs periodically
S-->>M: Move messages back to main queue
Quick Start¶
// 1. Extend the bundle
public class MyIgnisMQBundle extends IgnisMQBundle<MyConfig> {
@Override
protected IgnisMQContext context(MyConfig config) {
return IgnisMQContext.builder()
.clientId(config.getClientId())
.farmId(config.getFarmId())
.storage(new AerospikeStorage(config.getAerospikeConfig(), "my-ns"))
.curatorFramework(curator)
.build();
}
}
// 2. Register in your Application
bootstrap.addBundle(ignisMQBundle);
// 3. Initialize handlers and create a queue
manager.initialiseMessageHandlers(Map.of(
"order-handler", Map.entry(Order.class, new OrderHandler())
));
manager.createQueue(CreateQueueRequest.builder()
.name("order-queue")
.concurrency(8)
.messageHandlerType("order-handler")
.build());
// 4. Publish messages
manager.getQueue("order-queue").publish(new Order("ORD-123", 99.99));
// 1. Create storage and manager
BaseStorage storage = new AerospikeStorage(aerospikeConfig, "my-ns");
IgnisMQManager manager = new IgnisMQManager(
"my-client", storage, new ObjectMapper(),
new SimpleMeterRegistry(), curatorFramework, "farm-1",
IgnisMQSettings.defaults()
);
// 2. Register handlers
manager.initialiseMessageHandlers(Map.of(
"order-handler", Map.entry(Order.class, new OrderHandler())
));
// 3. Create a queue and publish
manager.createQueue(CreateQueueRequest.builder()
.name("order-queue")
.concurrency(4)
.messageHandlerType("order-handler")
.build());
manager.getQueue("order-queue").publish(new Order("ORD-123", 99.99));
Message Lifecycle at a Glance¶
stateDiagram-v2
[*] --> Published: publish()
Published --> Fired: consumer fires message
Fired --> Consumed: handler returns true
Fired --> Sidelined: handler returns false / exception
Consumed --> [*]: message deleted
Sidelined --> Shoveled: shovel task runs
Shoveled --> Published: message re-queued
Fired --> Swept: message stuck (no ack)
Swept --> Sidelined: sweeper recovers
Architecture Overview¶
flowchart TD
subgraph Application
App["Your App"] --> Bundle["IgnisMQBundle"]
Bundle --> Manager["IgnisMQManager"]
end
subgraph Queue Management
Manager --> Q1["MagazineQueue #1"]
Manager --> Q2["MagazineQueue #2"]
Manager --> QN["MagazineQueue #N"]
end
subgraph Per-Queue Components
Q1 --> C["Consumer tasks<br/>(1s fixed delay)"]
Q1 --> SH["Shovel tasks<br/>(configurable interval)"]
end
subgraph Cluster Coordination
Manager --> TI["TaskInitializer"]
TI --> LE["LeaderElector"]
LE --> ZK["ZooKeeper"]
TI --> SW["Sweeper<br/>(one assigned instance)"]
end
subgraph Storage
C --> Mag["Magazine<br/>(Main)"]
C --> Side["Magazine<br/>(Sideline)"]
SH --> Mag
SH --> Side
SW --> AQS["AerospikeQueueService"]
Mag --> AS["Aerospike"]
Side --> AS
AQS --> AS
end
Manager -.->|refreshQueues<br/>every 5 min| AS
What to Read Next¶
| Page | Description |
|---|---|
| Getting Started | Prerequisites, installation, and your first queue in 5 minutes |
| Core Concepts | Message lifecycle, sidelining, shoveling, sweeping, leader election |
| Usage Guide | Complete examples — Dropwizard, standalone, batching, shoveling |
| Architecture | Component deep-dive, threading model, data flow |
| API Reference | Every class, method, and field documented |
| Configuration | All configuration options with defaults and constraints |
| Error Codes | Complete error catalog with causes and solutions |
| Monitoring Runbook | What to alert on, what each alert means, what to check |
| Console | The bundled console: read views, per-shard depth, and the role-guarded actions |
| Aerospike Backend | Data model, indexing, sweep internals |
| Roadmap | What IgnisMQ does not do yet, and why |
| References | Academic and industry work that inspired IgnisMQ's design |