postgresql-data-model.md
10.8 KB
PostgreSQL 数据模型 / 当前 song-centric 4 表方案
更新:2026-06-04
关联 SQL:acr-engine/sql/acr_pg_schema_songcentric_v1.sql
1. 一页结论
当前默认只认 4 张核心物理表:
media_entity -> audio_object -> feature_fact -> set_membership
逻辑语义这样理解:
song -> asset -> window -> fingerprint / embedding
这套设计的核心价值:
-
song-centric:最终稳定返回
song_id -
融合优先:减少
recording/work/version首阶段理解成本 -
特征统一:exact lane 和 semantic lane 统一落到
feature_fact -
模型可替换:换
model_name/model_version/feature_set_name不必重拆 schema -
证据可回溯:任何召回都能回查到具体
window -> asset -> song
2. 为什么现在收敛成 4 表
当前目标不是先建一个最完整的音乐版权知识图谱,而是先把下面这件事做稳:
收到一个录音/BGM/片段/翻唱相关查询后,能够快速定位它最可能对应哪个
song_id。
因此当前优先级是:
- 先固定
song作为最终归属对象 - 保留
asset,支持同一song下多个音频文件 - 保留
window,支持切片级 evidence 与 offset - 用一张
feature_fact同时承载 fingerprint 与 embedding - 用一张
set_membership管理 reference/eval/hot 集合
3. 4 张表分别解决什么问题
| 表 | 当前主要 type | 解决的问题 |
|---|---|---|
media_entity |
song |
最终归属对象是谁 |
audio_object |
asset, window
|
实际音频文件是什么、切成了哪些窗口 |
feature_fact |
fingerprint, embedding
|
每个窗口/对象用了哪个模型、产出了什么特征 |
set_membership |
reference_set, eval_set, hot_set
|
哪些 song/asset/window/feature 属于哪个集合 |
4. 切片 / 模型 / feature 分别在哪张表
| 业务对象 | 物理表 | 关键字段 | 用途 |
|---|---|---|---|
| song | media_entity |
entity_type='song' |
最终返回 song_id
|
| asset | audio_object |
object_type='asset' |
存原始音频文件元数据 |
| window | audio_object |
object_type='window', parent_object_id=<asset_id>
|
存切片范围、offset、evidence |
| fingerprint | feature_fact |
feature_type='fingerprint', fingerprint_value
|
exact lane 检索 |
| embedding | feature_fact |
feature_type='embedding', embedding_uri/vector_table_name, embedding_dim
|
semantic lane 检索 |
| model identity | feature_fact |
model_name, model_version
|
区分 MERT / MuQ / ECAPA / fallback |
| feature set identity | feature_fact |
feature_set_name, feature_schema_ver
|
区分特征配置、窗口策略、schema 版本 |
| reference routing | set_membership |
set_type, set_name
|
控制 reference/eval/hot 范围 |
4.1 一个关键设计点
当前 模型信息不单独放 registry 表作为默认主链依赖,而是先直接沉淀在 feature_fact:
- 这样 Phase-1 更轻
- 更适合“直接复用开源 encoder,不先训练/微调”的当前策略
- 后续如果要补 registry,也可以把
feature_fact中已有事实反向注册
5. 核心流程图
5.1 落库流程
flowchart TD
A[media_entity\nentity_type=song] --> B[audio_object\nobject_type=asset]
B --> C[audio_object\nobject_type=window]
C --> D1[feature_fact\nfeature_type=fingerprint]
C --> D2[feature_fact\nfeature_type=embedding]
A --> E[set_membership]
B --> E
C --> E
5.2 查询回溯流程
flowchart LR
A[query audio] --> B[切片成 query windows]
B --> C[抽 fingerprint / embedding]
C --> D[命中 feature_fact]
D --> E[audio_object window]
E --> F[audio_object asset]
F --> G[media_entity song]
G --> H[输出 song_id + evidence]
5.3 表职责视图
flowchart TB
M[media_entity\n谁] --> A[audio_object\n哪份音频/哪段切片]
A --> F[feature_fact\n用了哪个模型/产出什么特征]
M --> S[set_membership\n属于哪个 reference/eval/hot 集]
A --> S
F --> R[召回/匹配/聚合]
6. 每张表的设计意图
6.1 media_entity
用途:
- 作为 song 主实体表
- 统一承载
song_id - 后续如需要,也允许保留
work/recordingtype,但当前默认只把song当主语义
当前最常用字段:
entity_identity_typebiz_keytitleartist_namemetadata_json
设计意图:
- 不再把 song 相关字段散落到多张表
- 先把最终归属对象固定下来
6.2 audio_object
用途:
- 同时管理
asset与window - 用
parent_object_id建立asset -> window父子关系
当前最常用字段:
object_typesong_idparent_object_idstorage_urichecksumduration_msstart_msend_ms
设计意图:
- 同一
song下可有多个音频文件 - 同一音频文件可切成多个检索窗口
- 查询命中后可以回查具体 offset
6.3 feature_fact
用途:
- 统一存 exact lane 和 semantic lane 的特征事实
- 统一挂模型信息、特征集信息、特征载荷位置
当前最常用字段:
feature_typeobject_idsong_idmodel_namemodel_versionfeature_set_nameembedding_dimfingerprint_valueembedding_urivector_table_name
设计意图:
- 避免为不同模型建一堆平行 embedding 表
- 未来换 MERT / MuQ / 其他 encoder 时只增 feature rows,不改主 schema
- exact / semantic 两条 lane 可以共用同一归属链
6.4 set_membership
用途:
- 统一管理 reference_set / eval_set / hot_set
- member 可以是
song/asset/window/feature
设计意图:
- reference 范围不硬编码到 song 表里
- 评测集、热集、灰度集能共用一张关系表
7. 为什么“切片数据 + 模型 + feature”这样分布最合理
切片数据放 audio_object
因为切片本质是音频对象的一种:
- 它有父 asset
- 它有
start_ms/end_ms - 它需要被回溯和复用
模型信息放 feature_fact
因为模型是“某次特征计算”的属性:
- 同一个 window 可能被多个模型重复编码
- 同一个模型也可能有多个版本
- 模型名和版本应该和 feature 结果绑定,而不是只和 asset 绑定
feature 放 feature_fact
因为 feature 是事实:
- 某个对象
- 用某个模型
- 以某个 feature set
- 产出某个结果
这正好就是一条事实记录。
8. 第一个阶段如何服务 100w 音频 / 30w 歌曲
建议的落盘顺序
- 先写
media_entity(song) - 再写
audio_object(asset) - 再批量切
audio_object(window) - 再按模型批次写
feature_fact - 最后写
set_membership(reference_set/hot_set/eval_set)
为什么这样落
因为这能把“音频对象生命周期”和“模型计算生命周期”解耦:
- 音频先入库
- 切片先固定
- exact lane 可先跑
- semantic lane 之后补跑也不影响主链
9. Phase-1 推荐策略
9.1 exact lane
- 默认:
ChromaprintMatcher - 落到:
feature_fact(feature_type='fingerprint')
9.2 semantic lane
- 当前优先:
MERT - challenger:
MuQ - 当前 host 若 runtime 不可用,保留 fallback
- 落到:
feature_fact(feature_type='embedding')
9.3 为什么不是 ECAPA-TDNN 主导
- ECAPA 更偏 speaker/audio identity 方向
- 当前目标是版权保护 / song-level ACR
-
MERT/MuQ更适合作为 song semantic baseline/challenger
10. 当前方案解决的问题
这套 4 表设计,当前主要解决:
- 同一
song下多音频文件管理 - 切片级 evidence 管理
- fingerprint 与 embedding 统一落库
- 模型切换时不重构主 schema
- reference/eval/hot 集统一治理
- 检索命中后快速回到
song_id
11. 当前不刻意解决的问题
Phase-1 暂不强求:
- 复杂
work / recording / version治理 - 完整权利层图谱
- 训练/微调闭环
- 重型 registry-first 体系
这些都可以后续逐步加,但不该反向阻塞当前主链。
12. 相关文档
13. 在线检索时怎么从 feature 回到 song_id
这是当前研发最需要牢记的一条回溯链:
feature_fact -> audio_object(window) -> audio_object(asset) -> media_entity(song)
13.1 在线检索流程图
flowchart LR
Q[query audio] --> QW[query windows]
QW --> QE[query fingerprint / embedding]
QE --> FF[feature_fact]
FF --> W[audio_object\nobject_type=window]
W --> A[audio_object\nobject_type=asset]
A --> S[media_entity\nentity_type=song]
S --> R[return song_id + title + artist + evidence]
13.2 聚合流程图
flowchart TD
A[query window features] --> B[命中多个 feature_fact rows]
B --> C[回查 window]
C --> D[回查 asset]
D --> E[聚合到 song_id]
E --> F[按 hit_count / score / offset coverage 排序]
F --> G[返回 topK songs]
13.3 最小查询 SQL 模板
select ff.feature_id,
ff.feature_type,
ff.model_name,
ff.model_version,
ff.feature_set_name,
w.object_id as window_id,
w.start_ms,
w.end_ms,
a.object_id as asset_id,
a.storage_uri,
s.entity_id as song_id,
s.title,
s.artist_name
from feature_fact ff
join audio_object w
on w.object_id = ff.object_id
and w.object_type = 'window'
join audio_object a
on a.object_id = w.parent_object_id
and a.object_type = 'asset'
join media_entity s
on s.entity_id = ff.song_id
where ff.feature_id = :feature_id;
13.4 一个 song-level 聚合 SQL 模板
select ff.song_id,
s.title,
s.artist_name,
count(*) as matched_windows,
min(w.start_ms) as first_hit_ms,
max(w.end_ms) as last_hit_ms
from feature_fact ff
join audio_object w
on w.object_id = ff.object_id
and w.object_type = 'window'
join media_entity s
on s.entity_id = ff.song_id
where ff.feature_type = :feature_type
and ff.model_name = :model_name
and ff.feature_set_name = :feature_set_name
and ff.feature_id = any(:matched_feature_ids)
group by ff.song_id, s.title, s.artist_name
order by matched_windows desc, first_hit_ms asc
limit 20;
13.5 这条链为什么重要
因为它把 3 件事拆清楚了:
-
feature_fact负责回答:命中了什么特征 -
audio_object(window/asset)负责回答:命中了哪段、来自哪个文件 -
media_entity(song)负责回答:最终该归到哪个song_id
所以 Phase-1 即使不引入更复杂的 recording/work/version,也已经足够支撑:
- 版权保护归属
- 片段/BGM 定位
- evidence 回查
- topK song 级召回