跳到主要内容

数据库设计

1. 数据库表设计原则

1.1 基础要求

  • 所有表都使用 PostgreSQL 默认的 public schema

  • 所有表名都使用 snake_case 命名风格,采用复数名词

  • 所有表都存在主键,当使用非联合主键时 id 应当为 <prefix>_<ulid> 的形式

  • 所有表都使用 timestamptz 作为时间字段

  • 所有表统一包含:

    • created_at timestamptz not null
    • updated_at timestamptz not null
  • 原则上任何直接或间接归属于 Tenant 的表都必须存在 tenant_id 、任何直接或间接归属于 Route 的表都必须存在 route_id,避免查询时过多联查

1.2 表的分类

  • schema_migrations 表用于存储迁移的执行情况
  • routes 表为路由的入口表,绝大多数功能紧贴 routes 设计
  • tenants 表为租户表,是 routes 的上层表
  • upstream providers 等表为上游配置表,consumers consumer_api_keys 等表为辅助配置表,这些都是归属于租户的资源
  • plugin_configs 表为统一插件配置表,对于无需复杂查询的插件而言将数据存储至其中即足够,这些都是归属于路由的资源

1.3 ID 规范与来源字段

该定义仅供 text 类型主键使用,部分情况下表可能需要选用联合主键或自增数字主键

所有实体主键及其引用字段统一使用 text,格式固定为 <prefix>_<ulid>

约束:

  • <prefix> 尽量控制在 2-3 个字符,极端不超过 5 个字符;
  • <prefix> 必须全局唯一,避免不同表之间产生歧义;
  • <prefix> 应尽量与实体原始单词或词组对应;
  • 同一张表允许存在多种 prefix,只要这些 prefix 能清楚表达差异。
  • 程序侧所有 ID 字段统一使用完整字符串,不拆分保存裸 ULID。

prefix 定义:

实体 / 表prefix说明
tenantstn_tenant
routesrt_route
providersgp_ / tp_global provider / tenant provider
upstreamsups_upstream
provider_pricingsppr_provider pricing
consumerscs_consumer
consumer_api_keyscak_consumer api key
audit_logsaud_audit log
request_logsrql_request log

对于同一张表中可能同时承载 global / tenant / route / provider 等多个来源的数据,除了在 id prefix 上体现来源外,还统一增加两个专门字段:

  • source_scope text not null
  • source_ref_id text not null default ''

示例:

  • 全局数据:source_scope='global'source_ref_id=''
  • tenant 数据:source_scope='tenant'source_ref_id='tn_xxx'
  • route 数据:source_scope='route'source_ref_id='rt_xxx'
  • provider 数据:source_scope='provider'source_ref_id='gp_xxx'tp_xxx

1.4 复杂策略使用 JSONB

以下内容不建议一开始强行拆成高度规范化小表:

  • Guard 规则 DSL
  • 脱敏规则 DSL
  • DLP / egress 规则 DSL
  • protocol adapter 的细节参数
  • fallback 条件细节

这类结构变化快,建议:

  • 元信息关系化;
  • 规则内容用 jsonb
  • 写入时直接落到主业务表中的当前生效结构,必要时做适度冗余。

1.5 Secret 处理

  • 适用范围包括 upstream 凭证,以及 consumer API key 等;
  • 首版本为降低复杂性,数据库中先直接存放明文凭证;
  • 后续版本再引入 KMS / 加密存储;

1.6 高频计数不要直接写 PG

以下运行时高频数据不建议由 router 直接写 PG:

  • QPS/CPS 限速计数

建议:

  • 实时判定用 Redis;
  • usage/event 在主流程写入 PG;
  • Consumer 与 API Key 的 Credit 余额由于要求强一致性,直接存于 RW PG 并在请求结束时扣减;
  • RO PG 负责配置读取/监听;RW PG 负责 Credit 配额余额、usage、汇总、审计、账单;Redis 仅负责 rate limit。

1.7 Credit 记账单位

