Skip to content

Schema and procedure catalogue

Every log table with its columns and purpose, and every log-engine stored procedure with its signature and role.

Updated View as Markdown

Everything here lives in the PostgreSQL schema queen, created by server/sql/schema.sql. That file plus exactly 33 files under server/sql/procedures/ (001033) are embedded in the binary with include_str! and applied at every boot in lexical order, one statement per transaction, under session advisory lock 778120010. All of it is idempotent (CREATE OR REPLACE, IF NOT EXISTS, ADD COLUMN IF NOT EXISTS), so re-apply is a no-op, and QUEEN_APPLY_SCHEMA=0 skips the step entirely, which lets a privileged role pre-apply the DDL so the broker can run as a low-privilege user.

The apply only creates and adds. There are no teardown files and no migration blocks: a deployment starts from an empty database, and schema.sql’s CREATE TABLEs declare the shape directly. Where a later release needed a column on a table an earlier one already declared, the file spells the step out as ADD COLUMN IF NOT EXISTS beside the CREATE, so a database on the same line picks it up at the next boot. That is not an upgrade path: pointing this build at a database written by a different schema is still unsupported, and a new version means a fresh database. See Retired objects for the history.

The engine needs CREATE SCHEMA and no extensions, with one floor worth naming: the cgm_identity_uk index below uses NULLS NOT DISTINCT, which needs PostgreSQL 15 or newer.

The file map: 001_log_schema and 002_streams_schema are the DDL, 003_log_push through 007_log_streams are the engine’s write, read, ack, maintenance and streaming paths, 008/009 are the streams registry calls, 010_log_admin and 011_log_stats are the log-native admin and stats surface, and 012023 (minus 020) are the shared management plane: configure, queue deletion, consumer groups, status, messages, traces, stats readers, metrics, Prometheus. 020_log_partition_counters is a special case: it only attaches triggers, and its position after both 001 (the table) and 019 (the trigger functions) is the point of it. 024027 are the KV and timer surfaces, applied on every boot like the rest: 024 and 025 carry their tables and their request-path procedures, 026 the sweeper’s two slow phases, and 027 the read-only quota poll every broker runs whether or not it sweeps. 028031 are the maintenance and tenancy surfaces the later releases added: 028 the retained-bytes slow lane, 029 the durable per-task lease table that elects one replica per maintenance cycle, 030 the whole database footprint of the ephemeral queue class (its declarations and quota, never its messages), and 031 the tenant purge. The ordering is not cosmetic anywhere: SQL-language function bodies resolve tables at creation time, so the DDL files must land before every procedure body that names them.

That last rule is why 024 and 025 are last rather than early. 005_log_ack applies before them and calls both of their apply procedures, which works only because the transaction wire’s body is plpgsql and resolves names at run time. In the other direction it is a free mechanical guard: nothing applied before 024 can name those tables from a LANGUAGE sql body without failing the boot, which is what keeps log_partition_dead_v1 in 006 from ever growing a veto leg on them, and with it the per-partition scan it fronts.

Queue identity: queen.queues (schema.sql)

One row per queue, and the single queue identity in the system: queen.log_partitions.queue_id references queues(id) ON DELETE CASCADE directly, so every partition→config join is by id. There is no separate log-engine queue table and no (tenant_id, name) bridge join between configuration and data, which makes the cross-tenant name-join hazard class structurally unrepresentable (the retention work list joins by id).

Identity is (tenant_id, name), enforced by the unique index queues_tenant_name_uk. The columns the engine reads directly:

Column Type Notes
id UUID PK. log_partitions.queue_id, consumer_watermarks.queue_id, consumer_groups_metadata.queue_id, queue_lag_metrics.queue_id and stats.queue_id all reference it ON DELETE CASCADE
tenant_id UUID default 00000000-0000-0000-0000-000000000001
name VARCHAR(255) not null; (tenant_id, name) unique
lease_time INTEGER default 60; /configure writes an explicit 300 when leaseTime is omitted on a create or a replacing call, and since 1.6.0 a merging edit keeps whatever the column holds
dedup_window_seconds INTEGER NOT NULL default 3600; 0 disables the push dedup probe
namespace, task VARCHAR(255) derived from the dotted name on implicit creation
created_at TIMESTAMPTZ

The rest of the row is the queue configuration /configure writes: retry limit and delay, DLQ flags, delayed_processing, window_buffer, the retention policy columns, encryption, max_wait_time_seconds, max_queue_size, min_pop_wait_time. See Queue options.

