SUBSCRIBE streams updates from a source, table, or view as they occur.
SUBSCRIBE statement is a more general form of a
statement. While a
SELECT statement computes a relation at a moment in time, a
subscribe operation computes how a relation changes over time.
SUBSCRIBE produces a sequence of updates. An update describes either
the insertion or deletion of a row to the relation at a specific time. Taken
together, the updates describe the complete set of changes to a relation, in
SUBSCRIBE is active.
You can use
- Power event processors that react to every change to a relation or an arbitrary
- Replicate the complete history of a relation while
- Test a SQL
SELECTstatement over non-materialized views
|object_name||The name of the source, table, or view that you want to subscribe to.|
The following options are valid within the
|Option name||Value type||Default||Describes|
||Whether to emit a snapshot of the current state of the relation at the start of the operation. See
||Whether to include detailed progress information. See
SUBSCRIBE emits a sequence of updates as rows. Each row contains all of the
columns of the subscribed relation or derived from the
SELECT statement, prepended
with several additional columns that describe the nature of the update:
Materialize's internal logical timestamp. This will never be less than any
timestamp previously emitted by the same
This column is only present if the
The change in frequency of the row. A positive number indicates that
|Column 1||Varies||The columns from the subscribed relation, each as its own column, representing the data that was inserted into or deleted from the relation.|
SUBSCRIBE will continue to run until canceled, session ends, or until all updates have been presented. The latter case typically occurs when
tailing constant views (e.g.
CREATE VIEW v AS SELECT 1).
Many PostgreSQL drivers wait for a query to complete before returning its
SUBSCRIBE can run forever, naively executing a
SUBSCRIBE using your
driver’s standard query API may never return.
SUBSCRIBE begins by emitting a snapshot of the subscribed relation, which
consists of a series of updates describing the contents of the relation at its
initial timestamp. After the snapshot,
SUBSCRIBE emits further updates as
For updates in the snapshot, the
mz_timestamp field will be fast-forwarded to the initial timestamp.
For example, an insert that occurred before the
SUBSCRIBE began would appear in the snapshot.
To see only updates after the initial timestamp, specify
WITH (SNAPSHOT = false).
Intuitively, progress messages communicate that no updates have occurred in a given time window. Without explicit progress messages, it is impossible to distinguish between a stall in Materialize and a legitimate period of no updates.
PROGRESS option is specified via
WITH (PROGRESS), an additional
mz_progressed column appears in the output.
When the column is
false the rest of the row is a valid update.
When the column is
true the rest of the row is not a valid update and its content should be ignored;
the row exists only to communicate that timestamps have advanced.
Not all timestamps that appear will have a corresponding row with
mz_progressed set to
For example, the following is a valid sequence of updates:
mz_timestamp | mz_progressed | mz_diff | column1 -------------|---------------|---------|-------------- 1 | false | 1 | data 2 | false | 1 | more data 3 | false | 1 | even more data 4 | true | NULL | NULL
Notice how Materialize did not emit explicit progress messages for timestamps
2. The receipt of the update at timestamp
2 implies that there
are no more updates for timestamp
1, because timestamps are always presented
in non-decreasing order. The receipt of the explicit progress message at
4 implies that there are no more updates for either timestamp
3—but that there may be more data arriving at timestamp
SUBSCRIBE produces rows similar to a
SELECT statement, except that
SUBSCRIBE may never complete.
Many drivers buffer all results until a query is complete, and so will never return.
Below are the recommended ways to work around this.
Creating a counter load generator
As an example, we’ll create a counter load generator that emits a row every second:
CREATE SOURCE counter FROM LOAD GENERATOR COUNTER WITH (SIZE = '3xsmall');
The recommended way to use
SUBSCRIBE is with
These must be used within a transaction, with a single
DECLARE per transaction.
This allows you to limit the number of rows and the time window of your requests.
Next, let’s subscribe to the
counter load generator source that we’ve created above.
First, declare a
BEGIN; DECLARE c CURSOR FOR SUBSCRIBE (SELECT * FROM counter);
FETCH in a loop to retrieve each batch of results as soon as it’s ready:
FETCH ALL c;
That will retrieve all of the rows that are currently available.
If there are no rows available, it will wait until there are some ready and return those.
timeout can be used to specify a window in which to wait for rows. This will return up to the specified count (or
ALL) of rows that are ready within the timeout. To retrieve up to 100 rows that are available in at most the next
FETCH 100 c WITH (timeout='1s');
To retrieve all available rows available over the next
FETCH ALL c WITH (timeout='1s');
0s timeout can be used to return rows that are available now without waiting:
FETCH ALL c WITH (timeout='0s');
If you want to use
SUBSCRIBE from an interactive SQL session (e.g.
psql), wrap the query in
COPY (SUBSCRIBE (SELECT * FROM counter)) TO STDOUT;
Mapping rows to their updates
After all the rows from the
SNAPSHOT have been transmitted, the updates will be emitted as they occur. How can you map each row to its corresponding update?
|mz_timestamp||mz_progressed||mz_diff||Column 1||….||Column N|
|1||false||1||id3||value3||<- Last row from
If your row has a unique column key, it is possible to map the update to its corresponding origin row; if the key is unknown, you can use the output of
In the example above,
Column 1 acts as the column key that uniquely identifies the origin row the update refers to; in case this was unknown, hashing the values from
Column 1 to
Column N would identify the origin row.
counter load generator source
When you’re done, you can drop the
counter load generator source:
DROP SOURCE counter;