对外暴露费用统一使用 Credit,内部所有计费、配额、余额、账单数据结构也统一使用 Credit,并且必须为整数。

设计约束:

  • 所有价格、余额、quota、账单金额字段统一使用 bigint
  • 程序中无论是配额还是计费,都只使用 Credit 作为单位,不涉及法币转换;
  • router 和所有数据库表内部不保存浮点金额或法币币种;
  • 法币与 Credit 的换算由业务侧负责,不属于 router 核心职责;
  • 建议默认采用 1 USD = 1_000_000 Credit1 CNY = 1_000_000 Credit
  • 上述换算只是业务接入建议,router 内部只消费和产出整数 Credit。

2. 核心业务表设计

2.1 tenants

租户主表。

字段类型说明
idtext pk主键,格式 tn_<ulid>
nametext not null租户名称
statustext not nullactive / disabled
inbound_rate_limit_qpsint not null default 0当前 tenant 级入口限速字段。[TODO: new plugin system]
detect_rate_limit_cpmint not null default 0当前 Guard detect 侧限速字段。[TODO: new plugin system]
metadatajsonb not null default '{}'扩展字段
created_attimestamptz not null创建时间
updated_attimestamptz not null更新时间

索引:

  • index (status)

2.2 routes

路由主表,只负责入口与治理,不承载唯一 upstream。

字段类型说明
idtext pk主键,格式 rt_<ulid>
tenant_idtext not null fk -> tenants.id所属 tenant
nametext not null路由名称
path_prefixtext not null路由前缀,如 /demo
disabled_attimestamptz null非空表示当前 route 禁用
disable_authboolean not null default falsetrue 时直接跳过认证
protocol_transformation_typetext not null default 'if-different-protocol'协议转换模式:enabled / if-different-protocol / disabled
legacy_bearer_auth_tokenstext[] not null default '{}'route 级 legacy bearer token 列表
passthrough_auth_tokenboolean not null default false当前仍保留;仅作为 deprecated 的 legacy 兼容开关;后续计划移除
groupstext[] not null default '{}'route 级分组约束;空数组表示不限制任何组
capabilitiestext[] not null default '{}'当前 route 级能力开关。[TODO: 移动到其它位置实现]
labelsjsonb not null default '{}'当前 route 级自定义标签
metadatajsonb not null default '{}'扩展字段
created_attimestamptz not null创建时间
updated_attimestamptz not null更新时间

约束/索引:

  • unique (path_prefix)
  • index (tenant_id, disabled_at)
  • index using gin (groups)
  • check (path_prefix ~ '^/[a-zA-Z0-9_-]+$')
  • check (protocol_transformation_type in ('enabled', 'if-different-protocol', 'disabled'))

2.3 providers

Provider 模板表。

Provider 可来源于全局默认或 tenant 自定义。代码本身不内置 provider 类型;运行时只根据 protocol + compat 工作。
其中 protocol 的语义对应 aidy-modelsProvider.api

字段类型说明
idtext pk主键,格式 gp_<ulid>tp_<ulid>
source_scopetext not nullglobal / tenant
source_ref_idtext not null default ''全局为空字符串;tenant 级为 tn_xxx
nametext not null展示名称
descriptiontext null描述
protocoltext not null运行时协议,语义对应 aidy-models.Provider.api
base_urltext null默认 base URL
headersjsonb not null default '{}'默认静态 headers
compatjsonb not null default '{}'默认兼容性配置
check_modeltext nullprovider 自检时使用的模型名
disabled_attimestamptz null非空表示当前 provider 禁用
created_attimestamptz not null创建时间
updated_attimestamptz not null更新时间

约束/索引:

  • unique (source_scope, source_ref_id, name)
  • index (source_scope, source_ref_id, disabled_at)

2.4 upstreams

tenant 下的上游目标。

创建 upstream 时必须选择一个 provider。
upstream 保存当前实际生效的 base_url / headers / compat 和内嵌 api_keys;运行时仍以 provider 的 protocol 决定协议实现。

