Skip to Content
Change Feed

Change Feed

koldstore.changes_since is the packaged SQL change API for managed tables. It merges the hot change-log mirror and cold Parquet into one latest-state stream ordered by seq.

This is catch-up for clients (and aligns with KalamDB subscribe semantics for from / newest-N). It is not a full temporal audit log of every historical event.

Signature

SQL
koldstore.changes_since(  table_name regclass,  since_seq  bigint  DEFAULT 0,      -- exclusive resume (seq > since_seq)  limit_rows integer DEFAULT 1000,   -- forward page size  last_rows  integer DEFAULT NULL    -- newest-N rewind when since_seq = 0)RETURNS TABLE (  seq        bigint,  op         smallint,   -- 1=INSERT, 2=UPDATE, 3=DELETE  pk         jsonb,  deleted    boolean,  row_image  jsonb,  source     text        -- 'hot' | 'cold')

Modes

ModeArgumentsBehavior
Resumesince_seq > 0Rows with seq > since_seq, ascending, capped by limit_rows. last_rows is ignored.
Rewindsince_seq = 0 and last_rows setNewest N retained changes, delivered oldest → newest
From startsince_seq = 0, no last_rowsStart of retained history

Precedence rule: a positive since_seq always wins over last_rows.

Examples

Fence first when you need a strong boundary against the heap:

SQL
SELECT koldstore.wait_for_async_mirror();

Page from the beginning

SQL
SELECT seq, op, pk, deleted, sourceFROM koldstore.changes_since(  table_name => 'app.messages'::regclass,  since_seq  => 0,  limit_rows => 100);

Resume after the last consumed seq

SQL
SELECT seq, op, pk, deleted, sourceFROM koldstore.changes_since(  table_name => 'app.messages'::regclass,  since_seq  => 184467440737095516,  -- last seq you already applied  limit_rows => 500);

Advance your cursor from the highest seq you successfully processed.

Newest N (subscribe-style rewind)

SQL
SELECT seq, op, pk, deleted, sourceFROM koldstore.changes_since(  table_name => 'app.messages'::regclass,  since_seq  => 0,  limit_rows => 1000,  last_rows  => 50);

last_rows must be <= limit_rows.

After flush

The same cursor continues to return flushed rows from cold (source = 'cold'). Do not treat “mirror empty” as “caught up” if cold still holds retained history.

Semantics and caveats

  • Exclusive cursor: seq > since_seq, never inclusive.
  • Authoritative seq values come only from the serialized WAL applier.
  • Latest-state per PK — updates replace prior images; deletes appear as tombstones until pruned by flush retention rules.
  • since_seq = 0 means “start of retained history,” not “I fell behind retention.”
  • A positive since_seq older than the retained cold floor raises a retention-gap error — do not treat an empty page as success in that case.
  • Hot + cold merge; source tells you which tier contributed the row image.

Direct mirror SQL (advanced)

You can inspect the hot mirror for debugging:

SQL
SELECT id, seq, opFROM koldstore.messages__clWHERE seq > $1ORDER BY seqLIMIT 100;

Prefer changes_since in application code so cold history stays visible after flush and retention-gap errors stay consistent.

Next

Last updated on