Why lease_time defaults to 60 and why there is no storage column are both explained in the storage model; the storage: "segments" wire literal is hard-coded in 012_configure, 010_log_admin and 011_log_stats.

A queue row is created by the first push (003_log_push provisioning), by /configure (012_configure), by the transaction wire (005_log_ack), by a streams sink (007_log_streams), and by a wildcard pop or subscription registration on a queue that does not exist yet (004_log_pop). That last one is a deliberate widening: the pop writes a durable queue-scoped consumer_groups_metadata row, whose FK needs the queue row, and subscribe-before-first-push is legitimate, so the FK must not turn an early subscriber into an error. The flip side: a consumer typo now creates a queue row, where it previously left only an orphan watermark.

Queue deletion is one call: queen.delete_queue_v1 (013_analytics) resolves the id, deletes the queue’s log_txns and log_dlq rows explicitly (both FK-less by design, the cascade cannot reach them), then deletes the queues row and lets the FK cascade take log_partitionslog_segments/log_consumers, consumer_watermarks, the queue-scoped consumer_groups_metadata rows, queue_lag_metrics and stats. Previously this was two non-atomic steps (the SQL delete plus a broker-side log-table teardown).

Log tables

queen.log_partitions (001_log_schema)

One row per partition, and the per-partition write serializer; the columns (the last_offset allocator and the two retention watermarks) are documented in the storage model.

PRIMARY KEY (id) and UNIQUE (queue_id, name); queue_id references queen.queues(id), cascading. Index: idx_log_partitions_queue_write (queue_id, last_write_at), the wildcard candidate scan’s hot filter. Storage parameters: fillfactor = 70, autovacuum_vacuum_scale_factor = 0, autovacuum_vacuum_threshold = 500, autovacuum_vacuum_cost_delay = 0, vacuum_truncate = off.

The row is deleted only by the cleanup phase, and only once the partition is empty and both its timestamps are older than PARTITION_CLEANUP_DAYS. Deleting it cascades to log_segments (none, by definition) and log_consumers, so the group cursors go with it; a later push recreates the partition under a new id, allocating from last_offset = -1 again. See Retention internals.

queen.log_segments (001_log_schema)

The messages, packed as length-prefixed zstd-compressed frame blobs; the columns are documented in the storage model.

PRIMARY KEY (partition_id, base_offset) and no secondary indexes: the PK is also the pop path. blob is SET STORAGE EXTERNAL. Autovacuum: scale factor 0.1, insert scale factor 0.3, cost limit 4000, cost delay 0, with the same values mirrored onto the TOAST table.

queen.log_txns (001_log_schema)

The hash sidecar, written on every push whether deduplication is on or off; the columns, and the obligation the missing foreign key puts on the two partition deleters, are documented in the storage model.

PRIMARY KEY (partition_id, base_offset), no secondary indexes, and deliberately no foreign key on partition_id. Autovacuum: scale factor 0.1, insert scale factor 0.3, cost delay 0.

queen.log_consumers (001_log_schema)

Coordination state per (partition, consumer_group): the cursor, the lease and the retry budget. The columns are documented in the storage model.

PRIMARY KEY (partition_id, consumer_group); partition_id references log_partitions(id), cascading. Storage parameters: fillfactor = 50, autovacuum_vacuum_scale_factor = 0, autovacuum_vacuum_threshold = 500, autovacuum_vacuum_cost_delay = 0, vacuum_truncate = off.

queen.log_dlq (005_log_ack)

Dead-lettered frames, as snapshots.

Column Type Notes
id UUID PK
partition_id UUID no FK: the snapshot must outlive segment retention
consumer_group TEXT
"offset" BIGINT where the poison frame lived; quoted, offset is reserved
message_id UUID
transaction_id TEXT
payload JSONB the snapshot itself
error TEXT failure reason from the nack
retry_count INTEGER retries consumed when it was dead-lettered
failed_at TIMESTAMPTZ default now()

Index: idx_log_dlq_partition_failed_at (partition_id, failed_at DESC).

Nothing in the retention cycle purges this table. Because these rows have no foreign key either, a partition that still holds one is never reclaimed by the cleanup phase: dropping the partition would leave the snapshots unreachable rather than clean them up, since every DLQ reader joins through log_partitions. (delete_queue_v1 does clear them, explicitly, as part of deleting the queue.)

Shared tables the log engine uses

These are created by schema.sql (and 019_worker_metrics for the metrics tables) and are not engine-specific. Since the queue-identity merge they key on queen.queues(id) wherever they used to carry a queue name, so a deleted queue cascades its shared rows away.

