Expand description
SQL databases for the RustStream messaging framework,
through sqlx.
The crate has two parts. The inbox is a task queue in the service’s own table, on Postgres, MySQL,
MariaDB and SQLite: a struct describes the table, SqlxBroker serves it as a RustStream
broker, a subscription claims rows, and a handler’s outcome settles them. The outbox records what
a service publishes on any RustStream broker until a consumer has processed it, and publishes the
rest again at startup.
§A service
A queue in a Postgres table, a handler that owns its backoff, and a route that writes a publish
to emails through the service’s own INSERT:
use std::time::Duration;
use chrono::{DateTime, Utc};
use ruststream::OutgoingMessage;
use ruststream_sqlx::prelude::*;
use serde::Deserialize;
use sqlx::postgres::{PgConnectOptions, PgPoolOptions};
use sqlx::{PgConnection, Postgres};
// email_jobs: job_id BIGSERIAL PRIMARY KEY, name TEXT, retry_after TIMESTAMPTZ DEFAULT now(),
// attempt SMALLINT DEFAULT 1, processed_at TIMESTAMPTZ, payload BYTEA
#[derive(Inbox, sqlx::FromRow)]
#[inbox(table = "email_jobs")]
pub struct SendEmail {
#[field(id, generated)]
job_id: i64,
#[field(group)]
name: String,
#[field(retry_after, generated)]
retry_after: DateTime<Utc>,
#[field(attempt, generated)]
attempt: i16,
#[field(processed_at, generated)]
processed_at: Option<DateTime<Utc>>,
#[field(payload)]
payload: Vec<u8>,
}
impl Publish<Postgres> for SendEmail {
async fn publish(
conn: &mut PgConnection,
message: &OutgoingMessage<'_>,
) -> Result<(), sqlx::Error> {
sqlx::query("INSERT INTO email_jobs (name, payload) VALUES ($1, $2)")
.bind(message.name())
.bind(message.payload())
.execute(conn)
.await?;
Ok(())
}
}
#[derive(Deserialize)]
pub struct Email {
to: String,
}
#[subscriber(InboxQueue::<SendEmail>::new("emails"))]
async fn send(email: &Email, Ctx(attempt): Ctx<keys::Attempt>) -> HandlerOutcome {
if deliver(email).await {
return HandlerOutcome::ack();
}
// The handler owns the backoff: a minute per attempt.
HandlerOutcome::retry_after(Duration::from_secs(60 * attempt.unwrap_or(1)))
}
#[ruststream::app]
fn app() -> impl App {
// The options read the `PG*` environment variables.
let broker = SqlxBroker::from_connect_options(PgConnectOptions::new(), PgPoolOptions::new());
RustStream::new(AppInfo::new("mailer", "0.1.0")).with_broker(
broker.route::<SendEmail>("emails"),
|b| {
b.include(send);
},
)
}SqlxBroker::from_connect_options only records the pool’s settings, so the app’s builder runs
before the runtime starts. The runtime connects the broker at startup, which builds the pool then,
and shuts it down when the service stops, which closes it. SqlxBroker::new takes a pool the
service shares with code of its own and leaves it open.
§Where things are
The guides build a service on SQL databases, in this order: getting started, enqueueing jobs, retries and dead letters, long jobs and the claim form, ordering, transactional handlers, several instances, relaying events to another broker, the outbox, testing and running in production. The architecture pages show how a queue in a SQL table works and where the crate sits between the database and the runtime, and the benchmarks what it costs.
Each topic below links to its component page on the site, which shows it in a service; the item’s own documentation keeps the detail.
§Broker
- SqlxBroker:
SqlxBroker, connected intoConnectedSqlxBrokerand closed intoClosedSqlxBroker. - Connections: what each message in work holds of the pool, and
SqlxBroker::connection_limit. - Startup: the statements a subscription prepares when it opens.
- Shutdown: the advisory locks released, and a publisher kept past it.
- Prelude:
prelude, the one glob an inbox service imports. - Errors:
SqlxBrokerError.
§Tables
- Inbox:
Inbox, a queue table described by a struct. - Roles: what each
#[field(..)]role turns on, and the row traits by hand (PayloadRow,KeyRow,AttemptRow,HeaderRow). - Columns: column names from sqlx’s attributes.
- InboxSpec:
InboxTableandInboxSpec, a table described by hand, with its settings inspec. - Insert:
Insert, a task written in the service’s own transaction, anddialect::insertby hand. - Events:
custom(..)andspec::own, one event in the service’s own SQL (Claim,Fetch,Ack,Retry,RetryAfter,Discard,DeadLetter,Extend). - checked:
#[inbox(checked, db = ..)], the statements in sqlx’s compile-time check.
§Messages
- Payload: payload mode, the column’s bytes lent to the codec.
- Rows: row mode, a handler of the row itself, and
RowBatch. - Decoding: a row that does not decode, settled by the decode policy.
§Headers
- Headers: a
headerscolumn of JSON strings. - HeaderColumn:
HeaderColumn, a headers column of the service’s own type. - InboxHeaders:
InboxHeaders, a message assembled from a headers struct. - HeaderField:
HeaderField, the header value of a field, andHeaderFieldswithput_headerby hand. - Fetch:
custom(fetch), a message read over a join.
§Subscriptions
- InboxQueue:
InboxQueue, one queue of a table, opened as anInboxSubscriber. - poll_interval:
InboxQueue::poll_intervalandSqlxBroker::poll_interval. - Workers: claims in flight for
workers(n), andworkers_by_key(n). - Batches: one claim as a batch.
- FIFO:
#[field(group, fifo = true)], one row of a group in work. - on_threads:
InboxSettings::on_threadsandInboxThreads. - Names:
#[subscriber("name")], aNamedSubscriberthrough a route.
§Forms
- Forms: the row lock form,
FOR UPDATE SKIP LOCKEDheld while the handler runs. - Lease:
#[field(locked_until)],InboxQueue::leaseandSqlxBroker::lease. - Advisory:
advisory_lock = "..", a lock on each row’s key. - Lock:
custom(lock, unlock)withLockandUnlock.
§Settlement
- Outcomes: what
ack,retryanddropdo to a row. - retry_after: a row hidden until its
retry_aftercomes. - dead_letter:
max_attempts(n)withdead_letter(..).
§Transactions
- Transactional:
InboxSettings::transactionalandTx, the handler’s writes committed with the settlement. - Isolation:
isolation = ..and SQLite’smode = ...
§Context
- Context:
keys, what a handler reads throughCtx<..>. - Attempt:
keys::Attempt. - Pool:
keys::Pool, writes that commit on their own.
§Time
- Time:
SystemClock, “now” from the host. - DatabaseClock:
DatabaseClock, “now” from the database. - Clock:
Clock, a clock of the service’s own. - QueueTime:
QueueTime, the time typeschronoandtime.
§Publishing
- Repository:
PublishandRepository, a publish written into a table. - route:
SqlxBroker::routeandRouted.
§Wake
- Wake: a publish that wakes the subscriptions of its table.
- listen_notify:
SqlxBroker::listen_notifyon Postgres.
§Databases
- Databases:
SqlxBroker::newandBuiltIn, the dialect built in for the pool’s database. - Postgres, MySQL and SQLite: what each database serves.
- AnyPool: an
AnyPool, served by the dialect of the database it reaches. - Dialect:
SqlxBroker::with_dialectanddialect, a dialect of the service’s own.
§Tooling
- AsyncAPI: the server and the channels
the generated document names (feature
asyncapi). - Testing: the in-process mode
TestAppconnects the broker through (featuretesting), and Live on the real clock.
§Outbox
- Transactional outbox:
outbox, what a service publishes tracked until a consumer processed it. - Record:
OutboxandOutboxTable, the outbox table. - Registry:
outbox!,Outbox::trackandOutbox::register. - Tracking:
Outbox::layerandOutbox::publish_layer. - republish:
Outbox::republishat startup. - wrap:
Outbox::wrap, a publisher outside the pipeline. - set_pool:
Outbox::set_poolinside#[ruststream::app]. - Statements:
custom(..), the record’s events in the service’s own SQL. - Interplay: the outbox beside the inbox.
- Switch:
RUSTSTREAM_SQLX_OUTBOX=onin test builds. - OutboxError:
OutboxError.
§Cargo features
inbox turns the broker on and outbox the outbox, each independently of the other. postgres,
mysql (MySQL and MariaDB) and sqlite add a built-in dialect and its sqlx driver, and any an
AnyPool served by the dialect of the database it reaches. chrono or time add the time
columns, and json a headers column. testing adds the broker’s in-process mode for TestApp
and the outbox’s test switch, and asyncapi what a subscription adds to the generated document.
Re-exports§
pub use outbox::Outbox;pub use outbox::OutboxDatabase;pub use outbox::OutboxRow;pub use outbox::OutboxSpec;pub use outbox::OutboxTable;pub use ruststream_sqlx_dialect as dialect;
Modules§
- keys
- What a handler reads off the delivery it handles, through
Ctx<Key>. What a handler reads off the delivery it handles, throughCtx<Key>. - outbox
- The transactional outbox: what a service publishes is recorded in its own table until a consumer has processed it, and what was not processed is published again at startup.
- prelude
- The one glob a service on the inbox imports: the framework’s prelude and the inbox’s own names.
- spec
- The settings of a queue table described by hand, as types.
Macros§
- outbox
- Registers outbox records under the names they track, and returns the registry.
Structs§
- BuiltIn
- The dialect built into the crate for the database
DB: whatSqlxBroker::newbuilds its statements with. - Closed
Sqlx Broker - The broker after
shutdown: what it did to the advisory locks its deliveries held. - Connected
Sqlx Broker - The connected inbox broker: the typed witness that the database answered.
- Database
Clock - The database’s own clock:
#[inbox(clock = DatabaseClock)]makes the statements read it (statement_timestamp()on Postgres,UTC_TIMESTAMP(6)on MySQL) instead of binding the host’s time. - Inbox
Delivery - One claimed row in a handler’s hands.
- Inbox
Queue - A subscription to a queue table: the rows of
Rowthat the name addresses. - Inbox
Spec - The description of a queue table, with its settings as types.
- Inbox
Subscriber - The subscriber an
InboxQueueopens: a stream of deliveries claimed from the table. - Inbox
Threads - The dedicated threads of an
InboxQueuesubscription, and the connections each one opens for its handlers’ queries: whatInboxSettings::on_threadsmounts. - Named
Delivery - A delivery of a by-name subscription: a row of the table its name’s route leads to.
- Named
Subscriber - The subscriber a by-name subscription opens: the claim loop of the table its name’s route leads to.
- Plain
- A subscription whose handler leaves the delivery’s transaction alone: the default mode.
- Repository
- The typed publish policy of a row with
Publish: a message published through it becomes a row ofRow’s table. - Repository
Publisher - The live form of a
Repository: publishes intoRow’s table through itsPublish. - Routed
- The broker’s default publish policy: a publish goes where the route table leads its name.
- Routed
Publisher - The live form of
Routed: publishes into the table the route table leads each name to. - RowBatch
- The rows one claim of a table in row mode took, which a batch handler borrows as one slice,
&[Row], and the claims that settle them. - Sqlx
Broker - The inbox broker: task queues in the service’s own tables, served through a sqlx pool.
- System
Clock - The host’s system clock: what a table reads unless it names another.
- Transactional
- A subscription whose handler writes through the delivery’s transaction, which acknowledgement commits.
- Tx
- The delivery’s transaction, lent to the handler: it dereferences to the connection the transaction runs on, and goes back to the delivery when the handler ends.
Enums§
- Named
Time - A time a by-name subscription binds, in the type its column holds: the current time, a delayed retry’s, or in the lease form a lease’s expiry.
- Sqlx
Broker Error - What can go wrong in the inbox broker.
Traits§
- Ack
- Acknowledges a row: by default it deletes the row, or sets
processed_at. - Attempt
Column - The type of a field playing
attempt: the delivery’s redelivery count. - Attempt
Row - The row’s attempt, which a table described by hand reads off its row where its description
sets
InboxSpec::attempt. - Built
InDialect - A database whose dialect is built into this crate, so
SqlxBroker::newneeds no dialect of the service’s own. - ByName
- Subscriptions by name on the database
DB, as a feature of a dialect: the headers and the times their rows hold, which no struct of the service’s own names. - Claim
- Claims up to
limitrows of the queuequeueand returns their ids, inside the claim’s transaction. - Clock
- A clock on the host: what “now” is for the statements that bind it.
- Dead
Letter - Moves a row whose attempts are spent to
destination: by default it moves the row to that group (on a table with agroupcolumn) or into that table. - Discard
- Drops a row: by default it deletes the row, or sets
processed_at. - Extend
- Extends the lease on a row: writes
untilintolocked_untilwhile the row still holdsheld, and says whether it did. - Fetch
- Reads the rows of claimed ids, on the claim’s connection.
- Header
Column - The type of a field playing
headers: what the delivery’s header map holds. - Header
Field - A value a field of a headers struct holds, as the header the delivery’s header map carries.
- Header
Fields - Header fields of the row: where its description sets
InboxSpec::header_fields, the delivery builds its header map from them on its first read. - Header
Row - A record or a row whose headers live in one column: the field holding the column, which the
table’s description names with its
headerssetter. - Inbox
Headers - A struct that describes a queue table whose message is assembled from it: the mechanics a
subscription runs the queue by, and the service’s own headers.
#[derive(InboxHeaders)]implements it. - Inbox
Row - A struct that describes a queue table;
#[derive(Inbox)]implements it. - Inbox
Settings - The inbox’s own mount-site steps, on a
#[subscriber]definition and on its settings builder. - Inbox
Table - A queue table described by hand.
- Insert
- Inserts the row: every field except the
generatedones, the queue’s mechanics included, so a delayed task is a row whoseretry_afterlies ahead. - KeyColumn
- The type of a field playing
partition_key: the bytes the delivery’s key lends. - KeyRow
- The row’s partition key, which a table described by hand reads off its row where its
description sets
InboxSpec::partition_key. - Lease
Row - A queue row taken by lease: its struct has a
#[field(locked_until)]field. - Lock
- Takes the advisory lock on a row’s key for the session that will hold its delivery, without
waiting: the
lockevent of the advisory lock form. - Notifies
- A database whose sessions notify each other: a broker on it may
listen_notify. - Payload
Row - A queue row that carries its message as bytes: payload mode.
- Publish
- Writes a published message into the table: the name, the bytes and the headers reach the service’s SQL, which lays them out in its columns.
- Queue
Database - A sqlx database the inbox runs on: one that binds and reads the values the queue’s own statements use, runs statements on a connection, and reports the rows a statement changed.
- Queue
Time - A time a queue column holds:
retry_after,processed_atorlocked_until. - Retry
- Releases a row for another attempt at once: by default it counts the attempt, or leaves the row for the rollback to release; in the lease form it clears the lease.
- Retry
After - Releases a row for another attempt after
delay: by default it setsretry_after. - Time
Column - The type of a field playing a time role: a
QueueTimeor anOptionof one. - Time
Source - Where a table reads “now”: a
Clockon the host, orDatabaseClock. - Unlock
- Releases the advisory lock on a delivery’s key: the
unlockevent of the advisory lock form, which a table names as its own besideLock.
Functions§
- put_
header - Puts
fieldintoheadersundername, the way a field of a struct derivingInboxHeadersbecomes a header; a field without a value leaves the header out.
Derive Macros§
- Inbox
- Describes a queue table with a struct: writes its
InboxTableimpl, the description a table described by hand writes, and the traits its roles need. - Inbox
Headers - Describes the queue table of a message assembled from it, and implements
InboxHeadersfor the struct. - Outbox
- Describes a service’s outbox table with a struct: implements
OutboxTable, the description a record by hand writes, andHeaderRowfor aheadersfield.