SQLSpec Storage Backend¶
The SQLSpec backend (SQLSpecQueueBackend, configured via SQLSpecBackendConfig) provides high-throughput relational storage across 11 SQL dialects without requiring an ORM.
Runnable Example¶
"""Configuring SQLSpecBackendConfig with AiosqliteConfig, schema creation, and terminal_ttl."""
from pathlib import Path
from tempfile import TemporaryDirectory
from sqlspec.adapters.aiosqlite import AiosqliteConfig
from litestar_queues import QueueConfig, QueueService, WorkerConfig, task
from litestar_queues.backends.sqlspec import SQLSpecBackendConfig, SQLSpecQueueBackend, SQLSpecWorkerWakeupConfig
@task("reports.archive_monthly", queue="reports")
async def archive_monthly(month: str) -> str:
"""Archive a monthly report record in SQLSpec storage."""
return f"archived:{month}"
async def main() -> None:
"""Create the SQLSpec schema in a temporary SQLite database and execute a task."""
with TemporaryDirectory() as tmp_dir:
db_path = Path(tmp_dir) / "queues.sqlite3"
sqlspec_config = AiosqliteConfig(connection_config={"database": str(db_path)})
backend_config = SQLSpecBackendConfig(
sqlspec_config=sqlspec_config,
manage_schema=True,
terminal_ttl=86_400.0,
worker_wakeups=SQLSpecWorkerWakeupConfig(transport="polling"),
)
config = QueueConfig(
queue_backend=backend_config, execution_backend="immediate", worker=WorkerConfig(placement="external")
)
async with QueueService(config=config) as service:
backend = service.get_queue_backend()
assert isinstance(backend, SQLSpecQueueBackend)
await backend.create_schema()
handle = await service.enqueue(archive_monthly, "2026-11")
await handle.refresh()
assert handle.status == "completed"
assert handle.result == "archived:2026-11"
Supported SQLSpec Adapters & Dialects¶
litestar-queues ships dedicated SQLSpecQueueStore implementations tuned for each driver's parameter binding, locking (FOR UPDATE SKIP LOCKED), UPDATE ... RETURNING batch claiming, and JSON codecs:
| Database Engine | Supported SQLSpec Adapter Names | Claim & Locking Strategy | Default Wakeup Transport | Native Row-Level TTL |
|---|---|---|---|---|
| PostgreSQL | asyncpg, psycopg (async & sync), psqlpy |
Single-statement UPDATE ... RETURNING with FOR UPDATE SKIP LOCKED |
notify_queue |
Worker terminal_ttl sweep |
| CockroachDB | cockroach_asyncpg, cockroach_psycopg (async & sync) |
UPDATE ... RETURNING with FOR UPDATE SKIP LOCKED + conflict retry |
polling |
Native ttl_expiration_expression |
| Google Cloud Spanner | spanner |
Optimistic read-write transaction with conflict retry | polling |
Native ROW DELETION POLICY |
| Oracle Database | oracledb (async & sync) |
FOR UPDATE SKIP LOCKED cursor claim |
polling (aq / txeventq optional) |
Worker terminal_ttl sweep |
| DuckDB | duckdb |
Atomic statement claim | poll_queue |
Worker terminal_ttl sweep |
| SQLite | aiosqlite, sqlite, adbc (dialect="sqlite") |
Single-statement UPDATE ... RETURNING |
polling |
Worker terminal_ttl sweep |
| MySQL / MariaDB | asyncmy, aiomysql, pymysql, mysqlconnector (async & sync) |
FOR UPDATE SKIP LOCKED batch claim |
polling |
Worker terminal_ttl sweep |
| Microsoft SQL Server | pymssql, mssql_python, arrow_odbc |
UPDLOCK, READPAST, ROWLOCK batch claim |
polling |
Worker terminal_ttl sweep |
Schema Management & Migrations¶
The SQLSpec queue schema (0001_create_queue_tasks) provisions four tables:
queue_task(override viaqueue_table_name): Active and terminal task records, payload JSON, lease timestamps, and retry fences.queue_task_event_history(override viaevent_history_table_name): Durable lifecycle and custom progress events whenQueueEventsConfig(history=EventHistoryConfig(...))is active.queue_maintenance(override viamaintenance_table_name): Distributed maintenance lease coordination rows.queue_task_reservation(override viatask_reservation_table_name): Permanentunique_until="forever"identity reservations (TaskReservation).
Automatic vs Externally Managed Schemas¶
- SQLSpec Migration Integration: When
SQLSpecBackendConfigis passed toQueueConfig,QueuePlugininvokesSQLSpecBackendConfig.configure_migrations()(configure_queue_migration_extensionandconfigure_events_migration_extension) sosqlspecCLI migrations automatically include the queue and event-queue migration directories. - Programmatic Schema Creation (
create_schema()): Callawait backend.create_schema()in tests, bootstrap scripts, or Google Cloud Spanner administrative DDL setup whenmanage_schema=True(the default). - Adopter-Managed Schemas (
manage_schema=False): Setmanage_schema=Falsewhen your organization manages all DDL through external migration pipelines. Whenmanage_schema=False,litestar-queuesnever executesCREATE TABLEorDROP TABLEstatements and supports custom column mappings viacolumn_mapandnative_json_columns.
Native Row-Level Terminal TTL & Worker Sweeps¶
Setting SQLSpecBackendConfig(terminal_ttl=86_400.0) configures retention for finished records (completed, failed, cancelled, expired) in two complementary ways:
- Database-Native Row Expiration (CockroachDB & Google Cloud Spanner):
- CockroachDB:
create_statements()and migration0001_create_queue_tasksattach attl_expiration_expressionstorage parameter computed fromcompleted_at + INTERVAL '<N> seconds'so only rows with a non-nullcompleted_atin terminal status expire automatically in the background. - Google Cloud Spanner:
create_statements()attachesROW DELETION POLICY (OLDER_THAN(completed_at, INTERVAL <N> DAY))(roundingterminal_ttlup to whole days) because Spanner only deletes rows wherecompleted_at IS NOT NULL. - In-Worker Periodic Cleanup (All SQL Dialects):
- When
WorkerConfig.terminal_retentionis left asNone, workers automatically inheritSQLSpecBackendConfig.terminal_ttland run periodiccleanup_terminal()sweeps everyWorkerConfig.terminal_check_interval(300.0s).
Worker Wakeup Transports¶
Configure SQLSpecWorkerWakeupConfig on SQLSpecBackendConfig.worker_wakeups to control how workers learn about newly enqueued tasks:
| Transport | Supported Dialects | Durable Queue Table Required | Behavior |
|---|---|---|---|
notify_queue |
PostgreSQL (asyncpg, psycopg, psqlpy) |
Yes (sqlspec_event_queue) |
Default on PostgreSQL. Combines instant PostgreSQL LISTEN/NOTIFY push wakeups with a durable event-queue table fallback across reconnects |
notify |
PostgreSQL (asyncpg, psycopg, psqlpy) |
No | Transient PostgreSQL LISTEN/NOTIFY push wakeups without querying or provisioning sqlspec_event_queue (ideal when manage_schema=False) |
poll_queue |
All SQLSpec dialects (default on duckdb) |
Yes (sqlspec_event_queue) |
Polls the SQLSpec durable event-queue table for wakeup hints |
aq / txeventq |
Oracle (oracledb) |
Oracle AQ / TxEventQ queues | Native Oracle Advanced Queuing / Transactional Event Queues wakeup delivery |
polling |
All SQLSpec dialects | No | Disables event-channel wakeups; workers rely on adaptive interval polling (WorkerConfig.poll_interval .. poll_backoff_max) |
Passing worker_wakeups=None on SQLSpecBackendConfig is equivalent to SQLSpecWorkerWakeupConfig(transport="polling"). When notify_queue or poll_queue is selected, SQLSpecQueueBackend.open() validates at startup that the event-queue table exists and raises a descriptive QueueConfigurationError if migrations have not been applied.
Dedicated Heartbeat Connection Pool¶
Under heavy concurrency, long-running query transactions can contend with periodic worker heartbeat writes. Pass a separate SQLSpecStoreConfig with a small dedicated pool to SQLSpecBackendConfig.heartbeat_pool_config to isolate touch_heartbeats() traffic from normal enqueue and claim queries:
from litestar_queues.backends.sqlspec import SQLSpecBackendConfig
from sqlspec.adapters.asyncpg import AsyncpgConfig
main_pool = AsyncpgConfig(connection_config={"dsn": "postgresql://app:secret@db:5432/app", "min_size": 4, "max_size": 20})
heartbeat_pool = AsyncpgConfig(connection_config={"dsn": "postgresql://app:secret@db:5432/app", "min_size": 1, "max_size": 2})
backend_config = SQLSpecBackendConfig(
sqlspec_config=main_pool,
heartbeat_pool_config=heartbeat_pool,
)
Third-Party SQLSpec Adapter & Store Resolution¶
When using an external or custom SQLSpec adapter package outside sqlspec.adapters.*, litestar-queues resolves the adapter name and store classes through class attributes or sqlspec.utils.adapters helpers (resolve_adapter_name and resolve_extension_store_class) without requiring changes to internal adapter registries:
adapter_name: ClassVar[str]: Declares the canonical adapter identifier on the custom SQLSpec config class (or resolves it from the base class MRO).queue_store_class: ClassVar[type[SQLSpecQueueStore]]: Pointscreate_queue_store()directly to your customSQLSpecQueueStoresubclass.events_store_class: ClassVar[type[BaseEventQueueStore]]: Points SQLSpec's durable event channel to your custom event-queue store class when usingnotify_queueorpoll_queue.
"""Declaring adapter_name and queue_store_class on a third-party SQLSpec adapter configuration."""
from typing import ClassVar
from sqlspec.adapters.aiosqlite import AiosqliteConfig
from litestar_queues.backends.sqlspec import SQLSpecBackendConfig, SQLSpecQueueBackend
from litestar_queues.backends.sqlspec.stores.aiosqlite import AiosqliteQueueStore
from litestar_queues.backends.sqlspec.stores.base import SQLSpecQueueStore
from litestar_queues.backends.sqlspec.stores.factory import create_queue_store
class CustomSqliteQueueStore(AiosqliteQueueStore):
"""Custom queue store subclass for a third-party or wrapped SQLite adapter."""
__slots__ = ()
class CustomSqliteAdapterConfig(AiosqliteConfig):
"""Third-party SQLSpec adapter configuration advertising explicit store hooks."""
adapter_name: ClassVar[str] = "custom_sqlite"
queue_store_class: ClassVar[type[SQLSpecQueueStore]] = CustomSqliteQueueStore
async def main() -> None:
"""Verify create_queue_store and SQLSpecQueueBackend resolve queue_store_class directly."""
adapter_config = CustomSqliteAdapterConfig(connection_config={"database": ":memory:"})
store = create_queue_store(adapter_config, terminal_ttl=3600.0)
assert isinstance(store, CustomSqliteQueueStore)
backend = SQLSpecQueueBackend(
backend_config=SQLSpecBackendConfig(sqlspec_config=adapter_config, terminal_ttl=3600.0)
)
assert isinstance(backend, SQLSpecQueueBackend)