跳到主要内容

数据库设计

数据库表设计原则

基础要求

  • 所有表都使用 PostgreSQL 默认的 public schema
  • 所有表名都使用 snake_case 命名风格,采用复数名词
  • 所有表都存在主键,当使用非联合主键时 id 应当为 <prefix>_<ulid> 的形式
  • 所有时间字段都使用 timestamptz
  • 所有表统一包含:
    • created_at timestamptz not null
    • updated_at timestamptz not null
  • 不做软删除(deleted_at
  • 原则上任何直接或间接归属于 Tenant 的表都必须存在 tenant_id 、任何直接或间接归属于 Route 的表都必须存在 route_id,避免查询时过多联查

表的分类

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

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
credit_ledger_entriescle_credit ledger entry

对于同一张表中可能同时承载 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

复杂策略使用 JSONB

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

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

这类结构变化快,建议:

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

补充约束:

  • db/schema/index.ts 里原则上所有 jsonb 列都应声明明确的 .$type<...>()
  • jsonb 类型优先对应正式 protobuf message 的 ProtoJSON(统一使用 use_proto_name / snake_case)
  • 少数确认不补 proto 的字段,也必须有文档化的手写类型
  • 当前明确保留为宽类型的例外只有 plugin_configs.config,因为它按 plugin_key / plugin_sub_key 承载异构 JSON

Secret 处理

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

高频计数不要直接写 PG

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

  • QPS/CPS 限速计数

建议:

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

核心业务表

tenants

租户主表。

字段类型说明
idtext pk主键,格式 tn_<ulid>
nametext not null租户名称
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]
allow_missing_model_pricingboolean not null default falsealias 映射后的真实上游模型未配置 provider pricing 时,是否允许请求继续
created_attimestamptz not null创建时间
updated_attimestamptz not null更新时间

routes

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

字段类型说明
idtext pk主键,格式 rt_<ulid>
tenant_idtext not null fk -> tenants.id所属 tenant
nametext not null路由名称
path_prefixtext not null路由前缀,如 /demo
max_attemptsint not null default 2单次请求最多尝试的候选数,最小为 1
disabled_attimestamptz null非空表示当前 route 禁用
disable_authboolean not null default falsetrue 时直接跳过认证
protocol_transformation_typetext not null default 'if-different-protocol'协议转换模式:forced / if-different-protocol / disabled
legacy_bearer_auth_tokenstext[] not null default '{}'route 级 legacy bearer token 列表
passthrough_auth_tokenboolean not null default false当前仍保留;仅作为 deprecated 的 legacy 兼容开关;后续计划移除
allow_missing_model_pricingboolean not null default falsealias 映射后的真实上游模型未配置 provider pricing 时,是否允许请求继续
labelsjsonb not null default '{}'route label 集合,用于 consumer / api key 的 route_selector 匹配;语义上是扁平 string -> string 映射
upstream_selectorjsonb not null default '{}'route 级 upstream label selector
model_selectorjsonb not null default '{}'route 级 upstream model label selector
created_attimestamptz not null创建时间
updated_attimestamptz not null更新时间

约束/索引:

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

route_upstreams

route 与 upstream 的显式绑定表。

字段类型说明
route_idtext pk-part所属 route
upstream_idtext pk-part绑定的 upstream
priorityint not null default 0route 级优先级,数值越小越优先
lb_weightint not null default 100route 级负载均衡权重
created_attimestamptz not null创建时间
updated_attimestamptz not null更新时间

约束/索引:

  • primary key (route_id, upstream_id)
  • index (route_id, priority)
  • index (upstream_id)

providers

Provider 模板表。

Provider 可来源于全局默认或 tenant 自定义。运行时按 protocolcompatrequest_header_overrides 分层工作:compat 只提供协议层配置,request_header_overrides 只提供网关层 request header 覆盖。
其中 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
proxy_urltext null默认出站代理 URL,支持 http://socks5://
headersjsonb not null default '{}'默认静态 headers;语义上必须是扁平 string -> string 映射
compatjsonb not null default '{}'默认兼容性配置;按 protocol 对应 aidy.v2.protocol.* 的 ProtoJSON
request_header_overridesjsonb not null default '{}'默认 request header override 迷你 DSL;这是刻意不补 proto 的手写 JSON 类型
capabilitiestext[] not null default '{}'provider 默认 forwarder 能力集合;空数组表示按 protocol 推导默认集
missing_usage_policytext not null default 'estimate'上游未返回 usage 时的计费策略:estimate 估算并计费,fail 只让计费/结算失败
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)

upstreams

tenant 下的上游目标。

