Skip to content

Commit 62828b2

Browse files
NikolaySclaude
andcommitted
docs(reference): document receive_coop empty-batch and lock behavior
- Add an "empty tick windows are auto-finished" note: receive_coop() closes empty batches internally, so callers polling a quiet queue do not need to ack a batch_id. This is a meaningful divergence from receive() and was previously undocumented. - Add a throughput note explaining that cooperative allocation serializes on a FOR UPDATE of the coop_main row, so undersized batches and short tick periods make the main row a hotspot. - Drop the "in PgQue 0.2" version tag from the experimental marker per the docs rule ("no concrete version tags in README/docs"). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 27eb849 commit 62828b2

1 file changed

Lines changed: 6 additions & 1 deletion

File tree

docs/reference.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ perform pgque.nack(msg.batch_id, msg, interval '5 minutes', 'validation failed')
148148

149149
## Cooperative consumers / subconsumers
150150

151-
**Experimental in PgQue 0.2.** Function names, edge-case behavior, and client API shape may change before this feature is marked stable. Do not use this as the only processing path for critical workloads without idempotent handlers and stale-worker takeover tests.
151+
**Experimental.** Function names, edge-case behavior, and client API shape may change before this feature is marked stable. Do not use this as the only processing path for critical workloads without idempotent handlers and stale-worker takeover tests.
152152

153153
Cooperative consumers let several subconsumers share one logical consumer cursor. The main consumer row (`sub_role = 'coop_main'`) owns the group cursor; member rows (`sub_role = 'coop_member'`) own active batches. The feature is bundled in the default SQL install, but downgrade after creating subconsumers is unsupported unless subconsumers are unregistered first.
154154

@@ -168,7 +168,12 @@ Grant: `pgque_reader`. Source: `sql/pgque-api/cooperative_consumers.sql`.
168168

169169
Receives messages for one subconsumer. `max_return` must be >= 1. `dead_interval` enables stale-batch takeover from another inactive member; takeover allocates a fresh `batch_id`, so old tokens cannot ack/nack the new owner's state. The cooperative group is a trust boundary: callers allowed to use the same `(queue, consumer)` can steal stale batches from each other by design, so do not share one cooperative group across mutually untrusted workers.
170170

171+
**Empty tick windows are auto-finished.** When the current batch's tick window holds no events, `receive_coop()` calls `finish_batch` internally and returns the empty set. Callers polling a quiet queue do not see (and do not need to ack) a `batch_id`; this differs from `receive()`, which still returns an active batch token even when the result set is empty.
172+
171173
**Batch-ownership caveat.** As with `receive()`, `max_return` limits only returned rows; `ack(batch_id)` advances the cooperative cursor past the whole underlying batch. Use `max_return >= ticker_max_count` or consume the full batch before acking.
174+
175+
**Throughput note.** Cooperative allocation serializes on a `FOR UPDATE` of the `coop_main` subscription row, so many workers polling tiny batches contend on a single hot row. If you scale workers, also tune `ticker_max_count` and tick cadence so each batch is large enough to amortize the lock.
176+
172177
Grant: `pgque_reader`. Source: `sql/pgque-api/cooperative_consumers.sql`.
173178

174179
#### `pgque.next_batch(queue text, consumer text, subconsumer text, dead_interval interval default null) → bigint`

0 commit comments

Comments
 (0)