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

# 多表区块高度对齐订阅

> 使用 Aligned 状态机，让多张表在同一高度都就绪后再触发下游计算。

# 多表区块高度对齐订阅

## 业务背景

在级联实时分析里，下游的目标表是一张 Block 表，按区块粒度写入：每个高度 H 的所有计算结果作为一个批次，原子写入目标表的 H 高度。因此当上游有多张表时，必须先等所有上游表都把 H 处理完毕，才能一次性计算 H、作为一个批次写入目标表；否则 H 批次会缺数据、产生脏批次。

更关键的是，计算函数里常用 `get_state` 读取依赖表的状态快照。读的是哪个高度版本，取决于依赖表的共识高度：只有依赖表的共识高度 ≥ H，`get_state` 才能返回 H 高度版本的状态数据；否则会因共识高度小于 H 而报错、拿不到 H 的状态。因此必须先等依赖表的水位推进到 H 及以上，才能执行函数。

直接同时订阅多张表的事件流，会因各表计算快慢不一，收到完全乱序的高度通知（例如 `trace` 已到 18000010，而 `token` 还在 18000005）。

SDK 内置的 `Aligned` 对齐状态机，把这两类等待需求区分开，在数据库变更流层面自动完成对齐。

## Trigger 表与 Depends 表

`Aligned` 区分两类表，各自采用不同的对齐规则（类似多流 join，但两类的 join 条件不同）：

1. **Trigger 表（`loose_align`）**：上游数据源。对齐规则是「**所有 Trigger 表都在高度 H 就绪**」——因为下游要按 H 批次写入，上游少一张就代表 H 批次缺数据。

2. **Depends 表（`strict_align`）**：计算中用 `get_state` 读取的状态表。对齐规则是「**Depends 表的共识高度 ≥ H**」——因为 `get_state` 只能读到不超过共识高度的快照，水位没到 H 就读不到 H 的状态。

两者的判据不同：Trigger 看「是否都产生了 H」，Depends 看「水位是否覆盖 H」。只有当 H 同时满足所有 Trigger 就绪、且所有 Depends 水位到位，才会放行 H。**每个高度只放行一次**，天然幂等。

## 完整示例

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

# 1) 初始化逻辑表实例
idx = BlockTable("chaintable.trace.eth")        # Trigger：你要消费的核心表
token = BlockTable("token.token_transfer.eth")  # Depends：依赖的中间表
addr = BlockTable("address.addresses.eth")      # Depends：依赖的中间表

# 2) 一条订阅流喂给 Aligned 做高度对齐
sub = Subscribe(tables=[idx, token, addr])
aligned = Aligned(
    sub,
    loose_align=[idx],          # 只有 idx 产生新高度，才具备触发资格
    strict_align=[token, addr], # 依赖表进度滞后时，阻塞放行
)

# 3) 像消费生成器一样，在区块时间下安全消费
for block in aligned.listen():
    # 此刻 idx、token、addr 在 block.height 下已全局就绪
    rows = idx.get_block_rows(block)
    # 执行业务计算并写回下游
```

## 共识高度是怎么计算的

`Aligned` 通过查询每张 Depends 表的 `_height` 派生表来计算共识高度：

* 每张 Block 表对应一张 `<表名>._height` 表，每行记录一段已处理的高度区间（`block_height_start` / `block_height_end`），写入侧已合并成连续区间。
* 共识高度 = 起始高度最小那行的 `block_height_end`，即第一个连续区间的右边界。
* 表还没被处理过时，共识高度取 `start_height - 1`——一张起始高度不为 0 的表，比起点低的高度永远不会有数据。

因此，只要 Depends 表从 `start_height` 起连续提交每个块（含空块），它的共识高度就会持续推进，依赖它的下游才能随之放行。

## 最佳实践

* **Depends 表每个块都要提交任务，包括空块**：空块也要提交一个空任务来推进 `_height` 水位，否则 coverage 出现 gap，共识高度就停在 gap 前，下游整条管道会静默停住。
* **单表订阅无需 `Aligned`**：只订阅一张表直接 `for ... in Subscribe(...).listen()` 即可。
* **同一张表可同时作为 Trigger 和 Depends**：`Aligned` 会先记它的 ready、再刷新它的水位，保证 diff 区间能看到刚标上的高度。
* `Subscribe` 内置重连与游标续传，直接 `for ... in aligned.listen():` 消费，无需自己写重试。

## 相关阅读

* [实时订阅](/zh/guides/subscribe)
* [写入与事件订阅](/zh/concepts/write-subscribe)
* [配合 BlockX 高并发计算](/zh/guides/blockx)


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