创建 upstream 时必须选择一个 provider。
upstream 保存当前实际生效的 base_url / headers / compat / request_header_overrides 和内嵌 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上游名称
base_urltext nullupstream 侧实际使用的 base URL
proxy_urltext nullupstream 侧出站代理覆盖;为空继承 provider,- 表示显式直连
headersjsonb not null default '{}'upstream 侧实际使用的 headers;语义上必须是扁平 string -> string 映射
compatjsonb not null default '{}'upstream 侧实际使用的 compat 配置;按 provider protocol 对应 aidy.v2.protocol.* 的 ProtoJSON
request_header_overridesjsonb not null default '{}'upstream 侧实际使用的 request header override 迷你 DSL;这是刻意不补 proto 的手写 JSON 类型
capabilitiestext[] not null default '{}'upstream forwarder 能力覆盖;空数组表示继承 provider 默认值
api_keysjsonb not null default '[]'内嵌的 core.UpstreamAPIKey protobuf JSON 数组
labelsjsonb not null default '{}'upstream label 集合,用于 selector 匹配;语义上是扁平 string -> string 映射
max_idle_conns_per_hostint null连接池参数
missing_usage_policytext null上游未返回 usage 时的计费策略;为空继承 provider
disabled_attimestamptz null非空表示当前 upstream 禁用
created_attimestamptz not null创建时间
updated_attimestamptz not null更新时间

约束/索引:

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

说明:

  • upstreams 直接表达 endpoint、proxy_url、headers、compat、request_header_overrides、连接池参数、forwarder 能力覆盖与内嵌 API key; 其中 headers 只表示静态单值 header map,不承载任意 JSON 结构;
  • proxy_url 支持 http://socks5://upstream.proxy_url 为空时继承 provider,写成 - 时禁用 provider 代理并直连;
  • api_keys 为空,则表示该 upstream 不需要额外出网凭证;
  • api_keys 非空,运行时会把每个 id/key 展开成独立候选;
  • upstream 自身不再承载路由优先级与权重;这两项由 route_upstreams 在 route 级定义。
  • request_header_overrides 当前不支持覆盖或透传 HostHost 始终由最终 upstream URL 决定。

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_modeltextalias 行实际发往 upstream 的模型名;is_alias=false 时保持 NULL,运行时使用 model
labelsjsonb not null default '{}'upstream model label 集合,用于 selector 匹配;语义上是扁平 string -> string 映射
created_attimestamptz not null创建时间
updated_attimestamptz not null更新时间

约束/索引:

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

说明:

  • upstream_models 直接承担“模型到 upstream”的映射,不再经过独立的 model_routing_* 表;
  • upstream_models 没有独立 id,始终使用 (upstream_id, model) 作为联合主键与资源定位键;
  • 若要暴露 alias,就直接再写一行 is_alias = true 的记录。

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 '{}'定价结构;存储为 ext.billing.Pricing 的 canonical ProtoJSON
created_attimestamptz not null创建时间
updated_attimestamptz not null更新时间

约束/索引:

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

pricing 当前约定为如下 JSON:

{
"base_pricing": {
"text_input": 500,
"text_output": 1500
},
"adjustments": [
{
"mode": "ADJUSTMENT_MODE_MULTIPLIER",
"when": {
"service_tier": "priority"
},
"values": {
"text_input": 2,
"text_output": 2
}
}
]
}
  • 固定只支持 millionTokens 口径,因此不再写 unit
  • 所有数字都表示每 1,000,000 tokens 的 credit
  • pricing 使用 ext.billing.Pricing 的 canonical protobuf JSON
  • pricing 始终使用 protobuf JSON 的 snake_case key
  • base_pricing / adjustments.values 只支持文本计费四项:text_inputtext_outputtext_input_cache_readtext_input_cache_write
  • 当前运行时结算只读取 basePricing,完全忽略 adjustments
  • textInput 的运行时计费口径为 max(input_tokens - cached_read_tokens - cached_creation_tokens, 0)
  • textOutput 直接使用 output_tokens
  • textInputCacheRead 直接使用 cached_read_tokens
  • textInputCacheWrite 直接使用 cached_creation_tokens
  • 四项原始费用求和后统一做四舍五入,0.5 向上,得到最终整数 Credit

consumers

tenant 下的调用方实体。

字段类型说明
idtext pk主键,格式 cs_<ulid>
tenant_idtext not null fk -> tenants.id所属 tenant
nametext not null名称
disabled_attimestamptz null非空表示当前 consumer 禁用
unlimited_creditboolean not null default falsetrue 时表示不限制使用额度
remaining_creditbigint not null default 0当前剩余 Credit
used_creditbigint not null default 0历史累计总用量
route_selectorjsonb not null default '{}'consumer 级 route label selector;存储为 core.LabelSelector ProtoJSON
upstream_selectorjsonb not null default '{}'consumer 级 upstream label selector;存储为 core.LabelSelector ProtoJSON
model_selectorjsonb not null default '{}'consumer 级 upstream model label selector;存储为 core.LabelSelector ProtoJSON
created_attimestamptz not null创建时间
updated_attimestamptz not null更新时间

约束/索引:

  • index (tenant_id, disabled_at)

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历史累计总用量
route_selectorjsonb not null default '{}'api key 级 route label selector;存储为 core.LabelSelector ProtoJSON
upstream_selectorjsonb not null default '{}'api key 级 upstream label selector;存储为 core.LabelSelector ProtoJSON
model_selectorjsonb not null default '{}'api key 级 upstream model label selector;存储为 core.LabelSelector ProtoJSON
last_used_attimestamptz null最近使用时间
created_attimestamptz not null创建时间
updated_attimestamptz not null更新时间