Table Role under the log engine
queen.consumer_groups_metadata durable subscription registration (mode + timestamp) and the group-first-contact seed marker. queue_id UUID NULL FK to queues(id), cascading, with NULL only for namespace/task discovery rows, which name no queue and keep their own tenant_id for scoping. Uniqueness is cgm_identity_uk (tenant_id, consumer_group, queue_id, partition_name, namespace, task) NULLS NOT DISTINCT (PG ≥ 15, so two discovery rows with NULL queue ids collide instead of duplicating)
queen.consumer_watermarks the per-(queue, group) empty-scan watermark and its verification time. PRIMARY KEY (queue_id, consumer_group), FK cascade; tenant scoping is inherited from the queue row
queen.stats output of the stats reconciler: queue, namespace, task and system rows. queue_id FK to queues(id), cascading; partition_id carries a log_partitions id with no FK
queen.retention_history one audit row per retention step that deleted something, plus one __partition_deleted__ row per partition the cleanup phase reclaimed. The __%__ prefix keeps partition events out of the messages-evicted timeseries. partition_id has no FK: the audit row is written by the very step that deletes the partition
queen.message_traces, queen.message_trace_names tracing; partition_id and message_id carry log-engine ids with no FK, because a trace must outlive the segment it describes
queen.system_state the two maintenance flags, as {"enabled": bool} JSONB rows
queen.maintenance_leases the durable schedule for the cluster-singleton loops (stats_refresh, retained_bytes, retention): one row per task with next_due_at, a lease, a fencing token and per-task run history. enabled = false pauses a task cluster-wide; the DB clock arbitrates, so pod clocks and replica counts do not shape the cadence
queen.worker_metrics, queen.worker_metrics_summary, queen.system_metrics the metrics collector’s output
queen.queue_lag_metrics per-queue ops counters and lifecycle events. queue_id UUID NOT NULL FK to queues(id), cascading; UNIQUE (bucket_time, queue_id). Readers join queues for display names; a deleted queue’s metric rows cascade away
queen_streams.queries, queen_streams.state, queen_streams.quota streaming query registry (tenant-scoped: name is unique per tenant_id), per-(query, partition, key) state, and the per-tenant streams grant with its max_queries cap. state carries no tenant column on purpose: every row is attributable through its query_id FK, whatever happened to its partition

Procedures, file by file

Every function below is in the schema queen. Where a signature ends in p_tenant UUID DEFAULT '00000000-...0001', the parameter was added last with a default so an unscoped caller lands on the default tenant, and the older signature is explicitly dropped on re-apply so no ambiguous overload survives.

001_log_schema.sql (tables)

The four log_* tables above (log_dlq lives in 005_log_ack), plus queen.hotlist_repairs and one helper.

queen.hotlist_repairs is not a log table: it is the durable announcement of a deliberate cursor move, which is the one class of pendingness the windowed reseed cannot see (nothing was written). PRIMARY KEY (tenant_id, queue_name, consumer_group), one nullable partition_name (NULL = the whole queue), a repair_at timestamp indexed for the readers and a reason. A seek and a consumer-group delete write it in their own transaction; every broker reads it on the reconcile pass and repairs the rings named there; rows older than an hour are pruned inline by the writer. Two repairs naming different partitions widen to NULL rather than one being lost. It is a publication channel, not a general lost-notification repair: an entry cleared in error, a stranded claim or a stale lease park are still the full walk’s job.

Function Role
log_unnest_hashes(p BYTEA) RETURNS TABLE (idx INT, h BYTEA) explode a 16-byte-stride blob into zero-based (index, hash) rows. IMMUTABLE pure byte slicing, so PostgreSQL inlines it into the push probe and ack resolution

003_log_push.sql (push)

Function Role
log_push_one_v1(p_queue TEXT, p_partition TEXT, p_msg_count INT, p_hashes BYTEA, p_verified BIGINT, p_blob BYTEA, p_pid UUID DEFAULT NULL, p_window INT DEFAULT NULL, p_tenant UUID DEFAULT …) RETURNS JSONB the single allocator code path. Validates the hash stride, provisions the queue and partition rows on first contact, probes before allocating under the partition row lock, allocates, inserts the segment and the log_txns row. Returns {"status":"queued","baseOffset":B,"createdAt":…} or {"status":"duplicate","dups":[{"i":k,"off":O}]}
log_push_multi_v1(p_queues TEXT[], p_partitions TEXT[], p_msg_counts INT[], p_hashes BYTEA[], p_verified INT8[], p_blobs BYTEA[], p_tenants UUID[] DEFAULT NULL) RETURNS JSONB bundle N disjoint partitions’ segments into one transaction. Alignment guards, provisioning skip, one set-based pre-lock ascending by log_partitions.id, then log_push_one_v1 per segment. Returns a JSON array in input order
log_segment_at_v1(p_pid UUID, p_off BIGINT) RETURNS TABLE (r_base BIGINT, r_end BIGINT, r_created TIMESTAMPTZ, r_blob BYTEA) fetch the segment covering an absolute offset. One descending PK probe with the covering test applied outside the LIMIT, so a miss cannot walk the partition head. Zero rows when retention removed it

