MG-03-005 数据模型
状态说明:以当前有效边界为准。供应执行规则与历史实现证据保留;客户身份、售价、结算、全量旧跨仓契约及图像开放计划不代表当前验收范围。
1. 关系总览
CustomerGroup 1─N User 1─N APIKey
CustomerGroup N─N PublicModel
Channel 1─N CredentialVersion
Channel 1─N Supply
Supply 1─1 ImageCapability | VideoCapability
Supply 1─N CostRuleVersion
Supply 1─1 SupplyHealth
PublicModel N─N Supply through SupplyBinding
PublicModel 1─1 DispatchPolicy
PublicModel 1─N DegradationRule
MediaRequest 0─1 AsyncTask
MediaRequest 1─N Attempt
MediaRequest 1─N MediaOutput
Attempt N─1 Supply
Attempt 0─N ResultSpoolEntry2. 主键与时间
- 内部主键使用 PostgreSQL
uuid; - 应用生成 UUIDv7,避免数据库序列暴露规模;
- 用户可见 ID 使用稳定前缀,例如
req_、task_、out_; - 所有时间使用 UTC
timestamptz; - API 根据客户端或用户设置转换显示时区。
3. 关键约束
Channel 与 Credential
- Channel 名称在租户管理域内唯一;
- Channel
media_type只能是image/video,创建后不可修改; - Supply 使用
(channel_id, media_type)外键保证不会跨媒体挂载; - 同一 Channel 只能有一个 active Credential Version;
- Credential 密文、密钥版本和创建时间不可为空;
- Credential 切换不修改历史 Attempt。
Supply
唯一约束:
UNIQUE(channel_id, upstream_model, request_profile, variant_key)检查约束:
media_type只能是image/video;concurrency_limit >= 0;production_ready=true时必须存在已验证 Capability、有效 Credential、 有效 Cost Rule 和成功测试证据;- Image Supply 只能关联 ImageCapability;
- Video Supply 只能关联 VideoCapability。
SupplyBinding
UNIQUE(public_model_id, supply_id)
role IN ('exact', 'fallback', 'degraded')Binding 不包含能力、成本或 Provider 模型字段。
DispatchPolicy
- 每个 Public Model 一条;
max_attempts首版固定为 2;retry_deadline_seconds首版固定为 1200;minimum_success_rate在 0 与 1 之间;- 成本倍率和绝对上限必须为正数。
DegradationRule
target_supply_id必须已经作为degradedBinding;- 变换只能使用允许枚举;
- 发布时必须证明变换后请求满足 Target Supply 能力;
- 每个步骤顺序唯一;
- 未验证规则不能启用。
4. 请求与任务
media_requests
建议字段:
id uuid primary key
public_id text unique
request_id text
user_id uuid
api_key_id uuid
customer_group_id uuid
media_type text
execution_mode text
public_model_id uuid
request_schema_version integer
normalized_request jsonb
status text
degraded boolean
error_category text
error_code text
created_at timestamptz
started_at timestamptz
completed_at timestamptz索引:
(user_id, created_at DESC);(api_key_id, created_at DESC);(public_model_id, created_at DESC);(media_type, status, created_at DESC);- 管理员队列查询使用部分索引。
渠道模型统计读取 attempts(supply_id, started_at DESC) 与
media_requests(public_model_id, created_at DESC);查询窗口最长 31 天。24 小时使用小时
桶,7/30 天使用日桶,不为图表创建第二份可漂移统计事实。
async_tasks
id uuid primary key
public_id text unique
media_request_id uuid unique
status text
attempt_count integer
max_attempts integer
retry_deadline_at timestamptz
next_action_at timestamptz
lease_owner text
lease_expires_at timestamptz
created_at timestamptz
completed_at timestamptz同步图像没有该行。
attempts
id uuid primary key
media_request_id uuid
async_task_id uuid null
supply_id uuid
channel_id uuid
credential_version_id uuid
cost_rule_version_id uuid
upstream_task_id text null
status text
request_snapshot jsonb
routing_snapshot jsonb
estimated_cost numeric
actual_cost numeric null
currency text
error_category text null
error_scope text null
error_code text null
started_at timestamptz
finished_at timestamptz null约束:
- 同一异步 Task 的 Attempt 序号唯一;
- Attempt 一旦得到 Provider Task ID,Supply 和 Credential 不可变;
- 实际成本非负;
- 终态更新幂等;
- 首版数据库约束或事务逻辑阻止第三次 Attempt。
result_spool_entries
仅用于异步结果已经从 Provider 临时位置安全落盘、但尚未完成平台对象存储发布的状态:
output_id uuid primary key
asset_id uuid
output_public_id text unique
media_request_id uuid
attempt_id uuid
media_type text
output_index integer
spool_key text unique
content_type text
byte_size bigint
sha256 text
failure_count integer
first_failed_at timestamptz null
last_failed_at timestamptz null
created_at timestamptz
updated_at timestamptzUNIQUE(attempt_id, output_index) 固定每个输出身份。表内禁止保存 Provider 临时 URL、
Base64、Credential 和媒体正文;正文只存在私有 Result Spool 命名卷。达到配置的失败
次数后不再访问对象存储,等待终态补偿和管理员处理。
5. 幂等
独立 idempotency_keys:
api_key_id
idempotency_key_hash
request_fingerprint
media_request_id
expires_at唯一约束:
UNIQUE(api_key_id, idempotency_key_hash)请求指纹基于规范化请求,不包含 Authorization 和短期签名 URL。
6. 健康
channel_health
channel_id
state
reason_code
consecutive_failures
window_successes
window_failures
opened_until
last_success_at
last_failure_at
version
updated_atsupply_health
字段同上,并增加:
supply_id
available_capacity
cooldown_until每次状态变化追加 health_events,实时状态可以覆盖更新,但历史事件不可覆盖。
7. 容量租约
Capacity Lease 必须同时检查 Channel 和 Supply:
capacity_leases
id
channel_id
supply_id
media_request_id
attempt_id
state
expires_at
released_at生产实现可以使用 Redis 原子脚本加速,但 PostgreSQL 保存 Attempt 与租约关联和恢复 事实。过期租约由 Reconciler 幂等释放。
8. 成本和报价
Cost Rule 使用 numeric 或整数最小货币单位,不使用 float。
每个路由轮次保存:
routing_decisions
media_request_id
round
policy_version
request_snapshot
candidates
selected_supply_id
selected_quote
baseline_quote
selection_reason
created_atcandidates 至少包含:
- Supply ID;
- 包含或排除;
- 排除原因;
- Health Version;
- Capacity Version;
- Quote;
- Binding Role;
- 降级步骤。
9. 输出资产
media_assets
id
media_type
content_type
byte_size
sha256
status
expires_at
asset_locations
asset_id
storage_backend
bucket_role
object_key
status
created_at短期签名 URL 不入库。用户查询使用 MediaOutput → MediaAsset → /content。
10. 保留与分区
首版建议:
- 请求、任务和 Attempt 至少保留 180 天;
- 正式输出按产品策略保留,默认值在核心设置中配置;
- 健康明细事件可按月归档;
- 审计记录至少保留 1 年。
是否分区由验收负载和预计数据量决定。若启用分区,幂等唯一性必须继续由独立、 非时间分区 Registry 保证。