数据库设计
1. 数据库表设计原则
1.1 基础要求
所有表都使用 PostgreSQL 默认的
publicschema所有表名都使用
snake_case命名风格,采用复数名词所有表都存在主键,当使用非联合主键时 id 应当为
<prefix>_<ulid>的形式所有表都使用
timestamptz作为时间字段所有表统一包含:
created_at timestamptz not nullupdated_at timestamptz not null
原则上任何直接或间接归属于 Tenant 的表都必须存在
tenant_id、任何直接或间接归属于 Route 的表都必须存在route_id,避免查询时过多联查
1.2 表的分类
schema_migrations表用于存储迁移的执行情况routes表为路由的入口表,绝大多数功能紧贴routes设计tenants表为租户表,是routes的上层表upstreamproviders等表为上游配置表,consumersconsumer_api_keys等表为辅助配置表,这些都是归属于租户的资源plugin_configs表为统一插件配置表,对于无需复杂查询的插件而言将数据存储至其中即足够,这些都是归属于路由的资源
1.3 ID 规范与来源字段
该定义仅供 text 类型主键使用,部分情况下表可能需要选用联合主键或自增数字主键
所有实体主键及其引用字段统一使用 text,格式固定为 <prefix>_<ulid>
约束:
<prefix>尽量控制在 2-3 个字符,极端不超过 5 个字符;<prefix>必须全局唯一,避免不同表之间产生歧义;<prefix>应尽量与实体原始单词或词组对应;- 同一张表允许存在多种 prefix,只要这些 prefix 能清楚表达差异。
- 程序侧所有 ID 字段统一使用完整字符串,不拆分保存裸 ULID。
prefix 定义:
| 实体 / 表 | prefix | 说明 |
|---|---|---|
tenants | tn_ | tenant |
routes | rt_ | route |
providers | gp_ / tp_ | global provider / tenant provider |
upstreams | ups_ | upstream |
provider_pricings | ppr_ | provider pricing |
consumers | cs_ | consumer |
consumer_api_keys | cak_ | consumer api key |
audit_logs | aud_ | audit log |
request_logs | rql_ | request log |
对于同一张表中可能同时承载 global / tenant / route / provider 等多个来源的数据,除了在 id prefix 上体现来源外,还统一增加两个专门字段:
source_scope text not nullsource_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 Credit、1 CNY = 1_000_000 Credit; - 上述换算只是业务接入建议,router 内部只消费和产出整数 Credit。
2. 核心业务表设计
2.1 tenants
租户主表。
| 字段 | 类型 | 说明 |
|---|---|---|
id | text pk | 主键,格式 tn_<ulid> |
name | text not null | 租户名称 |
status | text not null | active / disabled |
inbound_rate_limit_qps | int not null default 0 | 当前 tenant 级入口限速字段。[TODO: new plugin system] |
detect_rate_limit_cpm | int not null default 0 | 当前 Guard detect 侧限速字段。[TODO: new plugin system] |
metadata | jsonb not null default '{}' | 扩展字段 |
created_at | timestamptz not null | 创建时间 |
updated_at | timestamptz not null | 更新时间 |
索引:
index (status)
2.2 routes
路由主表,只负责入口与治理,不承载唯一 upstream。
| 字段 | 类型 | 说明 |
|---|---|---|
id | text pk | 主键,格式 rt_<ulid> |
tenant_id | text not null fk -> tenants.id | 所属 tenant |
name | text not null | 路由名称 |
path_prefix | text not null | 路由前缀,如 /demo |
disabled_at | timestamptz null | 非空表示当前 route 禁用 |
disable_auth | boolean not null default false | 为 true 时直接跳过认证 |
protocol_transformation_type | text not null default 'if-different-protocol' | 协议转换模式:enabled / if-different-protocol / disabled |
legacy_bearer_auth_tokens | text[] not null default '{}' | route 级 legacy bearer token 列表 |
passthrough_auth_token | boolean not null default false | 当前仍保留;仅作为 deprecated 的 legacy 兼容开关;后续计划移除 |
groups | text[] not null default '{}' | route 级分组约束;空数组表示不限制任何组 |
capabilities | text[] not null default '{}' | 当前 route 级能力开关。[TODO: 移动到其它位置实现] |
labels | jsonb not null default '{}' | 当前 route 级自定义标签 |
metadata | jsonb not null default '{}' | 扩展字段 |
created_at | timestamptz not null | 创建时间 |
updated_at | timestamptz 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-models中Provider.api。
| 字段 | 类型 | 说明 |
|---|---|---|
id | text pk | 主键,格式 gp_<ulid> 或 tp_<ulid> |
source_scope | text not null | global / tenant |
source_ref_id | text not null default '' | 全局为空字符串;tenant 级为 tn_xxx |
name | text not null | 展示名称 |
description | text null | 描述 |
protocol | text not null | 运行时协议,语义对应 aidy-models.Provider.api |
base_url | text null | 默认 base URL |
headers | jsonb not null default '{}' | 默认静态 headers |
compat | jsonb not null default '{}' | 默认兼容性配置 |
check_model | text null | provider 自检时使用的模型名 |
disabled_at | timestamptz null | 非空表示当前 provider 禁用 |
created_at | timestamptz not null | 创建时间 |
updated_at | timestamptz 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决定协议实现。
| 字段 | 类型 | 说明 |
|---|---|---|
id | text pk | 主键,格式 ups_<ulid> |
tenant_id | text not null fk -> tenants.id | 所属 tenant |
provider_id | text not null fk -> providers.id | 绑定的 provider 模板 |
name | text not null | 上游名称 |
group | text not null default 'default' | upstream 所属分组 |
base_url | text null | upstream 侧实际使用的 base URL |
headers | jsonb not null default '{}' | upstream 侧实际使用的 headers |
compat | jsonb not null default '{}' | upstream 侧实际使用的 compat 配置 |
api_keys | jsonb not null default '[]' | 内嵌的 upstream API key 数组 |
check_model | text null | upstream 自检时使用的模型名 |
max_idle_conns_per_host | int null | 连接池参数 |
lb_weight | int not null default 100 | 默认权重 |
priority | int not null default 100 | 默认优先级 |
disabled_at | timestamptz null | 非空表示当前 upstream 禁用 |
created_at | timestamptz not null | 创建时间 |
updated_at | timestamptz 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_id | text not null fk -> tenants.id | 所属 tenant |
upstream_id | text not null fk -> upstreams.id | 所属 upstream |
model | text not null | 对外暴露并统一用于路由/计费的模型名 |
is_alias | boolean not null default false | 是否为 alias 行 |
upstream_model | text not null | upstream 实际调用的模型名 |
model_features | jsonb not null default '{}' | 模型能力补充信息 |
created_at | timestamptz not null | 创建时间 |
updated_at | timestamptz 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 的定价。
| 字段 | 类型 | 说明 |
|---|---|---|
id | text pk | 主键,格式 ppr_<ulid> |
tenant_id | text null | tenant 自定义 provider 时为对应 tn_xxx;global provider 时为空 |
provider_id | text not null fk -> providers.id | 所属 provider |
model | text not null | 对外模型名 |
pricing | jsonb not null default '{}' | 定价结构,原样存储 |
created_at | timestamptz not null | 创建时间 |
updated_at | timestamptz not null | 更新时间 |
约束/索引:
unique (provider_id, model)index (tenant_id)index (provider_id)
2.7 consumers
tenant 下的调用方实体。
| 字段 | 类型 | 说明 |
|---|---|---|
id | text pk | 主键,格式 cs_<ulid> |
tenant_id | text not null fk -> tenants.id | 所属 tenant |
name | text not null | 名称 |
status | text not null | active / disabled |
unlimited_credit | boolean not null default false | 为 true 时表示不限制使用额度 |
remaining_credit | bigint not null default 0 | 当前剩余 Credit |
used_credit | bigint not null default 0 | 历史累计总用量 |
metadata | jsonb not null default '{}' | 扩展字段 |
groups | text[] not null default '{}' | 可用组约束;空数组表示不限组 |
created_at | timestamptz not null | 创建时间 |
updated_at | timestamptz not null | 更新时间 |
约束/索引:
index (tenant_id, status)index using gin (groups)
2.8 consumer_api_keys
API Key 明细表。
| 字段 | 类型 | 说明 |
|---|---|---|
id | text pk | 主键,格式 cak_<ulid> |
tenant_id | text not null fk -> tenants.id | 所属 tenant |
consumer_id | text not null fk -> consumers.id | 所属 consumer |
name | text not null | 自定义名称 |
key | text not null | 完整 API key,运行时直接按该值查找 |
disabled_at | timestamptz null | 置非空后,该 key 立即视为禁用 |
expires_at | timestamptz null | 过期时间 |
revoked_at | timestamptz null | 吊销时间 |
unlimited_credit | boolean not null default false | 为 true 时表示不限制使用额度 |
remaining_credit | bigint not null default 0 | 当前剩余 Credit |
used_credit | bigint not null default 0 | 历史累计总用量 |
groups | text[] not null default '{}' | 可用组约束;空数组表示不限组 |
last_used_at | timestamptz null | 最近使用时间 |
created_at | timestamptz not null | 创建时间 |
updated_at | timestamptz 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_id | text not null | 所属 tenant |
route_id | text not null | 所属 route |
plugin_key | text not null | 插件主键,如 guard / rate_limit |
plugin_sub_key | text not null default '' | 插件子键;空字符串表示无子键 |
config | jsonb not null default '{}' | 当前插件配置 JSON |
created_at | timestamptz not null | 创建时间 |
updated_at | timestamptz 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_id | text not null fk -> tenants.id | 所属 tenant |
route_id | text not null fk -> routes.id | route |
consumer_id | text not null fk -> consumers.id | consumer |
created_at | timestamptz not null | 创建时间 |
updated_at | timestamptz not null | 更新时间 |
约束:
primary key (route_id, consumer_id)index (tenant_id)
2.11 route_allowed_models
route 对 model 的白名单。
| 字段 | 类型 | 说明 |
|---|---|---|
tenant_id | text not null fk -> tenants.id | 所属 tenant |
route_id | text not null fk -> routes.id | route |
model_name | text not null | 允许的统一模型名 |
created_at | timestamptz not null | 创建时间 |
updated_at | timestamptz not null | 更新时间 |
约束:
primary key (route_id, model_name)index (tenant_id)
2.12 audit_logs
审计与追责基础表。
| 字段 | 类型 | 说明 |
|---|---|---|
id | text pk | 主键,格式 aud_<ulid> |
tenant_id | text not null fk -> tenants.id | 所属 tenant |
actor | text not null | 操作人/系统 |
action | text not null | create / update / delete |
resource_type | text not null | tenant / route / upstream / ... |
resource_id | text null | 对应实体 ID |
change_summary | jsonb not null default '{}' | 变更摘要 |
created_at | timestamptz not null | 操作时间 |
updated_at | timestamptz not null | 更新时间 |
索引:
index (tenant_id, created_at)
2.13 request_logs
请求日志摘要表。
该表只存摘要,不存 request / response body。
route_labels也不落库,只保留在 Kafka 事件中。
| 字段 | 类型 | 说明 |
|---|---|---|
id | text pk | 主键,格式 rql_<ulid> |
tenant_id | text not null default '' | 所属 tenant;未知 route / 无 tenant 时为空字符串 |
route_id | text not null | route id |
route_name | text not null | route name |
request_id | text not null | 请求 ID |
requested_model | text null | 请求体中的 model 字段 |
remote_addr | text not null default '' | 请求来源地址 |
request | jsonb not null default '{}' | 入口请求摘要,仅含 url/path/method/header |
response | jsonb not null default '{}' | 出口响应摘要,仅含 code/header |
upstream_requests | jsonb not null default '[]' | 上游尝试数组;每项包含 request、response、meta |
timing | jsonb not null default '{}' | 时间点信息 |
duration | jsonb not null default '{}' | 耗时信息 |
error | text null | 内部错误字符串 |
additional_data | jsonb not null default '{}' | 扩展字段;当前主要存 guard 结果 |
created_at | timestamptz not null | 创建时间 |
updated_at | timestamptz not null | 更新时间 |
说明:
upstream_requests按真实尝试顺序保存多个上游 request/response 对meta当前至少包括:attempt_index、upstream_id、upstream_name、upstream_api_key_id、provider_protocol、final、error
索引:
index (tenant_id, created_at)index (route_id, created_at)index (request_id)index (requested_model)