log_push_one_v1 is also called by the transaction wire and the streams cycle, so there is exactly one place in the system where an offset is assigned.

004_log_pop.sql (pop)

Function Role
log_pop_v1(p_queue TEXT, p_partition TEXT, p_group TEXT, p_budget INTEGER, p_lease_seconds INTEGER, p_worker TEXT, p_auto_ack BOOLEAN DEFAULT FALSE, p_sub_mode TEXT DEFAULT 'all', p_sub_from TEXT DEFAULT '', p_skip_window_debounce BOOLEAN DEFAULT FALSE, p_tenant UUID DEFAULT …) RETURNS TABLE (r_base BIGINT, r_start_idx INTEGER, r_take INTEGER, r_msg_count INTEGER, r_created_at TIMESTAMPTZ, r_blob BYTEA) the claim core. The single-partition, wildcard and discovery variants call it once per partition; log_pop_list_v1 calls it only for a candidate on first contact that has no consumer row yet, and claims the rest in one batched pass. Visibility gates, first-contact subscription seeding, claim-first FOR UPDATE SKIP LOCKED, head probe plus forward scan, empty-partition cursor seal, then auto-ack or lease
log_pop_specific_v1(p_queue, p_partition, p_group, p_budget, p_lease_seconds, p_worker, p_auto_ack, p_sub_mode, p_sub_from, p_tenant) RETURNS JSONB the single-partition pop’s wire assembly, VOLATILE plpgsql: one log_pop_v1 claim, then the partitionId and group-scoped attempt_count resolution in a later command, whose snapshot is at least as new as anything the claim saw. The old shape (the broker’s outer statement resolving partitionId under an older snapshot than the claim’s internals) could deliver a leased batch whose every message carried partitionId "" when the pop raced the partition-creating push commit, and the client’s mandatory-partitionId ack guard then threw
log_pop_wildcard_wire_v1(p_queue, p_group, p_budget, p_lease_seconds, p_worker, p_auto_ack, p_max_partitions, p_sub_mode, p_sub_from, p_tenant) RETURNS JSONB SQL candidate scan across a queue’s partitions; blobs base64-encoded inside the JSON. Carries the group-first-contact bulk seed, the empty-scan watermark maintenance, subscribe-before-first-push queue provisioning, and each claimed partition’s post-lease deliveryAttempt
log_pop_wildcard_bin_v1(… same args …) RETURNS TABLE(meta JSONB, blobs BYTEA[]) identical claim logic with blobs as a native bytea[] out of band: no encode() on the server, no base64 decode on the broker. The default wildcard path; metadata includes each partition’s group-scoped deliveryAttempt
log_pop_discover_wire_v1(p_namespace TEXT, p_task TEXT, p_group, p_budget, p_lease_seconds, p_worker, p_auto_ack, p_max_partitions, p_sub_mode, p_sub_from, p_tenant) RETURNS JSONB namespace/task discovery pop across matching queues, each partition leased with its own queue’s lease_time and carrying that group’s deliveryAttempt
log_has_pending_v1(p_queue TEXT, p_group TEXT, p_tenant UUID DEFAULT …) RETURNS BOOLEAN cheap indexed superset probe: does any partition have last_offset > COALESCE(committed, -1). Coarse on retention gaps by contract; the pop resolves the truth under the row lock
log_pop_list_v1(p_queue, p_group, p_partitions TEXT[], p_budget, p_lease_seconds, p_worker, p_auto_ack, p_max_partitions, p_sub_mode, p_sub_from, p_skip_window BOOLEAN DEFAULT FALSE, p_tenant) RETURNS TABLE(meta JSONB, blobs BYTEA[], states JSONB) the hot-list serve path: claim from a caller-supplied candidate list in one batched pass (one FOR UPDATE ... SKIP LOCKED claim over every candidate, one no-lock probe of what it did not take, one segment-metadata read with no blob column, one lease UPDATE, one blob fetch: about six statements whatever the candidate count), return each partition’s atomically updated deliveryAttempt, and return a tri-state verdict per evaluated candidate: took (with lastOff), empty, or leased (with until)
log_hotlist_reseed_v1(p_queue TEXT, p_group TEXT, p_after_id UUID, p_limit INTEGER, p_tenant UUID DEFAULT …) RETURNS TABLE(r_id UUID, r_name TEXT) keyset-paginated enumeration of probably-pending partitions in id order, for ring reseeding. Includes lease-held partitions; pass the nil UUID to start a walk
log_hotlist_reseed_window_v1(p_queue TEXT, p_group TEXT, p_after_write TIMESTAMPTZ, p_after_id UUID, p_limit INTEGER, p_window_ms BIGINT, p_cutoff TIMESTAMPTZ DEFAULT NULL, p_tenant UUID DEFAULT …) RETURNS TABLE(r_id UUID, r_name TEXT, r_write TIMESTAMPTZ, r_cutoff TIMESTAMPTZ) the same enumeration bounded to partitions written in the last p_window_ms, which is what the reseed floor runs between full walks. Keyset on (last_write_at, id) so the access path stays on idx_log_partitions_queue_write even under a generic plan; pass ('-infinity', nil UUID) to start a walk. p_cutoff is the lower bound the walk is pinned to: NULL means “first page, derive it from p_window_ms and return it in r_cutoff”, and later pages echo it back, so the bound cannot creep forward between pages while the cursor climbs. DROP-and-CREATE, including of 1.0.1-beta.1’s shape, which would otherwise survive as a second candidate for a defaulted call

