如何保存AI生成记录

保存用户的生成记录,是 AI 产品不可或缺的基础能力。用户花了几十秒等待模型生成一张图片、一段文案或一篇分析报告,如果刷新页面后记录消失,信任感会瞬间崩塌。更现实的问题是——生成记录不仅关系到用户体验,还是计费依据、质量回溯的数据基础,以及后续模型微调的素材来源。

从技术角度看,AI 生成记录的保存涉及存储策略选择、数据库表结构设计、历史查询性能优化、数据导出能力和存储成本控制等多个环节。这一节会系统梳理这些环节的设计思路和工程实践,帮助你在产品早期就搭建起一套可靠的记录保存体系。

记录保存策略

AI 生成记录的保存时机并不是「生成完再存」这么简单。不同产品的交互模式、用户对延迟的敏感度和数据的可靠性要求,会直接影响保存策略的选择。

实时保存(Synchronous Save)

实时保存是指 AI 模型完成输出后,立即将记录写入数据库,用户收到响应时数据已经持久化。

适用场景: 计费敏感型产品(按次扣费)、企业级应用(需要审计追踪)、多端同步场景。

实现要点:

  • 在 API 响应返回前完成数据库写入,确保「返回成功 = 已保存」
  • 使用事务保证 prompt、output、元数据原子写入
  • 写入延迟通常增加 10-50ms,对大多数场景可接受

风险: 如果数据库出现抖动,会直接拖慢用户请求的响应时间。

延迟保存(Asynchronous Save)

延迟保存是指先将生成结果返回给用户,再通过后台队列异步写入数据库。

适用场景: 对响应延迟极度敏感的对话类产品、生成频率高但单条记录价值低的场景。

实现要点:

  • 使用消息队列(如 Redis Stream、RabbitMQ、Kafka)缓冲写入请求
  • 消费端批量写入,减少数据库压力
  • 需要处理「用户看到了结果但记录尚未落库」的中间状态——比如用户立刻刷新历史记录页面时可能看不到刚才的生成

风险: 队列积压或消费者宕机时可能丢失记录。需要至少一次(at-least-once)的投递保证和幂等写入机制。

手动保存(Manual Save)

手动保存是指生成结果先存储在客户端或临时缓存中,用户主动点击「保存」后才写入数据库。

适用场景: 创意工具类产品(用户会大量生成但只保留满意的结果)、免费增值产品的免费额度场景。

实现要点:

  • 客户端使用 IndexedDB 或 localStorage 做本地缓存
  • 服务端提供临时存储(TTL 缓存),过期自动清理
  • 保存动作涉及客户端上传 + 服务端持久化两步

风险: 用户忘记保存、清除浏览器数据或换设备后记录丢失。需要在 UI 上给出明确的保存提示。

保存策略对比

维度实时保存延迟保存手动保存
数据可靠性最高(返回即落库)中等(依赖队列可靠性)最低(依赖用户操作)
响应延迟影响增加 10-50ms几乎无影响用户感知为即时
实现复杂度中(需处理队列和重试)中(需处理客户端缓存和同步)
存储成本高(全量保存)高(全量保存)低(只保存用户选择的内容)
适合产品类型企业应用、计费产品对话助手、高频生成创意工具、图片生成

对于出海产品,还需要考虑用户所在地区的网络延迟。东南亚、拉美等地区的数据库访问延迟可能显著高于北美和欧洲。如果你的数据库部署在 AWS us-east-1,雅加达用户的写入延迟可能超过 200ms。这种情况下,延迟保存或异地写入同步是更务实的选择。

数据库表设计

AI 生成记录的数据模型看起来简单——无非是「谁、什么时候、输入了什么、输出了什么」——但在实际工程中,需要兼顾查询性能、存储效率和扩展性。

核心表结构

一个典型的生成记录表包含以下字段:

CREATE TABLE generation_records (
  id            BIGINT PRIMARY KEY AUTO_INCREMENT,
  user_id       BIGINT NOT NULL,
  session_id    VARCHAR(64),
  feature_type  VARCHAR(32) NOT NULL,    -- 功能类型:text/image/code/chat
  model_id      VARCHAR(64) NOT NULL,    -- 模型标识:gpt-4o、dall-e-3 等
  prompt        TEXT NOT NULL,            -- 用户输入(prompt)
  output        MEDIUMTEXT,              -- 模型输出内容
  output_type   VARCHAR(16),             -- 输出类型:text/json/image_url/audio_url
  token_count   INT DEFAULT 0,           -- token 消耗量
  latency_ms    INT DEFAULT 0,           -- 生成耗时(毫秒)
  status        VARCHAR(16) DEFAULT 'success',  -- success/failed/timeout
  metadata      JSON,                    -- 扩展参数(温度、种子值等)
  created_at    DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  INDEX idx_user_created (user_id, created_at DESC),
  INDEX idx_feature_type (feature_type, created_at DESC),
  INDEX idx_status (status, created_at DESC)
);