索引:

  • unique (key)
  • index (tenant_id)
  • index (consumer_id)
  • index (disabled_at)
  • index (expires_at)
  • index (revoked_at)

plugin_configs

字段类型说明
tenant_idtext not null所属 tenant
route_idtext not null所属 route
plugin_keytext not null插件主键,如 guard / inbound_rate_limit
plugin_sub_keytext not null default ''插件子键;空字符串表示无子键
configjsonb not null default '{}'当前插件配置 JSON;这是少数刻意保留宽类型的字段,按 plugin_key / plugin_sub_key 承载异构 payload
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)

request_logs

请求日志摘要表。

该表只存摘要,不存 request / response body。
可选的 raw 列只在显式开启 plugins.log.store_full_body_in_pg 时保存完整 protojson。

字段类型说明
idtext pk主键,与 request_id 相同
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 ''请求来源地址
session_idtext nullroute 级 session 插件提取到的会话 ID
requestjsonb not null default '{}'入口请求摘要;存储为 logging.v1.HttpRequest ProtoJSON
responsejsonb not null default '{}'出口响应摘要;存储为 logging.v1.HttpResponse ProtoJSON
upstream_requestsjsonb not null default '[]'上游尝试数组;每项是 logging.v1.UpstreamRequest ProtoJSON
timingjsonb not null default '{}'时间点信息;语义上是 Record<string, number>,不单独补 proto
durationjsonb not null default '{}'耗时信息;语义上是 Record<string, number>,不单独补 proto
errortext null内部错误字符串
ext_fieldsjsonb not null default '{}'扩展字段;顶层是显式对象,当前已知 key、proto 与对应文档索引见 日志扩展字段
rawjsonb null可选完整日志 protojson;直接存 logging.v1.RequestLog 的完整 ProtoJSON,未开启完整日志落库时为 null
created_attimestamptz not null创建时间
updated_attimestamptz not null更新时间

说明:

  • upstream_requests 按真实尝试顺序保存多个上游 request/response 对
  • requested_model 保留客户端原始请求模型;真实发往上游的模型记录在 upstream_requests[*].request.model
  • session_id 只来自 route 级 session 插件;未配置插件或未提取到时为 null / 空字段
  • request / response / upstream_requests[*] 都遵循 aidy.v2.logging.v1 的 ProtoJSON,统一使用 snake_case key
  • upstream_requests[*].meta 也来自 logging.v1.UpstreamRequestMeta 的强类型 ProtoJSON
  • meta 当前至少包括:attempt_indexupstream_idupstream_nameupstream_api_key_idprovider_protocolfinalerror
  • rawnull 表示未开启完整日志落库
  • ext_fields 的当前已知 key / proto 索引见 日志扩展字段;具体字段解释见对应插件文档

索引:

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

约束:

  • check (id = request_id)

credit_ledger_entries

Credit 不可变账本表。

consumers.remaining_credit / used_creditconsumer_api_keys.remaining_credit / used_credit 只是余额快照。
真正用于审计、追溯与请求级去重的是 credit_ledger_entries

字段类型说明
idtext pk主键,格式 cle_<ulid>
tenant_idtext not null所属 tenant
subject_typetext not nullconsumer / consumer_api_key
subject_idtext not null计费主体 ID
consumer_idtext not null对应 consumer
consumer_api_key_idtext null对应 API key;consumer 级账项可为空
request_idtext null请求级幂等键;仅请求结算类账项使用
request_log_idtext null对应请求日志;当前可为空
entry_typetext not nullsettle / admin_adjustment / correction
amount_deltabigint not null有符号 Credit 变化量;扣费为负,加额为正
balance_beforebigint null该账项落地前的 remaining_credit 快照
used_beforebigint null该账项落地前的 used_credit 快照
balance_afterbigint null该账项落地后的 remaining_credit 快照
used_afterbigint null该账项落地后的 used_credit 快照
metadatajsonb not null default '{}'结算审计上下文;存储为 ext.billing.CreditLedgerEntryMetadata ProtoJSON
created_attimestamptz not null创建时间
updated_attimestamptz not null更新时间

约束/索引:

  • check (subject_type in ('consumer', 'consumer_api_key'))
  • check (entry_type in ('settle', 'admin_adjustment', 'correction'))
  • index (tenant_id, subject_type, subject_id, created_at)
  • index (tenant_id, request_id)
  • index (request_log_id)
  • unique (tenant_id, request_id, subject_type, entry_type) where request_id is not null

说明:

  • 请求后结算时:
    • consumer 扣费写 entry_type='settle'subject_type='consumer'
    • API key 也参与扣费时,再写一条 entry_type='settle'subject_type='consumer_api_key'
  • metadata 当前至少覆盖:route_idupstream_idupstream_api_key_idrequested_modelsourceoperation
  • Consumer 余额通过管理 API 调整时,必须写 entry_type='admin_adjustment'
  • API Key 余额通过管理 API 调整时,同样必须写 entry_type='admin_adjustment'