字段类型说明
idtext pk主键,格式 ups_<ulid>
tenant_idtext not null fk -> tenants.id所属 tenant
provider_idtext not null fk -> providers.id绑定的 provider 模板
nametext not null上游名称
grouptext not null default 'default'upstream 所属分组
base_urltext nullupstream 侧实际使用的 base URL
headersjsonb not null default '{}'upstream 侧实际使用的 headers
compatjsonb not null default '{}'upstream 侧实际使用的 compat 配置
api_keysjsonb not null default '[]'内嵌的 upstream API key 数组
check_modeltext nullupstream 自检时使用的模型名
max_idle_conns_per_hostint null连接池参数
lb_weightint not null default 100默认权重
priorityint not null default 100默认优先级
disabled_attimestamptz null非空表示当前 upstream 禁用
created_attimestamptz not null创建时间
updated_attimestamptz not null更新时间

约束/索引:

  • index (tenant_id, disabled_at)
  • index (tenant_id, provider_id)
  • index (tenant_id, group, disabled_at)

说明:

  • upstreams 直接表达 endpoint、headers、compat、连接池参数与内嵌 API key;
  • api_keys 为空,则表示该 upstream 不需要额外出网凭证;
  • api_keys 非空,运行时当前按稳定顺序选择首个可用 key。

2.5 upstream_models

定义某个 upstream 上可用的模型、映射与别名。

字段类型说明
tenant_idtext not null fk -> tenants.id所属 tenant
upstream_idtext not null fk -> upstreams.id所属 upstream
modeltext not null对外暴露并统一用于路由/计费的模型名
is_aliasboolean not null default false是否为 alias 行
upstream_modeltext not nullupstream 实际调用的模型名
model_featuresjsonb not null default '{}'模型能力补充信息
created_attimestamptz not null创建时间
updated_attimestamptz not null更新时间

约束/索引:

  • primary key (upstream_id, model)
  • index (tenant_id)
  • index (model)

说明:

  • upstream_models 直接承担“模型到 upstream”的映射,不再经过独立的 model_routing_* 表;
  • 若要暴露 alias,就直接再写一行 is_alias = true 的记录。

2.6 provider_pricings

Provider 级定价表。

定价现在直接来源于 provider。
使用内置 provider 时,读取该 global provider 的定价;使用 tenant 自定义 provider 时,读取该 tenant provider 的定价。

字段类型说明
idtext pk主键,格式 ppr_<ulid>
tenant_idtext nulltenant 自定义 provider 时为对应 tn_xxx;global provider 时为空
provider_idtext not null fk -> providers.id所属 provider
modeltext not null对外模型名
pricingjsonb not null default '{}'定价结构,原样存储
created_attimestamptz not null创建时间
updated_attimestamptz not null更新时间

约束/索引:

  • unique (provider_id, model)
  • index (tenant_id)
  • index (provider_id)

2.7 consumers

tenant 下的调用方实体。

字段类型说明
idtext pk主键,格式 cs_<ulid>
tenant_idtext not null fk -> tenants.id所属 tenant
nametext not null名称
statustext not nullactive / disabled
unlimited_creditboolean not null default falsetrue 时表示不限制使用额度
remaining_creditbigint not null default 0当前剩余 Credit
used_creditbigint not null default 0历史累计总用量
metadatajsonb not null default '{}'扩展字段
groupstext[] not null default '{}'可用组约束;空数组表示不限组
created_attimestamptz not null创建时间
updated_attimestamptz not null更新时间

约束/索引:

  • index (tenant_id, status)
  • index using gin (groups)

2.8 consumer_api_keys

API Key 明细表。

字段类型说明
idtext pk主键,格式 cak_<ulid>
tenant_idtext not null fk -> tenants.id所属 tenant
consumer_idtext not null fk -> consumers.id所属 consumer
nametext not null自定义名称
keytext not null完整 API key,运行时直接按该值查找
disabled_attimestamptz null置非空后,该 key 立即视为禁用
expires_attimestamptz null过期时间
revoked_attimestamptz null吊销时间
unlimited_creditboolean not null default falsetrue 时表示不限制使用额度
remaining_creditbigint not null default 0当前剩余 Credit
used_creditbigint not null default 0历史累计总用量
groupstext[] not null default '{}'可用组约束;空数组表示不限组
last_used_attimestamptz null最近使用时间
created_attimestamptz not null创建时间
updated_attimestamptz not null更新时间

