> ## Documentation Index
> Fetch the complete documentation index at: https://docs.blockdb.chaintable.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Multi-table alignment

> Use the Aligned state machine so all tables are ready at the same height before triggering downstream compute.

# 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

```python theme={null}
from blockdb import BlockTable, Aligned, Subscribe

# 1) Initialize the logical table instances
idx = BlockTable("chaintable.trace.eth")        # Trigger: the core table you consume
token = BlockTable("token.token_transfer.eth")  # Depends: an intermediate table you rely on
addr = BlockTable("address.addresses.eth")      # Depends: another intermediate table

# 2) Feed one subscription stream into Aligned for height alignment
sub = Subscribe(tables=[idx, token, addr])
aligned = Aligned(
    sub,
    loose_align=[idx],          # only idx produces candidate heights
    strict_align=[token, addr], # block release when a dependency lags behind
)

# 3) Consume like a generator, safely, on block time
for block in aligned.listen():
    # At this point idx, token, and addr are all ready at block.height
    rows = idx.get_block_rows(block)
    # run your compute and write back downstream
```

## 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.

## Related topics

* [Real-time subscription](/guides/subscribe)
* [Writes and event subscription](/concepts/write-subscribe)
* [High-concurrency compute with BlockX](/guides/blockx)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.