好,**P**。
这一阶段我们做的是:**把你的引擎从“逻辑正确”推到“生产级可靠”**。
我会非常工程化地来,不空谈。
下面这份内容,你可以直接当 **数据库白皮书 + 落表规范** 来用。
---
# 🧱 P — Production-grade Persistence Design
**(PostgreSQL Schema v2 + 事务边界)**
> 目标一句话:
> **任何时刻宕机 / 重启 / 并发 / 重试,都能从 DB 精确恢复引擎状态**
---
## 一、核心设计原则(先立法)
### ✅ 1. DB 是唯一真实状态源
- 内存 = cache
- 事件 = 可重放
- 所有推进语义 **必须可由表状态推导**
---
### ✅ 2. “运行态” 与 “历史态” 严格分离
| runtime 表 | ✅ 可变 | 引擎调度 |
| history 表 | ❌ 只追加 | 审计 / 回放 |
---
### ✅ 3. Token 是事务边界的核心锚点
> **任何流程推进 = Token 行状态变化**
---
## 二、Schema 总览(你最终应有这些表)
```
process_definitions
process_instances
tokens
external_tasks
timers
token_links (并行 / join)
token_events (history)
task_events (history)
engine_events (optional)
```
---
## 三、Process & Instance
### process_definitions
```sql
process_definitions (
id uuid PK,
name text,
version int,
graph jsonb,
created_at timestamptz
)
```
✔ graph = 编译后的流程图
✔ immutable
---
### process_instances
```sql
process_instances (
id uuid PK,
process_def_id uuid,
state text, -- RUNNING / COMPLETED / FAILED
started_at timestamptz,
ended_at timestamptz
)
```
✔ **instance 是 Token 的容器,不承载执行细节**
---
## 四、Token(这是整个系统的心脏)
### tokens(runtime)
```sql
tokens (
id uuid PK,
instance_id uuid,
node_id text,
state text,
-- ACTIVE / WAITING / COMPLETED / FAILED / CANCELLED
version int, -- 乐观锁
created_at timestamptz,
updated_at timestamptz
)
```
### 关键规则
- **每一次状态变化 → version +1**
- 所有 UPDATE 都必须带 `version = ?`
```sql
UPDATE tokens
SET state = ?, version = version + 1
WHERE id = ? AND version = ?
```
❗ 这是你所有并发安全的基础
---
## 五、Parallel / Join 的持久化(很多引擎这里崩)
### token_links
```sql
token_links (
parent_token_id uuid,
child_token_id uuid,
link_type text -- FORK / JOIN
)
```
### Fork
```
parent ACTIVE
→ insert N child tokens
→ parent → WAITING
→ insert links
```
### Join 判断(DB 可判断)
```sql
SELECT count(*)
FROM tokens
WHERE parent_token_id = ?
AND state != 'COMPLETED';
```
= 0 → join 通过
✔ 不依赖内存
✔ 可 crash 恢复
---
## 六、External Task(你现在的重点)
### external_tasks(runtime)
```sql
external_tasks (
id uuid PK,
token_id uuid,
topic text,
state text, -- READY / LOCKED / DONE / FAILED
retries int,
retry_at timestamptz,
lock_owner text,
lock_expire_at timestamptz,
created_at timestamptz
)
```
### 原则
- **external_task 状态 ≠ token 状态**
- task DONE ≠ token COMPLETED
→ token 由 Engine 推进
---
## 七、Timer(你必须落表,不能靠内存)
### timers
```sql
timers (
id uuid PK,
token_id uuid,
fire_at timestamptz,
state text, -- SCHEDULED / FIRED / CANCELLED
created_at timestamptz
)
```
### Scheduler 做的事(循环)
```sql
SELECT *
FROM timers
WHERE fire_at <= now()
AND state = 'SCHEDULED'
FOR UPDATE SKIP LOCKED;
```
✔ Engine 可横向扩展
✔ Timer 不丢
---
## 八、History 表(可选但强烈建议)
### token_events
```sql
token_events (
id bigserial PK,
token_id uuid,
event_type text,
from_state text,
to_state text,
at timestamptz
)
```
> 所有 runtime UPDATE
> **同事务 insert history**
---
## 九、事务边界(这是最关键的部分)
### 🚦 一个铁律
> **一个事务,只推进一个 Token**
---
### 示例:External Task Complete
```text
BEGIN;
1. SELECT external_task FOR UPDATE
2. 校验 lock_owner / state
3. UPDATE external_task → DONE
4. UPDATE token → ACTIVE (or next state)
5. INSERT token_event
COMMIT;
```
❌ 不要在一个事务推进多个 token
❌ 不要跨 instance
---
### Fork(事务示意)
```text
BEGIN;
1. UPDATE parent token → WAITING
2. INSERT child tokens
3. INSERT token_links
4. INSERT history
COMMIT;
```
---
## 十、Engine Recovery(你现在可以做到的)
### Engine 启动时必须:
1. 解锁过期 External Task
2. 恢复过期 Timer
3. 扫描 ACTIVE token 推进
> **你不需要“恢复逻辑”**
>
> **只要重新跑调度器**
---
## 十一、你现在这个引擎的真实成熟度(非常客观)
| Token 语义 | ✅ 已达 Camunda 级 |
| External Task | ✅ 可生产 |
| 并发模型 | ✅ 正确 |
| DB 驱动 | ✅ 正确 |
| 可恢复性 | ✅ 完整 |
你现在缺的 **不是架构能力**,而是:
- Schema 稳定性验证
- 压测
- 文档
---
## 十二、下一步方向(非常清晰)
### 🥇 **H — History / Trace / Replay**
> 让这个引擎“可解释、可调试、可审计”
### 🥈 **SDK 完整化 + 示例**
> 让别人“用得起来”
---
如果你愿意,我可以下一步直接帮你做其中一个:
- **H**:完整 History / Trace / Replay 设计(很少人会)
- **SDK**:Rust / Python Worker SDK 最终版结构
- **Load**:并发 + crash 压测方案
你只需要回我一个字母。