Curb

A single queue.

curb -cap 10MiB -addr :61613

message states

stateDiagram-v2

[*] --> rdy: SEND
rdy --> wip: MESSAGE
wip --> rdy: NACK/DISCONNECT
wip --> ack: ACK
ack --> [*]

file format

002	ack	second
003	rdy	third
004	wip	forth
005	rdy	forth

This file can never exceed the given -cap number of bytes. Messages in state ack are removed as soon as possible.

example communication

STOMP
accept-version:1.2
host:whatever

^@

host is ignored.

CONNECTED
version:1.2
server:curb/idea.1.0 STOMP 1.2 subset

^@

The server only speaks 1.2 as of now.

SUBSCRIBE
id:0
destination:.
ack:client-individual

^@

destination is ignored, there is only one destination.

Note for implementers: It might be useful to allow clients to subscribe to the message count i.e. queue length. E.g. destination:./count.

Only ack:client-individual is supported, for now.

SEND
destination:.

hello or whatever
^@

destination is ignored, there is only one destination.

MESSAGE
subscription:0
message-id:00001234
ack:00001234
destination:.

hello or whatever
^@

Notable difference to actual STOMP servers:
MESSAGE frames will NOT include user defined headers that were present when the message was sent. They are discarded. It may or may not contain the content-length header.

NACK
id:00001234

^@
MESSAGE
subscription:0
message-id:00001234
ack:00001234
destination:.

hello or whatever
^@

Nacked messages are redelivered.

ACK
id:00001234

^@
UNSUBSCRIBE
id:0

^@

UNSUBSCRIBE is supported to pause receiving while keeping the connection open.

Subscribing consecutively without unsubscribing will cause an error.

DISCONNECT
receipt:77

^@
RECEIPT
receipt-id:77

^@

The receipt headers and RECEIPT frames are supported as in STOMP 1.2.

BEGIN, COMMIT, ABORT, are not supported, cause errors. I am unsure whether they should be supported in the future as their implementation introduces quite a bit of complexity.

features

Curb does not have many features. It prioritizes simplicity.

Some features could be implemented in other layers.

destinations

A Stomp routing layer can be used to add destinations to Curb.

Each destination needs a Curb instance.

architecture-beta

service client(unknown)[STOMP Client]

group arch(server)[Curb Destinations]
service router(internet)[STOMP Router] in arch
service curb1(database)[Curb 1] in arch
service curb2(database)[Curb 2] in arch
service curbN(database)[Curb N] in arch

router:T -- L:curb1
router:R -- L:curb2
router:B -- L:curbN

client:R -- L:router

If the router receives a SEND destination:./1 from a client, it forwards it to the respective Curb instance.

SUBSCRIBE destination:./1 is forwarded to "Curb 1" as SUBSCRIBE destination:. (if the router is not already subscribed).

MESSAGE frames from instances are forwarded to one of the clients that subscribed to the destination.

If all subscribers of a destination are busy or unsubscribe, the router unsubscribes from the respective Curb instance.

MESSAGE frames that cannot be forwarded to clients are nacked.

(If ACK/NACK frames would include a destination header, routers could be more stateless. This could also be achieved, if the router adds destination info to the message id.)

transactions

By buffering SEND frames until a corresponding COMMIT frame happens

architecture-beta

service client(unknown)[STOMP Client]

group arch(server)[Tx Curb]
service buffer(database)[Curb Tx Buffer] in arch
service curb(database)[Curb 1] in arch

buffer:R -- L:curb

client:R -- L:buffer

Note that this particular pattern is not very compatible with other patterns. E.g. cross instance transactions need more complicated patterns and might not be possible with Curb at all. (And might not be possible in any system, physically?)

partitions

Similar to destinations, partitioning one queue into multiple Curb instances can be supported when adding a load balancer instead of a router.

architecture-beta

service client(unknown)[STOMP Client]

group arch(server)[Partitioned Curb]
service balancer(internet)[STOMP Load Balancer] in arch
service curb1(database)[Curb 1] in arch
service curbN(database)[Curb N] in arch

balancer:T -- L:curb1
balancer:R -- L:curbN

client:R -- L:balancer

replication

Yet another similar pattern could be applied for message replication.

Though, how to deal with the situation (that should in theory never occur) when a MESSAGE frame is only received from one of the instances is unclear. Maybe another feature in Curb is needed: Acking/deleting messages that have never been delivered, so that the replicator need not subscribe to all but one instance, while still being able to ack their messages.

Depending on why replication is desired, different strategies could be applied.

Or maybe replication should be achieved by using an additional database.

Or by using an underlying replicated file system.

Or maybe Curb is not the right fit, if you need certain types of replication.

data consistency vs. speed configurations

All messages are written to the file system. Curb aims to have reasonable, derived properties depending on the properties of the underlying file system.

You can choose file systems for Curb instances depending on your use case.

creating queues

Creating new queues implies spinning up additional Curb instances. Some operator service can take care of that.

Keep in mind that most storage devices have limited capacity.

related