log_pop_v1 is DROP-and-CREATE rather than CREATE OR REPLACE, because its OUT row type is part of the signature and OR REPLACE refuses return-type changes, which would brick the boot re-apply.

005_log_ack.sql (ack, lease renewal, DLQ, transactions)

Also creates queen.log_dlq (above).

Function Role
log_ack_v1(p_queue TEXT, p_partition TEXT, p_group TEXT, p_worker TEXT, p_upto BIGINT, p_ok BOOLEAN DEFAULT TRUE, p_acked_count INTEGER DEFAULT 0, p_tenant UUID DEFAULT …) RETURNS JSONB positional ack addressed by names. Locks the consumer row, validates the lease only when a worker is supplied, clamps to batch_end, advances or releases
log_ack_at_v1(p_partition_id UUID, p_group TEXT, p_worker TEXT, p_upto BIGINT, p_ok BOOLEAN DEFAULT TRUE, p_acked_count INTEGER DEFAULT 0) RETURNS JSONB the same decision procedure addressed by partition id, the ack registry’s fast path
log_ack_multi_v1(p_pids TEXT[], p_groups TEXT[], p_ends BIGINT[], p_workers TEXT[], p_counts INT[]) RETURNS JSONB N full-batch cursor advances in one transaction, for ack fusion. Rows execute ORDER BY (partition_id, group); verdicts are emitted by input ordinal
log_ack_by_hash_v1(p_partition_id UUID, p_group TEXT, p_worker TEXT, p_hashes BYTEA[], p_statuses TEXT[]) RETURNS JSONB the authoritative ack contract. One join resolves hashes to offsets; implements implicit ack, explicit-signal clamping, the retry budget, DLQ hand-off, and the noopHashes / staleHashes / unresolvedHashes honesty lists
log_renew_lease_v1(p_worker TEXT, p_seconds INTEGER) RETURNS JSONB renew every live lease held by a worker. GREATEST so it never shortens; reports the minimum expiry
log_dlq_head_v1(p_partition_id UUID, p_group TEXT, p_worker TEXT, p_off BIGINT, p_message_id UUID, p_txn TEXT, p_payload JSONB, p_error TEXT) RETURNS JSONB file the poison frame’s snapshot, advance the cursor past it, reset attempt and retry state, release the lease. Idempotent on a second call (the lease is gone)
log_transaction_wire_v1(p JSONB) RETURNS JSONB atomic push plus ack. Guards against cross-tenant partition ids, pre-locks partitions ascending by id, then acks ascending by (partition_id, group). Every failure raises, so the whole batch rolls back

get_dlq_messages_v1 used to be defined here too; it is not any more. 010_log_admin applies after this file and redefines the same signature, so the copy here was overwritten at every boot and never ran. 010_log_admin owns it now.

006_log_maintenance.sql (retention steps)

