Skip to content

Commit 853ffa7

Browse files
antiguruclaude
andcommitted
doc: design durable subscribe
A `SUBSCRIBE` cannot be resumed today. A client that loses its connection re-runs the subscribe and processes a fresh snapshot before it sees a new update. `AS OF` already expresses the resume, but nothing keeps the timestamp readable, and the default compaction window is one second, so a client that remembers a position finds it compacted away. This design proposes a durable subscription: a named catalog object holding a read hold on a storage collection, which the consumer advances by acknowledging what it has committed. The hold defines a window of readable time, the acknowledgement moves its lower edge, and a wall-clock time to live bounds how long the window stays open without progress. Two decisions carry most of the weight. The mechanism is a read hold rather than a read policy, because `ReadPolicy::Multiple` has no construction sites, policy installation only ratchets capabilities upward, and an ordinary `ALTER ... RETAIN HISTORY` would discard the contribution silently. And the subscription's hold is the attached dataflow's input floor rather than a second hold, because the compute controller never relaxes a sink's input hold, so a continuously connected reader would otherwise pin history from its attach point forever. The user documentation is included. It documents a feature that does not exist yet, and was written as part of the design to expose awkwardness in the SQL surface. It earned its place: writing it is what surfaced that progress messages are mandatory rather than optional, that the time to live must be required rather than defaulted, that retention is the minimum over readers, and that at-least-once delivery is a step back from what the existing manual pattern achieves for consumers with a transactional sink. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent c5d4f2e commit 853ffa7

8 files changed

Lines changed: 1394 additions & 6 deletions

File tree

doc/developer/design/20260825_durable_subscribe.md