设计时需要注意几个关键点:

user_id 必须有索引。 绝大多数查询都是「某个用户的历史记录」,这是最高频的查询模式。没有索引的话,随着数据量增长,查询会迅速恶化。

output 字段要考虑大小限制。 文本类输出通常不会太大,但如果是图片的 Base64 编码或长音频,单条记录可能达到数 MB。这种情况建议将大体积内容存储到对象存储(如 AWS S3),数据库只存引用 URL。

metadata 用 JSON 类型而非固定列。 AI 模型的参数经常变化——今天用的是 temperature 和 top_p,明天可能新增 response_format 或 seed。用 JSON 字段可以避免频繁的 DDL 变更。MySQL 5.7+、PostgreSQL 9.3+ 都支持 JSON 类型和 JSON 路径查询。

关联表设计

当产品功能增多后,单表结构往往不够用。常见的扩展是拆出会话表和附件表:

-- 会话表:记录用户的对话上下文
CREATE TABLE sessions (
  id          BIGINT PRIMARY KEY AUTO_INCREMENT,
  user_id     BIGINT NOT NULL,
  title       VARCHAR(256),
  feature_type VARCHAR(32) NOT NULL,
  created_at  DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at  DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  INDEX idx_user_updated (user_id, updated_at DESC)
);
 
-- 附件表:存储生成产物中的文件引用
CREATE TABLE generation_assets (
  id            BIGINT PRIMARY KEY AUTO_INCREMENT,
  record_id     BIGINT NOT NULL,
  asset_type    VARCHAR(16) NOT NULL,    -- image/audio/video/document
  storage_url   VARCHAR(512) NOT NULL,
  file_size     INT DEFAULT 0,
  mime_type     VARCHAR(64),
  created_at    DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  INDEX idx_record (record_id)
);

会话表的作用是将同一次对话的多轮生成归组。用户查看历史时,通常希望看到「某次对话」,而不是散落的单条记录。附件表则用于处理多模态输出——一次生成可能同时产出文本和多张图片,需要分别记录存储位置。

索引策略

索引设计要匹配实际的查询模式。以下是 AI 产品中最常见的几种查询:

  1. 用户历史记录列表WHERE user_id = ? ORDER BY created_at DESC LIMIT 20
  2. 按功能类型筛选WHERE user_id = ? AND feature_type = ? ORDER BY created_at DESC
  3. 全文搜索WHERE user_id = ? AND MATCH(prompt) AGAINST('关键词')
  4. 管理后台统计WHERE created_at BETWEEN ? AND ? AND status = 'success' GROUP BY feature_type

前两种查询通过组合索引 idx_user_createdidx_feature_type 就能覆盖。全文搜索需要额外的全文索引(MySQL 的 FULLTEXT 或 PostgreSQL 的 tsvector)。管理后台的统计查询则适合用独立的统计表或定期聚合来优化,避免在大量记录上实时 GROUP BY

历史记录查询

查询设计的核心矛盾是:数据量会随时间线性增长,但用户对查询速度的期望不会降低。一条 SQL 在 1 万条记录时跑得飞快,到 1000 万条时可能就需要几秒甚至更久。

分页方案

最常见的查询是「历史记录列表」,分页方案的选择至关重要。

Offset 分页是最直观的写法:LIMIT 20 OFFSET 100。问题是 offset 越大越慢——数据库需要先扫描前 120 条再丢弃前 100 条。当用户翻到第 100 页(offset = 2000)时,性能已经开始下降;翻到第 10000 页时,延迟可能超过一秒。

游标分页(Cursor-based Pagination) 是更好的方案:

-- 第一页
SELECT * FROM generation_records
WHERE user_id = 123
ORDER BY created_at DESC, id DESC
LIMIT 20;
 
-- 后续页:用上一页最后一条记录的 created_at 和 id 作为游标
SELECT * FROM generation_records
WHERE user_id = 123
  AND (created_at, id) < ('2026-06-15 10:30:00', 45678)
ORDER BY created_at DESC, id DESC
LIMIT 20;

游标分页的性能不随页数增长,因为每次查询都从索引的精确位置开始扫描。缺点是用户无法「跳转到第 N 页」,但对于历史记录这种场景,用户几乎不会跳页。

搜索与筛选

当用户的历史记录超过几十条后,搜索功能就变得必要了。

基础筛选通过组合条件实现——按功能类型、时间范围、状态过滤。这些查询依赖前面提到的组合索引,性能通常没有问题。

