文档
03 平台04 媒体供应03_设计

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 ResultSpoolEntry

2. 主键与时间

  • 内部主键使用 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 必须已经作为 degraded Binding;
  • 变换只能使用允许枚举;
  • 发布时必须证明变换后请求满足 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 timestamptz

UNIQUE(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_at

supply_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_at

candidates 至少包含:

  • 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 保证。

On this page