运行多租户工作流基础设施
隔离租户身份、Admission、公平认领、持久预算账本、清理所有权与不可变 Tombstone。
租户是安全边界
WorkflowTenantId 参与排队、Claim、Wait、Signal、预算、Task Lookup、Retention 与审计,
不是装饰性 Metadata。每次 Store 操作都必须先按 Tenant 限定,再观察或修改 Workflow
Identity。
可能泄漏存在性时,把跨租户 Lookup 统一成 Not Found。先认证调用者,再把应用身份转换成 Workflow Tenant。
Admission 与公平性
Admission Policy 在 Task 进入系统前限制每个 Tenant 的排队与活跃工作。Claim 顺序应避免 Hot Tenant 独占所有 Worker;全局和每个 Tenant 的 Batch Size 与并发都保持有界。
Backpressure 是产品行为。返回类型化 Overload 或 Quota 结果;不要接受无法持久表示的工作, 然后期待 Worker 最终追上。
持久预算账本
内存 Run Budget 约束一棵因果执行树。多进程 Workflow Budget 还需要持久 Reservation 与 不可变 Audit Fact。Dispatch 前预留,在带栅栏所有权下协调实际用量并 Release 或 Forfeit。
Projection 与 Supervisor 可以把 Ledger 转换成 Metric,但 Metric 不是 Ledger。一次漏采 不能恢复消费权限。
保留控制面
物理清理只处理超过运维 Retention 的 Terminal Task。Cleanup Ownership 也按 Tenant 带 Lease 与 Fencing。Candidate Selection、Tombstone Insert、依赖状态删除与 Task 删除必须 原子完成。
普通 Lookup 返回 Not Found 后,不可变 Tombstone 仍保存 Tenant、Workflow Identity、终止 状态与生命周期时间。删除审计证据需要另一项独立策略。
运维不变量
- 每条存储与授权路径都包含 Tenant Identity;
- 过期 Claim 或 Cleanup Owner 无法写入;
- Queued、Leased 与 Waiting Task 永远不是 Retention Candidate;
- 需要时,预算 Reservation 与 Task Transition 原子提交;
- Task ID 与 Signal ID 本身永远不授予权限;
- Fairness、Quota、Cleanup 与 Projection Batch 都保持有界;
- Audit Fact 比可变运维记录保存得更久。
配置 Tenant Admission
let tenant = WorkflowTenantId::parse("tenant-acme")?;
store
.set_tenant_policy(
tenant.clone(),
WorkflowTenantPolicy::new(
1_000, // 最大 Non-terminal Task
20, // 最大 Concurrent Lease
)?,
)
.await?;
let task = WorkflowTask::new("order-review", 1, input)?
.with_tenant(tenant);
store.enqueue(task).await?;WorkflowTenantId 必须从服务端已认证身份派生,不能直接接受模型或不可信 Request Body
中的值。入队前配置 Policy,拒绝时返回类型化 Overload / Quota Result。按有界 Tenant
Class 统计 Queue Age 与 Active Lease,不要把 Tenant ID 放进 Metric Label。