全文搜索的实现方式取决于数据规模和产品阶段:

方案适用规模延迟实现复杂度搜索质量
数据库 LIKE / ILIKE< 10 万条最低差(仅精确匹配)
MySQL FULLTEXT / PostgreSQL tsvector< 500 万条低-中中(支持分词)
Elasticsearch / Meilisearch不限高(分词+相关性排序+高亮)

早期产品用数据库自带全文索引就够了。当搜索请求成为性能瓶颈或需要更好的搜索体验(模糊匹配、拼音搜索、结果高亮)时,再引入专用搜索引擎。

查询性能优化

几个实用的优化手段:

  • 读写分离:历史查询走从库(replica),生成写入走主库。PostgreSQL 和 MySQL 都支持这种模式,云数据库(如 AWS Aurora、Neon)通常开箱即用。
  • 热冷分离:最近 7 天或 30 天的记录放在主表中,更早的记录归档到历史表。查询默认只扫描热数据,需要时再手动或自动切换到历史表。
  • 计数缓存:「你共有 N 条生成记录」这个数字如果每次都 COUNT(*) 会很慢。可以用一个计数器字段,每次新增或刪除时增减,或者用 Redis 缓存计数结果,设置 5 分钟的 TTL。

数据导出

让用户能够导出自己的生成记录,不仅是用户体验的加分项,在很多市场也是合规要求——GDPR 的「数据可携带权」(Right to Data Portability)明确要求用户能够获取自己存储的结构化数据。

导出格式

格式适用内容优点缺点
JSON结构化数据(含 prompt、元数据、token 数)完整保留所有信息,程序可直接解析普通用户难以阅读
CSV表格型数据(生成时间、类型、内容摘要)Excel 可直接打开,用户易读不适合嵌套结构和大文本
PDF图文混排的报告型内容排版美观,适合分享和存档生成成本高,数据不可编辑
Markdown文本类生成内容轻量可读,版本管理友好不支持复杂表格和元数据

导出实现

导出的核心挑战是「不要在导出时拖垮线上数据库」。如果用户有 10 万条记录,一次性查出来再生成文件,数据库连接和内存都会成为问题。

推荐的做法是异步导出:

流程图画布 · 115%
Mermaid 流程图加载中...

关键点:

  • 分批查询:用流式查询(MySQL 的 streaming query 或 PostgreSQL 的 cursor)避免一次性加载大量数据到内存
  • 临时文件存储:导出的文件放到 S3 或类似的对象存储,设置生命周期策略自动清理(比如 24 小时后删除)
  • 导出限流:限制单个用户的导出频率(比如每小时最多 3 次),避免恶意或频繁导出消耗资源

存储优化

AI 生成记录的体积增长速度往往超出预期。一次图片生成可能产生数 MB 的数据,一个活跃用户每天可能产生数百条记录。如果不做存储优化,存储成本会随用户增长线性上升,最终成为不可忽视的开支。

压缩策略

对于文本类输出,数据库层面可以开启行压缩(Row Compression)。MySQL 的 InnoDB 和 PostgreSQL 的 TOAST 机制都支持透明的数据压缩,文本数据的压缩比通常在 3:1 到 5:1 之间。

对于图片和视频等多媒体产物,不建议存在数据库中。更好的方案是直接存到对象存储,数据库只保留 URL 引用。对象存储本身已经针对大文件做了优化,成本也远低于数据库存储。

数据归档

随着时间推移,大量历史记录的访问频率会急剧下降。一个月前的生成记录,用户可能只在特殊情况下才会翻看。这时候就可以考虑归档。

分层存储方案:

层级数据范围存储位置查询延迟存储成本
热数据最近 30 天主数据库(SSD)< 50ms最高
温数据30 天 - 1 年从数据库或只读副本< 200ms
冷数据1 年以上对象存储(S3 Glacier)或归档表秒级到分钟级最低

归档的实现可以是定期任务(如每天凌晨执行一次),将超过 30 天的记录从主表迁移到归档表。归档表的结构可以与主表相同,但放在更廉价的存储介质上,或者直接从主表中删除并将完整数据导出到 Parquet 格式文件存到 S3。

成本控制

存储成本不只是硬盘费用。在云环境中,还需要考虑备份成本、复制流量费用和查询产生的计算费用。

几个实际的成本控制手段:

  • 定期清理无价值的临时数据:比如生成失败的记录、用户未保存的草稿,设置 7 天 TTL 自动删除
  • 限制免费用户的记录保留时长:免费用户的记录保留 30 天,付费用户保留全部。这既是成本控制手段,也是付费转化的推动因素
  • 使用对象存储存储大体积产物:1 GB 的数据库存储成本大约是 S3 的 5-10 倍。图片和视频产物应该尽早迁出数据库
  • 监控存储增长趋势:建立告警机制,当单表数据量超过阈值(如 5000 万条或 100 GB)时触发归档或分区操作