Function Role
log_retention_boundary_v1(p_partition_id UUID, p_from BIGINT, p_cutoff TIMESTAMPTZ) RETURNS BIGINT smallest base_offset >= p_from of a segment fresh enough to keep. A single forward PK walk, valid because created_at is monotone. MAX(end_offset) + 1 when everything above the watermark is stale
log_retention_step_v1(p_pid UUID, p_all_cutoff TIMESTAMPTZ, p_completed_cutoff TIMESTAMPTZ, p_max_rows INT, p_history_type TEXT DEFAULT NULL) RETURNS JSONB one bounded retention transaction for one partition. Rule 1 by time, rule 2 capped at MIN(committed) + 1, whole segments only, one ranged DELETE, watermark advance, one retention_history row. Returns {"deleted":N,"new_log_start":X,"done":bool}
log_txns_purge_step_v1(p_pid UUID, p_cutoff TIMESTAMPTZ, p_max_rows INT) RETURNS JSONB the same step pattern over log_txns and txns_start
log_evict_max_wait_step_v1(p_pid UUID, p_cutoff TIMESTAMPTZ, p_max_rows INT) RETURNS JSONB maxWaitTimeSeconds eviction, delegating to log_retention_step_v1 with rule 2 disabled and the history type max_wait_time_eviction
log_partition_dead_v1(p_pid UUID, p_cutoff TIMESTAMPTZ) RETURNS BOOLEAN the empty-partition eligibility predicate, in one place so the candidate scan and the under-lock re-check cannot drift: no segments, no log_dlq row, no queen_streams.state row, no live lease, no consumer activity since the cutoff, and both partition timestamps older than it
log_partition_cleanup_step_v1(p_cutoff TIMESTAMPTZ, p_max_rows INT) RETURNS JSONB deletes up to p_max_rows eligible partitions in one transaction: locks the batch ascending by id with SKIP LOCKED, re-checks the predicate under the locks, purges the FK-less log_txns rows explicitly, writes one __partition_deleted__ history row each, then deletes the rows (cascading to log_consumers). The only step here that is not per-partition

This file also adds idx_retention_history_executed_at (executed_at DESC), the index the retention analytics readers scan.

007_log_streams.sql (streaming)

Function Role
log_streams_cycle_v1(p_requests JSONB) RETURNS JSONB one atomic streaming-cycle commit per request element: state upserts, sink pushes through log_push_one_v1, and the source ack as a cursor advance over the leased span. A gate partial-ack walks K frames forward and retains the lease on the tail. Sink segments arrive broker-prepacked as hashesB64 plus blobB64

010_log_admin.sql (admin and observability)

Ported from the retired seg-era consumer-group, observability and traces files. The rows-engine legs those redefinitions once carried are gone. There is one engine, so every body here runs unconditionally, and the join to queen.queues on p.queue_id is itself the queue-existence guard (it supplies namespace, task, priority and tenant scoping in the same touch).

Function Role
log_delete_consumer_group_v1(p_group TEXT, p_delete_metadata BOOLEAN DEFAULT TRUE, p_tenant UUID DEFAULT …) RETURNS JSONB remove every log_consumers cursor for a group across all queues of one tenant, plus its empty-scan watermarks
log_seek_one_v1(p_partition_id UUID, p_last_offset BIGINT, p_log_start BIGINT, p_group TEXT, p_to_end BOOLEAN, p_timestamp TIMESTAMPTZ) RETURNS VOID seek one cursor: to last_offset for end, else just before the first segment at or after the timestamp (segment-granular). Releases any lease and resets retry and attempt state; creates the row if absent
log_seek_consumer_group_v1(p_group TEXT, p_queue TEXT, p_to_end BOOLEAN DEFAULT FALSE, p_timestamp TIMESTAMPTZ DEFAULT NULL, p_tenant UUID DEFAULT …) RETURNS JSONB seek every partition of a queue
log_seek_partition_v1(p_group TEXT, p_queue TEXT, p_partition TEXT, p_to_end BOOLEAN DEFAULT FALSE, p_timestamp TIMESTAMPTZ DEFAULT NULL, p_tenant UUID DEFAULT …) RETURNS JSONB seek one named partition
hotlist_repair_publish_v1(p_tenant UUID, p_queue TEXT, p_group TEXT, p_partition TEXT, p_reason TEXT) RETURNS VOID announce a cursor move in queen.hotlist_repairs for the peers to read: upsert on (tenant, queue, group), widening partition_name to NULL when a second repair names a different partition, and prune rows older than an hour in the same call. Called by both seeks and by both consumer-group deletes, inside their own transaction
get_consumer_groups_v4(p_tenant UUID DEFAULT …) RETURNS JSONB consumer-group listing
list_messages_v1(p_filters JSONB DEFAULT '{}') RETURNS JSONB message browsing; a segment seq carries base_offset and frameIdx carries offset - base_offset
get_dlq_messages_v1(p_filters JSONB DEFAULT '{}') RETURNS JSONB DLQ browsing over log_dlq; payload comes from the snapshot, createdAt is failed_at (per-frame enqueue time is not recoverable from an opaque blob), producerSub is null
record_trace_v1(p_data JSONB) RETURNS JSONB trace recording (this file owns the redefinition; 017_traces no longer defines it)
get_lagging_partitions_v1(p_min_lag_seconds INTEGER DEFAULT 0, p_tenant UUID DEFAULT …) RETURNS JSONB lagging-partition report
get_consumer_group_details_v1(p_consumer_group TEXT, p_tenant UUID DEFAULT …) RETURNS JSONB per-group detail
get_dlq_signatures_v1(p_filters JSONB DEFAULT '{}') RETURNS JSONB fold the newest DLQ rows of one queue into error-message shapes: identifiers, dates and integers are replaced by placeholders and the result is truncated to 120 characters, so near-identical failures collapse into one row. Reads error_message only; the payload is touched by octet_length alone, never returned

