Skip to main content

Multi-table alignment

Why alignment is needed

In cascaded real-time analytics, your downstream target table is a Block table written at block granularity: all compute results for a height H are written atomically to the target table’s height H as one batch. So when you have several upstream tables, you must wait until every upstream table has finished processing H before you can compute H once and write it as a single batch — otherwise the H batch would be missing data and become a dirty batch. Even more importantly, compute functions often use get_state to read state snapshots from dependency tables. Which height version you can read depends on the dependency table’s consensus height: only when it is ≥ H can get_state return the state at height H; otherwise it fails because the consensus height is below H and you cannot get H’s state at all. So you must wait for the dependency table’s watermark to reach H or beyond before running the function. Subscribing to several tables’ event streams directly gives you completely out-of-order height notifications, because each table computes at a different pace (for example trace has reached 18000010 while token is still at 18000005). The SDK’s Aligned state machine separates these two kinds of waiting and performs the alignment automatically at the database change-stream layer.

Trigger tables and Depends tables

Aligned distinguishes two kinds of tables, each with its own alignment rule (similar to a multi-stream join, but the join conditions differ):
  1. Trigger tables (loose_align): the upstream data sources. The rule is “every Trigger table is ready at height H” — because the downstream writes H as one batch, and a single missing upstream table means the H batch is incomplete.
  2. Depends tables (strict_align): the state tables your compute reads with get_state. The rule is “the Depends table’s consensus height is ≥ H” — because get_state can only read snapshots at or below the consensus height, so a watermark below H cannot serve H’s state.
The two criteria differ: Trigger asks “has every table produced H?”, Depends asks “does the watermark cover H?”. A height H is released only when all Trigger tables are ready and every Depends watermark is in place. Each height is released exactly once, so it is naturally idempotent.

Complete example

How the consensus height is computed

Aligned computes the consensus height by querying each Depends table’s _height derived table:
  • Every Block table has a <table>._height table whose rows each record a processed height range (block_height_start / block_height_end); the write path already merges them into contiguous ranges.
  • The consensus height is the block_height_end of the row with the smallest start height — the right boundary of the first contiguous range.
  • If the table has not been processed at all, the consensus height is start_height - 1: for a table whose start height is not 0, heights below that start will never have data.
So as long as a Depends table submits every block contiguously from start_height (including empty blocks), its consensus height keeps advancing and its downstream consumers can be released accordingly.

Best practices

  • Depends tables must submit a task for every block, including empty ones: an empty block still needs an empty task to advance the _height watermark. Otherwise coverage develops a gap, the consensus height stalls before the gap, and the whole downstream pipeline stops silently.
  • A single-table subscription needs no Aligned: just consume for ... in Subscribe(...).listen() directly.
  • The same table can be both Trigger and Depends: Aligned records its readiness first and then refreshes its watermark, so the diff range can see the height it just marked.
  • Subscribe handles reconnection and cursor resumption internally; consume with for ... in aligned.listen(): and skip your own retry logic.