2.1 存储桶规划:原始录音与转写结果分开放
业务场景:一个桶装到底的代价
最早图省事,我建了一个 COS 桶,会议录音、转写文本、最终纪要全往一个里扔。用了两周就乱套:转写结果混在录音文件堆里,前端纪要列表刷新慢半拍,因为翻出来的全是音频对象;有同事清理自己空间时误删过一次原始录音,导致第二个月做术语复盘的时候没了对照素材,只能靠转写文本硬啃。
更麻烦的是清理策略。纪要是要长期留的,录音过两个月基本没人再听,混在同一个桶里根本没法配生命周期规则——要么一起删,要么一起留,存储费用一点点涨上去。痛过之后我做的第一件事,就是把存储结构按用途拆开。这一节讲的就是这个拆分思路:桶怎么划、目录怎么设、生命周期和权限怎么配,都是可以直接照做的清单。
产品与功能:桶与目录前缀
对象存储 COS 的结构很简单:桶(Bucket)是顶层容器,桶里放对象(Object),对象的路径自己定,用斜杠隔开就像目录。所以拆分有两个层次:可以拆成多个桶,也可以在一个桶里用目录前缀拆分。我的选择是按用途拆桶,按日期在桶内建目录——桶级别的权限和生命周期是独立的,目录级别的路径方便检索。操作细节见COS 快速入门。
存储桶目录设计
下面是我们最终定下来的结构,一个主原则:原始音频、中间产物、最终交付物三者分桶。
| 桶 | 目录 | 存什么 | 生命周期 | 访问权限 |
|---|---|---|---|---|
| meeting-raw | /audio/{YYYYMMDD}/ | 原始录音 wav、m4a | 90 天归档,1 年删除 | 仅后端服务账号可写 |
| meeting-raw | /backup/ | 特殊情况补传的原始文件 | 90 天归档,1 年删除 | 仅管理员可读写 |
| meeting-text | /asr-result/{YYYYMMDD}/ | ASR 返回的原始结果、逐字稿 | 30 天后转低频存储 | 后端可写,本人可读 |
| meeting-text | /minutes/{YYYYMMDD}/ | 结构化纪要、待办与决议 | 永久,手动清理 | 后端可写,全员可读 |
| meeting-text | /diarization/ | 说话人分离的中间结果 | 30 天后删除 | 仅后端可读写 |
| meeting-log | /sms-log/ | 通知发送记录与回执 | 180 天删除 | 仅管理员可读 |
桶名我加了项目前缀,一眼能看出是哪个项目的;目录按日期切,是因为我们的检索习惯就是按会议日期找,配合前端列表直接列目录即可,不用额外维护索引。
对象命名也定了个规矩:日期下划线开头,接会议主题,接来源标记,比如 20261023_产品周会_mic.wav。下划线而不是空格,是因为空格在 URL 里要转义,下游脚本处理起来烦;来源标记是因为同一个会议可能有人用手机补录,哪怕两边内容重复,转写的时候也能区分是哪一路收音。命名规范这种事,越早定越省事,等积累了上千个文件再回头改,要写迁移脚本。
目录层级与检索的取舍
最初我想按部门再分一层目录,比如 /audio/产品部/20261023/。实践了两周发现是过度设计:我们就二十来号人,一周三次例会加零散项目会,一个月产生的目录数量两只手数得过来,多一层反而让列目录的接口要拼更多参数。如果以后团队过百、跨部门会议成灾,再加部门层级不迟。分层这件事的最佳判断标准是列表性能:一屏翻不完的时候才考虑拆。纪要归档与检索更系统的做法,可以参考我另一本笔记知识库实战,里面的标签和链接思路对结构化沉淀很有启发。
为什么原始音频和转写结果要分开存
第一个原因是用途不同。原始音频是素材,上传后只在两种场景被读:一是转写时被 ASR 拉走,二是纪要对不上的时候回听核对。转写结果和纪要是交付物,前端列表、纪要页面、后续的知识归档都在反复读它们。素材和交付物的读写频率差了一个数量级,放在同一个桶里,列目录、配缓存都要互相迁就。
第二个原因是清理周期不同。录音文件大,一个一小时的会议 wav 差不多 200MB,留一年光存储就是不小的开销;纪要全是文本,一页纸几百 KB,留五年也占不了多少地方。分开之后,录音桶可以大胆配归档和删除规则,纪要桶配永久保留,各管各的,费用明明白白。
第三个原因是权限不同。原始录音可能涉及敏感讨论,只开放给后端服务账号和本人;纪要是给全员看的,甚至打包发给参会人。放在同一个桶里,桶级权限只能取一个公约数,要么纪要播不出去,要么录音管不严。分桶之后,每个桶的访问策略各自独立,出问题时排查范围也小。
其实还有第四个隐性原因:出问题的爆炸半径。代码有 bug 误删目录的时候、被恶意刷接口的时候、配错规则误转类型的时候,影响只局限在一个桶。录音桶出事,纪要还在;纪要桶出事,原始录音还在,转写重跑一遍就能恢复。数据在最差的情况下还能自救,这是分桶带来的工程保险。
生命周期策略
生命周期规则是 COS 提供的自动转换存储类型和删除对象的功能,配上之后不用人工干预。我们的策略是按对象类型分层:
| 对象类型 | 30 天后 | 90 天后 | 1 年后 |
|---|---|---|---|
| 原始录音 | 保持标准存储 | 转归档存储 | 删除 |
| ASR 原始结果 | 转低频存储 | 保持低频存储 | 保持低频存储兜底 |
| 最终纪要 | 保持标准存储 | 保持标准存储 | 保持标准存储 |
| 通知发送日志 | 保持标准存储 | 保持标准存储 | 删除 |
录音 30 天内保持标准存储,是因为开会后一周是回听核对的高峰期;过了 90 天基本只剩审计价值,转归档把每 GB 的费用压到最低。纪要不转类型,是因为它小,而且偶尔会被叔回知识库做二次整理,低频存储的取回费用反而划不来。
存储类型的日子越往后越有讲究。我们每月初会扫一次各桶的对象数量与容量分布,录音桶稳中有降是正常的,说明生命周期在起作用;如果纪要桶涨得飞快,就该检查是不是有重复生成的文件没去重。这种月度巡检花十分钟,却能防止存储账单在不知道什么地方悄悄膨胀,是长期运行的小习惯。
另外提一句存储类型的选择逻辑,别盲目用最便宜的档位。归档存储取回慢,适合确认不再用的录音兜底;低频存储适合偶尔访问的 ASR 中间结果;标准存储留给高频读的纪要。规则配错一次,回听一个归档里的录音等取回等到怀疑人生,这账得算明白。归档存储取回慢,适合确认不再用的录音兜底;低频存储适合偶尔访问的 ASR 中间结果;标准存储留给高频读的纪要。规则配错一次,回听一个归档里的录音等取回等到怀疑人生,这账得算明白。
权限设计
权限我分三层配。桶级是私有读写,所有桶默认拒绝公网访问,对象只能通过后端签名访问或 STS 临时密钥拿。账号级给后端服务一个子账号,只授予对应桶的读写权限,密码定期轮换。对象级对纪要这种需要分享的,用带有效期的临时预签名 URL,参会人点开短信里的链接就能看,链接过期自动失效,不用单独做鉴权页面。
有一点提醒:不要为了方便把纪要桶设成公有读。看起来省事,实际上只要拿到对象路径谁都能扒走全部历史纪要,内部工具也不能这么放宽。
权限和后面要讲的客户端直传是配套的。直传意味着密钥要发到浏览器端,桶级权限就是最后一道闸门:桶默认私有,临时密钥的作用域只覆盖当天的上传目录,这套组合拳在 2.2 节会展开。整个云上底座的权限设计,腾讯云五件套实战里有更通用的讲法,读完能少走不少弯路。
踩坑与解法
上线第一周,有同事反馈昨天的会议纪要列表里看不到文件。查了半天,是我在 meeting-text 桶的 /minutes 目录写了权限,但生命周期规则挂在桶级别,把 30 天转低频的规则错误地覆盖了纪要目录,导致对象类型转完之后前端列举接口没带对参数。解法是规则生效范围一定在控制台里逐条确认到目录前缀级别,别贪快在桶根上一挂了之。
第二个坑是日期目录的时区。目录按 YYYYMMDD 切,但后端跑在 UTC 时区的服务器上,晚上九点开完会,本地已经是 10 月 23 日,落盘的目录却是 10 月 22 日,前端按用户时区查目录永远查不到当晚的会。统一把落盘命名改成用会议开始时间所在的东八区日期,而不是上传时间,从此跨天的会再也没找错过。
存储结构上线检查清单
最后给一份我自己上线前逐项打勾的清单,新团队照着建可以少返工:
| 检查项 | 通过标准 |
|---|---|
| 桶数量与用途 | 原始音频、中间产物、交付物三者分桶 |
| 目录前缀 | 按日期切,层级不超过两层 |
| 生命周期 | 每个桶都有规则,生效范围核对到目录级别 |
| 桶权限 | 默认私有,公网不可列举 |
| 服务账号 | 子账号最小权限,只覆盖职责所需桶 |
| 预签名 URL | 纪要分享走临时 URL,带过期时间 |
| 命名规范 | 日期下划线开头,含会议主题与来源标记 |
| 日志 | 桶操作开启日志记录,出问题可回查 |
前六项是安全与自动化的地基,后两项是运维省心的来源。清单看着繁琐,实际对照控制台半小时就能过一遍,比起上线后边用边修的模式,成本低得多。
延伸阅读
- 更通用的 COS 使用模式,可以看腾讯云五件套实战里的存储章节。
- 纪要长期归档与检索的思路,参考笔记知识库实战。
- 本地音频备份的对比方案,可以翻翻NAS 家庭服务器实战。
