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

# 写入架构

> BlockDB 的写入架构：批量写为预设，异步为核心，逻辑模型的读写订阅接口确保一致性。

# 写入架构

BlockDB 为区块链计算场景设计：上游数据总是按**区块粒度**产出，天然就是一批一批的行。因此 BlockDB 的写入以 **batch 批量写为预设**，而不是传统的单行写。区别只在于 batch 的规模——小的、近实时的 batch 走**实时写**；大的、回填场景的 batch 走**批量写**。

## 一切写均为异步

BlockDB **一切写操作均为异步**：SDK 不暴露 `sync` 参数，所有写入接口返回 `WriteResult { affectedRows, jobID }`，不代表数据已立即落库。

因此，普通的写入接口**不满足经典的写后读一致性**——写请求返回后立刻读，不保证能读到刚写入的数据。

但这个"缺口"在 BlockDB 的逻辑模型中被弥补：

* **Block 表**：写入成功后，write 服务通过 Kafka 发布 `BlockSubscribeEvent`。下游通过 `Subscribe` 接收事件后再读取该区块的数据，BlockDB 保证此时数据已落库可见。**在订阅驱动的计算链路中：写 → 事件 → 读，是严格满足写后读一致性的。**
* **Time 表**：写入后通过 `GetValue` 对 `_archive` 表查询，同样能获取已落库的历史桶数据。

因此，虽然底层写是异步的，但通过 Block 表和 Time 表的读写订阅接口，在区块链计算场景中严谨满足一致性需求。

## SDK 写接口总览

### 实时写

| 接口 | 说明 |
| - | - |
| `table.upsert_rows(rows)` | 普通表实时写入 |
| `block_table.write_block(block, rows)` | Block 表按区块实时写入 |
| `time_table.upsert_time_rows(rows, time_at)` | Time 表按时间桶实时写入 |

### 批量写（S3 中转，上限 1000 万行）

批量写统一走一个 gRPC 服务 `BatchWriteService`。在 `InitWriteJob` 请求中，通过选择 `strategy` 字段来指定目标表类型：

| Strategy | 表类型 | 说明 |
| - | - | - |
| `normal_upsert` | 普通表 | 普通表大批量写入 |
| `block_overwrite` | Block 表 | Block 表按区块/高度区间批量写入 |
| `time_upsert` | Time 表 | Time 表批量写入（回填） |

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

例如，发起一个 Time 表批量写任务：

```bash theme={null}
grpcurl -plaintext -d '{
  "tableId": "token_time_demo",
  "timeUpsert": {}
}' localhost:19002 blockdb.v1.BatchWriteService/InitWriteJob
```

所有写方法返回统一的 `WriteResult { affectedRows, jobID }`。

## 实时写架构

实时写由简单的无状态 writer 负责，内部采用**内存异步队列 + 多线程响应式写入**：

1. **入队**：写入请求进入内存异步队列（`asyncqueue`），立即返回 `jobID`。用户不会因后端落库速度而阻塞。
2. **消费**：多个 worker 线程并发从队列中取任务，批量写入底层物理数据库。
3. **流量整形**：队列容量有上限，当后端落库跟不上时触发拒绝（返回 `QUEUE_OVERLOAD`），将无上限的实时流量转为有上限的写入流量，保护底层数据库。

设计目标：实时流量不阻塞用户提交；空闲线程几乎立即响应写入请求，达到近实时。

## 批量写架构

当写入规模达到百万行级别（上限 1000 万行）时，实时写的内存队列不可行。批量写通过 **S3 对象存储中转 + 异步 worker** 实现：

### 任务生命周期

1. **`InitWriteJob`**：客户端发起写入任务申请。BlockDB 在表的 `_write` 表中创建一条 job 记录，返回 S3 presigned 上传 URL 与 `jobID`。
2. **上传数据文件**：客户端将数据上传到返回的 S3 URL。
3. **`CommitWriteJob`**：客户端调用提交接口，BlockDB 将 job 状态置为待处理，后台 worker 开始消费。
4. **Worker 执行**：worker 从 S3 拉取任务文件，执行排序、去重、批量插入等步骤后落库。

### 架构特点

* **提交吞吐无上限**：任务通过 S3 中转，不占用实时内存通路，可连续高吞吐提交。
* **落库速度快**：worker 以批处理方式工作，实际写入速度可达数十万\~百万行/s（视表类型与底层数据库规模）。
* **可取消**：堆积的写入任务可通过 `CancelWriteJob` API 取消，释放 S3 资源及 `_write` 表中的对应 job 记录。
* **任务状态可查**：job 在 `_write` 表中维护，包括已初始化、已提交、处理中、完成或失败等状态，便于运维排查。

## 实时写 vs 批量写

| 维度 | 实时写 | 批量写 |
| - | - | - |
| 数据规模 | 小批次（如单个区块的计算结果） | 大批次（如回填百万行历史数据） |
| 中转方式 | 内存异步队列 | S3 对象存储 |
| 提交吞吐 | 近实时，受队列容量约束 | 提交无上限 |
| 落库速度 | 近实时 | 数十万\~百万行/s |
| 任务管理 | 无持久化 job 记录 | job 维护在 `_write` 表，可查可取消 |


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