案例

案例 1:Midjourney 的生成记录管理

Midjourney 的生成记录保存有一个独特之处——它的主要交互发生在 Discord 中,而非独立的 Web 应用。每条 /imagine 命令的生成结果直接在 Discord 频道中展示,记录保存依赖 Discord 的消息历史。

这种模式的优势是实现简单——不需要自建消息存储,Discord 的消息系统天然提供了历史记录。缺点也很明显:用户无法高效搜索和筛选历史生成,图片与对话内容混杂在一起。

Midjourney 后来推出的 Web 端 Image Creator 和 Personalization 页面弥补了这个问题,将用户的生成记录同步到独立的 Web 界面,支持按时间排序和风格筛选。这个演进说明,即使早期可以用外部平台「偷懒」,生成记录的独立管理和检索能力最终都是绕不开的。

案例 2:Jasper 的企业级记录体系

Jasper 作为面向营销团队的 AI 写作工具,其记录保存需要满足企业级需求:完整的审计追踪、团队协作中的记录归属、按项目分组的组织方式。

Jasper 的设计思路是将每条生成记录关联到具体的项目(Project)、文档(Document)和品牌配置(Brand Voice)。用户在查看历史时,不仅可以看到「我什么时候生成了什么」,还能看到这条记录属于哪个营销项目、使用了什么品牌语调、是哪个团队成员创建的。

这种设计增加了表关联的复杂度(记录表 → 文档表 → 项目表 → 品牌配置表),但换来了企业客户需要的可管理性和可追溯性。对于 B 端出海产品,这种设计几乎是必须的——企业客户不会因为功能好用就买单,但如果缺少审计和追溯能力,他们一定不会买单。

AI 记录保存与查询完整流程

下面用一个完整的流程图展示从用户发起生成到记录保存、查询、导出的全链路:

流程图画布 · 115%
Mermaid 流程图加载中...

检查清单

在上线 AI 生成记录保存功能之前,逐项检查以下问题:

  • 是否确定了保存策略(实时 / 延迟 / 手动),并与产品形态匹配?
  • 生成记录表是否包含 user_id 索引和 created_at 组合索引?
  • output 字段是否根据内容类型选择了合适的数据类型(TEXT / MEDIUMTEXT / URL 引用)?
  • 多模态产物(图片、音频、视频)是否使用了对象存储而非数据库直接存储?
  • 历史记录查询是否使用了游标分页而非 offset 分页?
  • 是否实现了全文搜索能力,还是只能做条件筛选?
  • 导出功能是否为异步实现,不会拖垮线上数据库?
  • 导出文件是否有有效期限制,自动清理以节省存储?
  • 是否制定了数据归档策略(热 / 温 / 冷分层)?
  • 是否监控了单表数据量和存储总量,设置了告警阈值?
  • 免费用户和付费用户的记录保留策略是否做了区分?
  • 是否满足目标市场的合规要求(GDPR 数据可携带权、数据删除权)?
  • 是否考虑了数据库读写分离,避免查询影响生成写入?
  • 是否对 metadata 字段使用了 JSON 类型,以适应模型参数的变化?

小结

AI 生成记录的保存看似是一个简单的 CRUD 操作,但在产品规模增长后,它涉及的存储策略、查询性能、导出能力和成本控制都会成为工程挑战。

核心原则是:早期从简(一张主表 + 基本索引 + 实时保存),但预留扩展空间(JSON 字段存元数据、对象存储分离大文件、异步导出框架)。当数据量和用户量增长到一定规模后,再逐步引入游标分页、全文搜索、数据归档和分层存储。

对于出海产品,额外的考虑是:用户分布在全球各地时的写入延迟、GDPR 等合规框架下的数据导出和删除要求,以及多区域部署时的数据同步策略。这些在产品设计之初就纳入考量,远比事后补救要高效。

参考资料

  1. Amazon DynamoDB Data Models for Generative AI Chatbots — AWS
  2. How to Design a Database Schema for AI Chatbot — OpenAI Community
  3. Role of Databases in GenAI Applications — arXiv
  4. AI Storage Cost Optimization: A Complete Strategy Guide — Scality
  5. AI Data Storage: Challenges & Strategies — TierPoint
  6. Data Compression: Streamlining Archiving & Reducing Costs — ShareArchiver
  7. 一站式 AI Agent 长记忆方案 — 阿里云文档
  8. AI 记忆架构探索:从工程原型到架构蓝图 — 巴别之塔