Lines changed: 749 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
---
2+
title: "ACKNOWLEDGE"
3+
description: "`ACKNOWLEDGE` advances the position of a durable subscription."
4+
menu:
5+
main:
6+
parent: 'commands'
7+
---
8+
9+
`ACKNOWLEDGE` tells Materialize how far you have processed a [durable
10+
subscription](/sql/create-durable-subscription/), which advances the position it
11+
resumes from and releases the history before it.
12+
13+
## Syntax
14+
15+
```mzsql
16+
ACKNOWLEDGE DURABLE SUBSCRIPTION <name> AT <timestamp>
17+
;
18+
```
19+
20+
| Field | Use |
21+
| --- | --- |
22+
| `<name>` | The durable subscription to advance. |
23+
| `<timestamp>` | An [`mz_timestamp`](/sql/types/mz_timestamp/). Asserts that you have durably processed every update at times **strictly before** this value. |
24+
25+
## Details
26+
27+
### What to acknowledge
28+
29+
Acknowledge the `mz_timestamp` of a progress message, which is why
30+
[`SUBSCRIBE`](/sql/subscribe/#progress) must be run `WITH (PROGRESS)` when you
31+
intend to acknowledge. A progress message with timestamp `t` means no further
32+
updates will arrive at times strictly before `t`, which is exactly the claim
33+
`ACKNOWLEDGE ... AT t` makes back to Materialize.
34+
35+
Do not acknowledge the timestamp of an ordinary row. Not every timestamp
36+
produces a progress message, and a row at time `t` does not mean that time `t`
37+
is complete, so acknowledging it can skip updates you have not seen.
38+
39+
You do not have to subtract anything from the timestamp you acknowledge. When you
40+
resume without an explicit `AS OF`, Materialize positions the subscription so
41+
that you receive updates at and after the acknowledged time, whether or not you
42+
request a snapshot. The subtraction is only needed if you choose to pass [`AS
43+
OF`](/sql/create-durable-subscription/#where-reading-starts) yourself, which is
44+
an exclusive bound.
45+
46+
### Order of operations
47+
48+
Commit your data first, then acknowledge. If you acknowledge before your own
49+
processing is durable, and your application then fails, the acknowledged updates
50+
are gone and cannot be re-delivered.
51+
52+
### Semantics
53+
54+
`ACKNOWLEDGE` is:
55+
56+
* **Monotone.** Acknowledging a timestamp at or below the current position has
57+
no effect. It is not an error, so retrying is safe.
58+
59+
* **Idempotent.** Sending the same acknowledgement twice is indistinguishable
60+
from sending it once.
61+
62+
* **Not transactional.** The acknowledgement takes effect immediately and is
63+
not undone by `ROLLBACK`. This is deliberate: your data really was
64+
committed, so rolling back must not un-acknowledge it.
65+
66+
Acknowledging a timestamp above the current time of the target object is an
67+
error.
68+
69+
### Where you can run it
70+
71+
`ACKNOWLEDGE` may be run on the same connection as the subscription, interleaved
72+
between `FETCH` statements, or on a separate connection. Because a subscription
73+
is named, no coordination between connections is needed, and because
74+
acknowledgements are monotone, they cannot arrive out of order in any way that
75+
matters.
76+
77+
Running `ACKNOWLEDGE` while no one is reading the subscription is allowed. This
78+
matters for the separate-connection case, where the reading connection may drop
79+
while an acknowledgement is in flight.
80+
81+
### Effect on storage
82+
83+
The acknowledged position determines how much history Materialize retains for
84+
the subscription. Acknowledging more often releases storage sooner; acknowledging
85+
less often reduces round trips but retains more. Materialize records the position
86+
durably on a short interval rather than on every statement, so history is
87+
released slightly after you acknowledge.
88+
89+
## Examples
90+
91+
```mzsql
92+
ACKNOWLEDGE DURABLE SUBSCRIPTION winning_bids_feed AT 1723459200000;
93+
```
94+
95+
Acknowledging from a progress message received on the same connection:
96+
97+
```mzsql
98+
FETCH ALL c WITH (timeout = '1s');
99+
-- mz_timestamp | mz_progressed | mz_diff | ...
100+
-- 1723459200000 | t | |
101+
ACKNOWLEDGE DURABLE SUBSCRIPTION winning_bids_feed AT 1723459200000;
102+
```
103+
104+
## Related pages
105+
106+
* [`CREATE DURABLE SUBSCRIPTION`](/sql/create-durable-subscription/)
107+
* [`SUBSCRIBE`](/sql/subscribe/)
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
---
2+
title: "ALTER DURABLE SUBSCRIPTION"
3+
description: "`ALTER DURABLE SUBSCRIPTION` changes the time to live of a durable subscription, or resets one that has expired."
4+
menu:
5+
main:
6+
parent: 'commands'
7+
---
8+
9+
`ALTER DURABLE SUBSCRIPTION` changes the time to live of a [durable
10+
subscription](/sql/create-durable-subscription/), or resets one that has
11+
expired.
12+
13+
## Syntax
14+
15+
```mzsql
16+
ALTER DURABLE SUBSCRIPTION <name> SET (TTL = <interval>);
17+
ALTER DURABLE SUBSCRIPTION <name> RESET;
18+
```
19+
20+
| Field | Use |
21+
| --- | --- |
22+
| `SET (TTL = <interval>)` | Change how long you have to acknowledge. Takes effect immediately, including for a subscription that is currently behind. |
23+
| `RESET` | Re-arm an expired subscription at the current time. |
24+
25+
## Details
26+
27+
### Setting the time to live
28+
29+
Increasing the `TTL` gives a reader more time to recover, and increases the
30+
history that may be retained on its behalf. Decreasing it can expire a
31+
subscription immediately, if the reader is already further behind than the new
32+
value allows.
33+
34+
The `TTL` must remain above the system-wide minimum, which exists because the
35+
acknowledged position is recorded durably on an interval. A time to live close to
36+
that interval would expire readers that are acknowledging correctly.
37+
38+
### Resetting an expired subscription
39+
40+
`RESET` moves an expired subscription back to the current time and makes it
41+
usable again. It does not recover the history that was released when the
42+
subscription expired, so the next read must request a snapshot.
43+
44+
Use `RESET` rather than dropping and recreating: it preserves the subscription's
45+
name, owner, and privileges. Requiring it, instead of silently resuming from
46+
whatever history happens to remain, is what keeps a gap in the data from passing
47+
unnoticed.
48+
49+
`RESET` on a subscription that has not expired is an error. Fencing a live reader
50+
by resetting its position is not something to do by accident.
51+
52+
## Examples
53+
54+
```mzsql
55+
ALTER DURABLE SUBSCRIPTION winning_bids_feed SET (TTL = '5m');
56+
```
57+
58+
```mzsql
59+
ALTER DURABLE SUBSCRIPTION winning_bids_feed RESET;
60+
```
61+
62+
## Related pages
63+
64+
* [`CREATE DURABLE SUBSCRIPTION`](/sql/create-durable-subscription/)
65+
* [`DROP DURABLE SUBSCRIPTION`](/sql/drop-durable-subscription/)

0 commit comments

Comments
 (0)