Three procedures from the retired observability file were deliberately not ported, because they have no Rust call site: get_queue_messages_v1, has_pending_messages and get_prometheus_metrics_v1.

011_log_stats.sql (stats and analytics)

Function Role
log_oldest_pending_at_v1(p_pid UUID, p_wanted BIGINT) RETURNS TIMESTAMPTZ created_at of the segment holding an offset, or of the next existing segment when it fell into a retention gap. Two bounded PK probes, never a filtered backward scan
log_refresh_all_stats_v1() RETURNS JSONB recompute queen.stats queue rows from the log tables by watermark arithmetic, then reuse the existing namespace, task and system rollups and correct the system partition count. Returns a summary labelled engine: "segments", a literal wire-compat telemetry label, since no table carries a storage column
get_queue_detail_v2(p_queue_name TEXT, p_tenant UUID DEFAULT …) RETURNS JSONB log-native queue detail (this file owns it; 018_stats no longer defines it). cursor.batchesConsumed is 0 and lastActivity is MAX(lease_acquired_at)
get_queue_v2(p_queue_name TEXT, p_tenant UUID DEFAULT …) RETURNS JSONB log-native queue summary, same watermark arithmetic, plus an options object carrying all 21 configuration keys in the spellings configure_queue_v1 parses, so an editor can prefill and round-trip. failed is NULL, not 0: there is no per-partition failure counter
get_analytics_v1(p_filters JSONB DEFAULT '{}') RETURNS JSONB the message-volume series, bucketing log_segments by created_at and attributing each segment’s frame count to its creation bucket. Deliberately O(segments in range), since it is a per-request dashboard read with a time filter, not the refresh loop
log_queue_message_stats_v1(p_queue TEXT, p_tenant UUID DEFAULT …) RETURNS TABLE (segments BIGINT, messages BIGINT) per-queue counters for queue detail: messages by watermark arithmetic, segments by an index-only count
log_queue_stats_all_v1(p_tenant UUID DEFAULT …) RETURNS TABLE (queue_name TEXT, partitions BIGINT, segments BIGINT, messages BIGINT) the same accounting broker-wide, for queue listing (the live view, since queen.stats can lag one refresh cadence). Segment counts are pre-aggregated per partition before the queue join, or the watermark row would fan out once per segment and over-count
log_queue_depth_v1(p_queue TEXT, p_group TEXT DEFAULT NULL, p_tenant UUID DEFAULT …) RETURNS JSONB live O(partitions) backlog for scaling: pending, live-lease processing, ready, their partition counts, and conflation-adjusted effective pending/ready, with no segment scan
get_partition_liveness_v1(p_filters JSONB DEFAULT '{}') RETURNS JSONB per-queue partition counts: total against those written to within the window, from log_partitions.last_write_at and created_at, with pending read from queen.stats. Counts only, no partition rows leave the database

020_log_partition_counters.sql (triggers)

Defines no functions; attaches trg_partitions_created_counter and trg_partitions_deleted_counter to queen.log_partitions. It is a separate file for two reasons. It must run after both 001_log_schema (the table) and 019_worker_metrics (the trigger functions), so a fresh database gets the attachment on its very first boot. And the attachment is conditional on the trigger being absent: DROP TRIGGER takes ACCESS EXCLUSIVE and CREATE TRIGGER takes SHARE ROW EXCLUSIVE on the engine’s hottest table, so re-running the pair every boot would stall every push and pop behind each rolling restart. The trade-off is that changing a trigger definition needs a manual drop; changing the function body still takes effect on restart, since CREATE OR REPLACE FUNCTION rebinds it without touching the table.

