Events and Message Queues¶
sqlspec.extensions.events provides synchronous (SyncEventChannel) and asynchronous (AsyncEventChannel) pub/sub and competing-consumer event channels backed by your existing database configuration, supporting native PostgreSQL LISTEN/NOTIFY, durable hybrid notify_queue wakeups, Oracle Advanced Queuing (aq and txeventq), and portable table-backed polling (poll_queue) across 19 adapters.
Choosing a Transport Backend¶
Set extension_config["events"]["backend"] according to your delivery and durability requirements:
| Backend | Supported Adapters | Durability | Delivery Model | Default On |
|---|---|---|---|---|
"notify" |
asyncpg, psycopg, psqlpy |
Transient (in-memory wire notification) | Fire-and-forget broadcast wakeup (<= 7,999 UTF-8 bytes) |
PostgreSQL (asyncpg, psycopg, psqlpy) |
"notify_queue" |
asyncpg, psycopg, psqlpy |
Durable table row + native NOTIFY wakeup |
Competing-consumer queue with instant push wakeup and periodic poll reconciliation | Opt-in on PostgreSQL |
"poll_queue" |
All 19 event-capable adapters | Durable table row (sqlspec_event_queue) |
Competing-consumer queue with leased claims, ack/nack, and retry backoff |
All non-PostgreSQL adapters |
"aq" |
oracledb (sync & async) |
Durable Oracle Advanced Queuing table | Native DBMS_AQ enqueue/dequeue in python-oracledb Thin mode |
Opt-in on Oracle 21c+ / 26ai |
"txeventq" |
oracledb (sync & async) |
Durable Oracle Transactional Event Queue | Native high-throughput partitioned TxEventQ in Thin mode | Opt-in on Oracle 21c+ / 26ai |
Configuring EventsConfig and Publishing Events¶
Configure the events extension on your database config, provision the queue table when using a durable table backend (notify_queue or poll_queue), and publish/consume events through spec.event_channel(config):
import asyncio
from sqlspec import SQLSpec
from sqlspec.adapters.aiosqlite import AiosqliteConfig
async def main() -> None:
spec = SQLSpec()
config = spec.add_config(
AiosqliteConfig(
connection_config={"database": ":memory:"},
extension_config={
"events": {
"backend": "poll_queue",
"queue_table": "app_events",
"lease_seconds": 30,
"retention_seconds": 3600,
"event_poll_interval": 0.25,
}
},
)
)
try:
channel = spec.event_channel(config)
await channel.store.create_table()
event_id = await channel.publish(
"orders.created", {"order_id": 101, "status": "paid"}, metadata={"source": "checkout"}
)
message = await channel.dequeue("orders.created", poll_interval=0.1)
if message is not None:
assert message.event_id == event_id
await channel.ack(message.event_id)
finally:
await config.close_pool()
asyncio.run(main())
Batch Publication (publish_many)¶
Both AsyncEventChannel and SyncEventChannel provide publish_many(events) where each item is a (channel, payload, metadata) tuple. On poll_queue and notify_queue, SQLSpec bulk-inserts all rows in a single publisher transaction; on notify_queue, it emits a single compact wakeup marker (marker_id + batch_size) per channel in the same transaction so consumers drain the batch immediately.
PostgreSQL ListenerHub and Pooler Routing¶
On PostgreSQL (asyncpg, psycopg, psqlpy), notify and notify_queue manage a single persistent LISTEN connection per backend instance via ListenerHub:
- Minimum Pool Size: Because the
ListenerHubholds one dedicated connection forLISTENwhile publishers use pooled sessions, configuremax_size >= 2(ormax_db_pool_size >= 2onpsqlpy). - PgBouncer / AlloyDB Auth Proxy (
pooler_mode+direct_dsn): Transaction and statement poolers multiplex backend connections and cannot hold session-scopedLISTENregistrations. Whenpooler_modeis"transaction"or"statement"anddirect_dsnis configured,ListenerHubautomatically routes its dedicatedLISTENconnection throughconfig.derive_direct_config()while normal application queries continue using the pooled endpoint:
from sqlspec.adapters.asyncpg import AsyncpgConfig
from sqlspec.adapters.asyncpg.events import AsyncpgEventsConfig
config = AsyncpgConfig(
connection_config={"dsn": "postgresql://pgbouncer:6432/app", "min_size": 2, "max_size": 20},
driver_features={"pooler_mode": "transaction", "direct_dsn": "postgresql://postgres-primary:5432/app"},
extension_config={
"events": AsyncpgEventsConfig(
backend="notify_queue", event_poll_interval=1.0, listener_queue_capacity=256, fillfactor=80
)
},
)
Checking PostgreSQL NOTIFY Payload Budgets (7,999 Bytes)¶
PostgreSQL caps NOTIFY payloads at 7,999 UTF-8 bytes across the full encoded event envelope (event_id, payload, metadata, and timestamp). Use fits_notify_payload and measure_notify_payload before publishing to a transient notify channel, or use notify_queue so arbitrary-sized payloads live in the durable queue table while NOTIFY carries only a compact wakeup marker.
Adapter-Specific Storage Tuning¶
Several adapters export typed EventsConfig subclasses under sqlspec.adapters.<adapter>.events for storage-level optimizations:
- PostgreSQL (
AsyncpgEventsConfig,PsycopgEventsConfig,PsqlpyEventsConfig):fillfactor(10–100) andlistener_queue_capacity. - SQLite (
SqliteEventsConfig,AiosqliteEventsConfig): PRAGMA profile and overrides for high-concurrency WAL queue polling. - Cloud Spanner (
EventsConfigonSpannerSyncConfig/SpannerAsyncConfig):shard_count > 1hash-shard prefixing to prevent queue-head hotspots, snapshot-read candidate polling, and nativeROW DELETION POLICYTTL cleanup (cleanup_on_ack=False). - BigQuery (
BigQueryEventsConfig):partitioning=True(DATE(available_at)partitioning),partition_expiration_days, andrequire_partition_filter. - Oracle (
OracleEventsConfig):backend="aq"or"txeventq",aq_queue("SQLSPEC_EVT_{channel}"per-channel routing),aq_visibility("DEQ_ON_COMMIT"/"DEQ_IMMEDIATE"),OracleEventsPartitionConfig, andOracleEventsCompressionConfig.
Recap¶
- Choose
notify_queueon PostgreSQL for instant wakeup with durable table storage,aq/txeventqon Oracle for native broker queues, andpoll_queuefor zero-dependency portable queues across all 19 adapters. - Set
direct_dsnalongsidepooler_mode="transaction"on PostgreSQL soListenerHubbypasses transaction poolers automatically. - See the Events Extension Reference for
AsyncEventChannel,SyncEventChannel,EventMessage, andEventsConfigdetails.