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

# Writes & subscriptions

> BlockDB's real-time writes, batch writes, block subscription events, and the subscription loop.

# Writes & subscriptions

BlockDB splits writes into **real-time writes** and **batch writes**, and builds block event subscription on top of them.

## Real-time writes

Real-time writes target normal-sized online writes and provide near real-time, low-latency ingestion. The SDK offers:

| SDK interface | Description |
| - | - |
| `table.upsert_rows(rows)` | Write to a regular table |
| `block_table.write_block(block, rows)` | Write to a Block State / Block Event table at block granularity |
| `time_table.upsert_time_rows(rows, time_at)` | Write to a Time Table by time bucket |

Use these for continuously arriving online data.

## Batch writes

When you need to write massive amounts of data (for example historical backfill), use batch writes. Their characteristics:

* **Submit a single asynchronous write job containing millions of rows.**
* **Unbounded submission throughput**: data is staged through **object storage**, so it does not consume the real-time network and memory path, and you can submit jobs continuously at high throughput.
* **Fast actual ingestion**: the underlying speed of large batch writes reaches **hundreds of thousands to over a million rows per second**, depending on the table type and the size of the underlying database.

Batch writes go through the `BatchWriteService` gRPC service. In the `InitWriteJob` request you choose the target table type with the `strategy` field:

| Strategy | Table type | Description |
| - | - | - |
| `normal_upsert` | Regular table | Large batch write to a regular table |
| `block_overwrite` | Block table | Batch write to a Block table across a height/bundle range |
| `time_upsert` | Time table | Batch write to a Time table (backfill) |

```protobuf theme={null}
message InitWriteJobRequest {
  string table_id = 1;
  oneof strategy {
    BlockTableOverwriteStrategy block_overwrite = 2;
    NormalTableUpsertStrategy   normal_upsert   = 3;
    TimeTableUpsertStrategy     time_upsert     = 4;
  }
}
```

### Real-time vs batch writes

| Dimension | Real-time writes | Batch writes |
| - | - | - |
| Use case | Normal, online, near real-time writes | Massive historical backfill |
| Data size | Small per batch | Millions of rows |
| Submission | Direct | Object storage staging + async job |
| Throughput | Near real-time low latency | Unbounded submission; 100K – 1M+ rows/s ingestion |

All write interfaces return a uniform `WriteResult` (`affectedRows` plus a `jobID`).

## Block subscription events (BlockSubscribeEvent)

Every time BlockDB successfully writes to a Block table, it publishes a **block subscription event** (`BlockSubscribeEvent`). Per the `subscribe` protocol definition, the event fields are:

| Field | Description |
| - | - |
| `table_id` | Which Block table was updated |
| `block` | The related block metadata (`id` / `height` / `timestamp`) |
| `created_at` | When the event was produced |

Events are distributed through a Kafka event source, and subscribers receive them as a stream through the `Subscribe` service.

## The subscription loop

Through the **event subscription mechanism** (the Subscribe API), BlockDB closes the loop of "data production → event notification → downstream consumption", keeping your business logic fully in sync with the chain's heartbeat (its block production rate):

```python theme={null}
from blockdb.block_table import Subscribe

sub = Subscribe(tables=["onchain_token_balance.eth"])
for table_id, block in sub.listen():
    # your processing logic...
    print(table_id, block.height, block.id)
```

This loop lets your business code run on the database's change stream like a "virtual smart contract".

### Table-to-table ETL pipelines

You do not need to subscribe to the rawest trace and event block tables. You can start from any existing topic-specific Block table on the platform and process it further, turning subscriptions into an ETL pipeline that simplifies data processing.

## Multi-table alignment

When your business logic depends on several upstream tables computed in parallel, you must ensure every table has finished writing the target height before consuming it safely. BlockDB includes an `Aligned` state machine that handles out-of-order cross-table event streams automatically — see [Multi-table block height alignment](/guides/multi-table-alignment).

## Advantages of the BlockDB model

| Dimension | Traditional on-chain data processing | The BlockDB model |
| - | - | - |
| Latency | Manual sync, minutes of delay | Block-level sync, millisecond awareness |
| Development effort | Understand blocks, learn contracts, process from the source | Operate reads/writes at the database layer via the SDK; subscribe to decoded, modeled tables |
| Pipeline complexity | Maintain queues, offline scripts, intermediate tables | Tables drive tables directly; the pipeline is natural |
| Consistency | Handle multi-table updates and reorgs yourself | BlockDB updates block tables atomically; reorgs need no maintenance |


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