Advisory locks

Id Scope Held by
778120010 session boot-time schema apply, serialising DDL across replicas
737001 session the retention cycle leader
737002 transaction the stats reconciler leader
`hashtextextended(partition_id ‘/’
`hashtextextended(query_id ‘:’

The one global lock order

There are six lock spaces, not two, and a proof written over a subset of them is not a proof. The inventory below is the complete one, read off every FOR UPDATE, every UPDATE and every advisory lock in the engine’s procedure files.

Space Granularity Who takes it
queen.kv one row, (tenant, namespace, key) the transaction wire’s KV step, the standalone KV apply, the sweeper’s prune (SKIP LOCKED)
queen.log_timers one row, (tenant, queue, timer_key) the wire’s timer step, the standalone timer apply, the sweeper’s claim (SKIP LOCKED), the fire
advisory a hashed key the streams cycle (blocking), first-contact consumer creation in log_pop_v1 (try, never blocking)
queen.queues one row, plus the wait on the unique index’s transaction id lazy provisioning, in the push and in the wire
queen.log_partitions one row push, the wire’s pre-lock, multi push, streams sinks, retention, partition cleanup (SKIP LOCKED)
queen.log_consumers one row, (partition_id, group) pop (SKIP LOCKED), every ack, streams, cleanup by cascade

Leaf tables, which are insert-only or have a single writer, sit below all of them: log_segments, log_txns, log_dlq, retention_history, kv_usage, kv_quota.

The declared total order is:

queen.kv → queen.log_timers → advisory → queen.queues
         → queen.log_partitions → queen.log_consumers → leaves

And the rule that makes it a proof fits on one line:

No actor may ACQUIRE a lock on queen.kv or queen.log_timers after acquiring a lock on queen.queues, queen.log_partitions or queen.log_consumers.

Acquire is the load-bearing word, and the distinction it draws is what makes the rule falsifiable rather than merely severe. Writing again to a row this same transaction already holds is not an acquisition: it adds no edge to the wait-for graph. The precedent is in the house already, in the multi-push pre-lock, which re-locks rows it holds on purpose. Without the distinction the rule reads as a ban on the timer fire, whose last step deletes exactly the rows it locked in its first step, and the first person to apply it literally rewrites the one procedure that is correct.

Each actor that takes more than one row in a space also takes them in a declared order within that space: (namespace, key) ascending for queen.kv and (queue, timer_key) ascending for queen.log_timers, both with an explicit COLLATE "C"; name ascending for provisioning inserts; id ascending for queen.log_partitions; (partition_id, consumer_group) ascending for queen.log_consumers.

Those two conditions together are the whole argument, and both are needed: the first orders the spaces, the second orders the rows inside a space, which is where the first says nothing. Given both, the wait-for graph is acyclic by resource ordering.

Three actors never appear in that graph at all, whatever they visit: the pop, the sweeper’s timer claim and the sweeper’s KV prune all take their rows with SKIP LOCKED, so they never wait, and a vertex that never waits cannot be an edge.

log_partition_cleanup_step_v1 is the one maintenance step that holds many partition locks at once, so it is bound like any other multi-partition writer: it takes them ascending by id in a single statement, with SKIP LOCKED so it steps over a partition a pusher is holding instead of queueing in front of the write path. The ack path takes only consumer locks and never a partition lock; the per-partition retention steps take exactly one partition lock each.

The residue, stated honestly

No cycle survives the rule, but two costs do, and both are accepted rather than absent.

A fire holds timer rows while it waits for a partition lock, because T → P is the direction the order prescribes. If a cancel for one of those rows arrives in that window it waits. It does not fail, it is not lost, and it does not deadlock: the chain ends when the fire commits. The user’s cancel returns late, and what it returns then depends on whether the fire got there first.

The transaction wire holds a queen.kv row from its first step until its own commit, which is the outermost space held for the longest time in the system. It can spend that time waiting on a partition held by a fusion flush, and the flush never asks for a KV row, so the chain terminates. A chain is not a cycle, but the holding time is real and it is the price of putting the KV step first. It is first because the common case is a failed precondition, and failing there costs one insert and one raise, before a single partition lock has been taken, instead of an entire bundle written and thrown away.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close