索引:

  • unique (key)
  • unique (consumer_id, name)
  • index (tenant_id)
  • index (consumer_id)
  • index using gin (groups)
  • index (disabled_at)
  • index (expires_at)
  • index (revoked_at)

2.9 plugin_configs

字段类型说明
tenant_idtext not null所属 tenant
route_idtext not null所属 route
plugin_keytext not null插件主键,如 guard / rate_limit
plugin_sub_keytext not null default ''插件子键;空字符串表示无子键
configjsonb not null default '{}'当前插件配置 JSON
created_attimestamptz not null创建时间
updated_attimestamptz not null更新时间

约束/索引:

  • primary key (route_id, plugin_key, plugin_sub_key)
  • index (tenant_id)
  • index (route_id)
  • index (route_id, plugin_key)

2.10 route_allowed_consumers

route 对 consumer 的白名单。

若某个 route 在该表中没有任何记录,表示 默认允许该 tenant 下所有 consumers

字段类型说明
tenant_idtext not null fk -> tenants.id所属 tenant
route_idtext not null fk -> routes.idroute
consumer_idtext not null fk -> consumers.idconsumer
created_attimestamptz not null创建时间
updated_attimestamptz not null更新时间

约束:

  • primary key (route_id, consumer_id)
  • index (tenant_id)

2.11 route_allowed_models

route 对 model 的白名单。

字段类型说明
tenant_idtext not null fk -> tenants.id所属 tenant
route_idtext not null fk -> routes.idroute
model_nametext not null允许的统一模型名
created_attimestamptz not null创建时间
updated_attimestamptz not null更新时间

约束:

  • primary key (route_id, model_name)
  • index (tenant_id)

2.12 audit_logs

审计与追责基础表。

字段类型说明
idtext pk主键,格式 aud_<ulid>
tenant_idtext not null fk -> tenants.id所属 tenant
actortext not null操作人/系统
actiontext not nullcreate / update / delete
resource_typetext not nulltenant / route / upstream / ...
resource_idtext null对应实体 ID
change_summaryjsonb not null default '{}'变更摘要
created_attimestamptz not null操作时间
updated_attimestamptz not null更新时间

索引:

  • index (tenant_id, created_at)

2.13 request_logs

请求日志摘要表。

该表只存摘要,不存 request / response body。
route_labels 也不落库,只保留在 Kafka 事件中。

字段类型说明
idtext pk主键,格式 rql_<ulid>
tenant_idtext not null default ''所属 tenant;未知 route / 无 tenant 时为空字符串
route_idtext not nullroute id
route_nametext not nullroute name
request_idtext not null请求 ID
requested_modeltext null请求体中的 model 字段
remote_addrtext not null default ''请求来源地址
requestjsonb not null default '{}'入口请求摘要,仅含 url/path/method/header
responsejsonb not null default '{}'出口响应摘要,仅含 code/header
upstream_requestsjsonb not null default '[]'上游尝试数组;每项包含 requestresponsemeta
timingjsonb not null default '{}'时间点信息
durationjsonb not null default '{}'耗时信息
errortext null内部错误字符串
additional_datajsonb not null default '{}'扩展字段;当前主要存 guard 结果
created_attimestamptz not null创建时间
updated_attimestamptz not null更新时间

说明:

  • upstream_requests 按真实尝试顺序保存多个上游 request/response 对
  • meta 当前至少包括:attempt_indexupstream_idupstream_nameupstream_api_key_idprovider_protocolfinalerror

索引:

  • index (tenant_id, created_at)
  • index (route_id, created_at)
  • index (request_id)
  • index (requested_model)