在上一篇里我聊了怎么把散落在业务库里的数据整理成能用的宽表把那些杂乱无章的订单、日志、用户行为拼成一块相对干净的数据地基。不少朋友看完之后私信问我说宽表也建了SQL也写得飞起然后呢然后怎么把这些数据喂给AI这个问题其实问到了点子上。数据库和AI之间差的从来不是算力而是一条完整的数据链路。这篇“下篇”我就集中讲这条链路。我会用我最近给某项目组做的一个内容社区推荐系统当例子把从数据库里的业务表到AI模型真正跑起来之间的每一步拆开讲明白包括为什么要做特征存储、为什么非得搞向量化、向量数据库在中间到底扮演什么角色以及几个我踩过之后半夜爬起来改代码的坑。这篇文章适合已经能熟练写SQL、但对AI工程链路还比较模糊的同学看完之后你至少知道你的数据要从数据库走到模型面前中间要过几道关卡每一道关卡在解决什么问题。1. 整体设计与思路拆解为什么传统数据库喂不饱AI1.1 AI需要的不再是“一行数据”而是“一组关系”传统数据库里我们习惯的是行列结构每一行代表一个实体比如一个用户、一篇文章、一笔订单。SQL的join操作本质是在行与行之间建立关系。但在AI场景里模型要的不是某个用户的一行属性而是这个用户过去三十天的点击序列、他看过的文章标题对应的语义向量、他所在人群的群体行为特征。这些数据在关系型数据库里当然也能装但你没法要求一个推荐模型直接对着几百张表自己join模型需要的是给它准备好的、已经完成特征计算和拼接的输入。我在这个项目里最直观的感受是AI链路里的数据会从“一行一实体”变成“一行一特征组合”。同样是一个用户数据库里存的是user_id、age、city特征存储里存的是他的embedding向量、近七日活跃度、内容偏好簇编号。这个转变不是换张表那么简单它意味着你的数据建模思路要变加工流程要变存储引擎也得变。1.2 从数据库到AI的完整链路长什么样先把我这个项目的整体链路画个轮廓后面再拆细节。数据从业务库出发经过统一的清洗和标准化落进一个用于建模的明细层再从明细层计算成特征特征一部分进离线的特征存储一部分走实时通道进在线缓存。同时文本类数据要做向量化向量化之后进向量检索服务。到推理阶段推荐服务会把用户特征、物品特征、向量检索结果全部凑齐喂给模型打分数分数排序后再回写业务库形成标注和反馈数据。这一步最容易被忽略的是数据回写。很多团队做到模型上线就停了但反馈数据的回流才是模型迭代的燃料。没有回写你的模型就永远只能靠一开始的那批离线数据撑着越跑越衰减。我在这个项目里一开始也没做回写结果上线第二周模型效果明显下降后来才补上了行为日志的埋点和回灌通道。1.3 方案选型离线为主、实时为辅先跑通再优化技术选型上我当时的判断是离线为主、实时为辅。原因很简单项目组当时连完整的数据血缘都还没建好一上来就搞实时特征计算纯属给自己挖坑。所以我们的第一版方案是每天凌晨用定时的批处理任务计算特征写进MySQL和Redis在线推理的时候直接从Redis拿特征拿不到就用默认值兜底。这套方案的好处是链路短、排查容易性能也足够支撑初期每天几十万次请求的推荐服务。到了第二期我们才引入实时计算用消息队列消费埋点数据把用户最近十分钟的行为实时更新到特征缓存里。这个节奏我觉得对中小团队是健康的先让链路闭环再谈实时性。很多项目死在第一步就是因为一开始就把实时、离线、向量化全部铺开结果任何一环出问题都没法快速定位。2. 核心细节解析与实操要点特征、向量与存储2.1 特征建设是AI的数据工程核心很多时候大家聊AI张嘴就是模型结构、调参、效果提升但我做了几年数据之后发现真正决定模型上限的是特征建设的质量。数据库里有一百个字段不代表你有一百个特征。一个合格的AI特征至少得有这几个属性有明确的业务含义、在时间上是稳定的、在缺失时能给出合理的默认策略。以我们的推荐模型为例用户侧最重要的特征之一是活跃度分段。一开始我们直接取数据库里的最近登录时间后来发现很多用户永远不登出这个字段根本不准。最后改成利用埋点日志计算最近七天的活跃天数再按活跃天数映射成高、中、低、沉寂四档。特征做不做准直接影响模型的判断这个环节省不了功夫。还有一个容易踩坑的地方是特征穿越。就是说你在训练模型的时候用了某个特征而这个特征本身包含了未来信息。比如你想预测用户明天会不会购买结果你的特征里有“用户今天是否已购买”这看起来没什么但如果这个特征是从业务库里抽的而业务库里记录的是截至当前的所有状态那训练集里它的值就带了未来信息推理时根本不可能提前知道。这个问题在数据库转型AI的项目里非常常见排查起来也很隐蔽需要按时间窗口逐项核对特征。2.2 向量化让数据库里的文字变成模型能用的语义数据库里存的文章标题、内容摘要、用户评论都是自然语言文本。模型不认字只认向量。所以你得有一套把文本转换成向量的流程这就是向量化。目前业界最主流的做法是用预训练模型生成embedding把一个句子映射成一串几百维的浮点数。语义相近的句子它们的向量在空间里距离也近。这一步有几个实操细节值得注意。第一向量化的模型要和你的业务场景匹配。通用领域的embedding模型用在专业内容社区里效果会打折我们后来换了一个在垂直领域语料上微调过的模型召回的相关性明显提升。第二向量要归一化。如果不做归一化后续计算余弦相似度的时候向量的模长差异会干扰排序结果这是我们项目组同事一开始漏掉的点后来排查召回质量问题时才补上。第三向量化服务的稳定性容易被低估如果模型部署在GPU机器上单条文本的向量化延迟可能只有几毫秒但在批量高峰期会积压一定要配上队列和超时降级策略。2.3 向量数据库怎么选从pgvector到Milvus的迁移路径向量这个环节还需要一个专门的存储和检索组件。小规模实验阶段我建议直接用PostgreSQL的pgvector插件它能在你已有的数据库基础上增加向量列和相似度检索功能不用额外维护一套系统。我们项目初期就是用的这个方案几万条文章向量检索也就几十毫秒完全够用。等到数据量上来到了几千万条量级pgvector的性能就会明显吃紧再叠加业务数据本身对数据库的压力很容易互相拖累。这个时候我们迁移到了专用的向量检索服务。选型时重点看三个指标索引构建速度、查询延迟的波动情况、以及是否支持过滤条件与向量检索的混合查询。实际用下来混合查询能力非常关键因为推荐场景基本不可能只做纯向量检索通常都得拼上“分类过滤”或者“状态过滤”。2.4 特征存储与缓存别把所有数据都塞进同一个篮子在AI链路里特征存储和业务数据库的职责要分开。业务库要保证事务一致性特性存储要保证读取速度。我在这个项目里用的是MySQL加Redis的组合MySQL存特征的明细版本Redis存线上推理要用的实时特征。每天早上批处理把特征算完后一部分写进MySQL做归档一部分直接同步进Redis。线上服务读到特征缺失时策略是先用默认值顶住同时记录一条日志方便事后排查。这里有个经验特征缓存一定要设置合理的过期时间并配上主动刷新机制。我见过有团队把用户活跃特征放在缓存里过期时间设成七天结果用户已经连续五天没打开App了推荐系统还在给他推高活跃用户才看的内容这就是特征过期没及时刷新的典型问题。3. 实操过程与核心环节实现一步步搭起数据到AI的流水线3.1 建表与数据准备从业务表到特征宽表我用一个简化版的内容推荐场景来走完整流程。假设你的业务库里有两张表一张是文章表存文章ID、标题、分类、发布时间一张是用户行为表存用户ID、文章ID、行为类型、行为时间。我们的目标是给每个用户生成他可能感兴趣的文章列表。第一步从文章表和用户行为表加工出两张特征宽表。文章特征宽表包含文章ID、分类、标题向量、发布时间特征、历史点击率。用户特征宽表包含用户ID、最近七天点击的文章分类分布、活跃天数、历史点击文章的标题向量平均值。第二步把这两张宽表分别注册成特征视图后面无论是离线训练还是在线推理都从这套视图取数。这段建表SQL看起来不复杂但加工过程里有一个关键点时间窗口必须严格对齐。比如“历史点击率”这个特征如果你的统计窗口是自然周那周一和周日跑出来的特征口径就不一样模型训练时用一个口径线上推理时用另一个口径效果必然打折。我们后来把时间窗口统一成“滚动近7天”每次跑批时动态计算起始日期才解决了这个问题。3.2 离线特征计算与存储用定时任务稳住数据底座特征计算我用的是定时批处理每天凌晨两点跑一次。任务内容分三段第一段从业务库抽取前一天新增的行为数据和已有的特征明细合并第二段跑特征加工逻辑生成当天的特征宽表第三段把特征宽表同步到Redis和向量检索服务供线上使用。这个定时任务如果只是一张宽表可能会有同学觉得简单但实际生产里它往往要拆成几十个步骤每个步骤之间还有依赖关系。我强烈建议用一个带DAG调度能力的工具来管理而不是写一个大Shell脚本从头跑到尾。我们的第一版就是一个大脚本某天中间某个步骤因为数据量暴增卡住了整个任务全部失败排查问题花了整整一个上午。后来拆成了小任务每个任务独立重试单点失败不影响全链路。3.3 向量化流程实现离线批量生成与在线增量更新向量化的实现要分离线和在线两种路径。离线路径最简单每天凌晨批处理时把当天新增的文章标题取出来调用embedding模型批量生成向量再写进向量库。在线路径稍微复杂一点用户发布新文章时我们希望这篇文章能立刻进入推荐候选池这时可以在发布接口里同步调用向量化服务向量化成功后直接写入向量库写入失败也不影响正文发布异步重试即可。我们实际用的是混合方案发布时走在线向量化保证新鲜度凌晨再跑一次全量校验任务把当天所有新增内容重新向量化一遍用来修正在线链路可能的失败和偏差。这个校验任务我们跑过一次救回来两百多条因为服务抖动而没写进向量库的新文章。3.4 向量检索与相似度计算召回阶段的关键实现向量检索这一块最核心的实现逻辑就是相似度计算。以文章推荐为例我们需要把“用户的兴趣向量”和“候选文章的向量”做余弦相似度计算分数越高代表越匹配。用户兴趣向量可以用用户最近点击过的文章向量的平均值来近似。这个近似方案虽然粗糙但作为召回阶段的第一版足够了后续再引入序列模型来精算。实操里我把用户兴趣向量计算做成一个独立的特征任务输出结果直接同步到Redis。在线推理时推荐服务拿到用户ID先查Redis里的兴趣向量再去向量检索服务里做近邻检索取回TopN篇文章ID然后进入排序阶段。这个链路里最容易超时的环节是向量检索如果候选集太大或者索引参数没调好一次查询可能超过一百毫秒。后来我们把索引参数里的M值调高、在构建索引时加大efConstruction查询延迟降到了二十毫秒上下。3.5 推理服务与结果排序把特征喂给模型召回阶段拿到的是大概几百篇候选文章排序阶段要用模型给这些候选文章打分。我们这个项目用的排序模型是梯度提升树输入特征是用户特征、物品特征、交叉特征的拼接。服务端在拿到候选文章ID之后需要逐个去特征存储里取这些文章的特征组成一条条模型可读的特征向量然后批量调用模型服务打分。这个阶段要特别小心特征拼接的顺序不一致。训练时怎么拼的特征推理时就必须一模一样地拼。我们曾因为训练代码里特征顺序是A、B、C而推理代码里写的是C、B、A导致模型效果看起来完全随机排查了整整两天。最后是写了一个特征顺序的单元测试把训练和推理的schema钉死才彻底解决。3.6 反馈数据回写让数据库和AI形成闭环模型上线后每次推荐曝光、用户点击、阅读时长、收藏行为都要回写到行为日志表。这一步看起来简单但它决定了AI系统能不能自进化。我建议回写的数据至少包含用户ID、物品ID、曝光位置、是否点击、阅读时长、行为时间。有了这些数据第二天跑批时就能计算出新的点击率特征也才能做模型的效果评估和迭代。我们在回写时遇到过一个问题就是曝光日志和点击日志的时间戳对不齐。用户可能是早上刷到的文章下午才点击如果按各自的日志时间简单join点击率特征会算错。后来统一了会话ID和时间窗口用曝光会话作为join基准才把口径对齐。这块经验我认为比模型调参重要得多数据链路不准模型再先进也是空中楼阁。4. 常见问题与排查技巧实录数据到AI链路的坑与解问题现象可能原因排查方法解决方案模型上线效果远低于离线评估特征穿越或离线在线特征不一致抽一批线上请求日志比对特征取值做特征一致性比对任务每天自动校验向量检索结果相关性差向量模型和业务场景不匹配抽样检查相似文章是否语义相关换微调过的垂直领域embedding模型某类用户没有推荐结果该用户兴趣向量为空或特征缺失查Redis和特征库里该用户的记录配置默认兴趣向量和兜底推荐策略线上服务QPS高时延迟突增向量检索参数未调优或缓存穿透看监控里P99延迟和缓存命中率调整索引参数给缓存加空值保护定时任务偶尔失败但看不出来任务步骤无依赖管理和失败告警查看调度平台的任务流状态拆分DAG任务、配置失败重试和告警新发布内容迟迟进不了推荐池在线向量化失败且无补偿机制查发布接口日志和向量库写入时间加异步重试和每日全量校验任务4.1 特征一致性离线评估和线上效果对不上的头号元凶这个问题的坑我踩得最深值得单独写一小节。离线训练时我们用的是T-1日的数据算特征模型指标很漂亮AUC也挺高。上线之后线上效果却一塌糊涂。后来定位发现问题出在离线特征和线上特征的计算口径不一样。离线时用户历史点击率是用T-2到T-1的数据算出来的线上推理时实时程序用的是T-2到当前时刻的数据多出了今天凌晨到现在的行为记录。这看起来是个很小的差异但积累下来模型学到的规律就全偏了。解决思路是给离线打特征和在线打特征使用同一套特征计算代码。我们当时的做法是把特征计算逻辑抽成一个公共的库离线批处理和在线服务都调用同一个函数只是传入的时间窗口参数不同。上线这个改造之后离线评估和线上效果的差距基本缩小到了正常范围。如果你们的离线在线特征已经割裂我建议把这个作为第一优先级来修它比换模型结构收益大得多。4.2 向量化服务的稳定性没人会提前告诉你的性能瓶颈向量化服务看着不起眼却容易成为整条链路的瓶颈。我们项目早期没在意这个环节直接把embedding模型部署在一个普通的CPU机器上结果每次批量跑向量化任务时耗时长达三四个小时凌晨跑批根本完不成。后来把服务迁到带GPU的机器上同样的任务十几分钟就跑完了。如果你的数据量不小向量化这步千万别省GPU。另一个容易忽视的问题是批量推理时的显存占用。大batch size能提高吞吐但会把显存打满极端情况会OOM。我们建议从32开始调试batch size观察显存和耗时曲线找到一个吞吐和稳定的平衡点。调好之后给向量化服务配上队列和超时降级这样即使模型服务波动也不会拖垮主流程。4.3 Redis缓存穿透与空值保护线上推理链路里Redis承担了特征存储的职责但缓存穿透问题几乎一定会遇到。所谓缓存穿透就是某个用户ID在Redis里没有特征请求直接打到下游存储一旦这样的请求量大了下游数据库就会被打爆。我们的解决方案是空值保护查不到特征时在Redis里写一个较短的过期空标记后续请求直接返回默认特征同时对这类请求单独记日志定期分析是特征没算出来还是用户真的没有行为数据。4.4 冷启动用户如何破局冷启动是推荐系统绕不开的难题。新用户没有历史行为兴趣向量无从算起这时候如果硬走向量检索返回的结果基本是随机内容。我们的做法是给冷启动用户走规则召回按照内容分类的热度排行来推荐同时用较短的时间窗口试探性推荐多个分类一旦用户产生点击立刻更新他的兴趣向量。这个方法不算高性能但实操下来很稳新用户的次日留存有提升。更激进的做法是接入人群相似度找一批和老用户行为最相似的新用户借他们的兴趣向量来推荐这个可以等链路稳定后再尝试。4.5 工作流编排别让一条流水线拖垮所有任务我前面提到工作流要拆成DAG这里补一个真实案例。有一次某个上游数据源延迟了导致依赖它的三个任务全部挂在等待状态而另外两个不相关的任务也因为我们最初的线性编排方式被连带阻塞。后来我们花了一个下午把任务流改成了层次依赖不相关的任务各自独立调度互相之间没有阻塞。这个改动看似不大但让整个数据链路的稳定性上了一个台阶。如果你的项目还在用一个大脚本跑全流程听我一句劝早点拆开。5. 一些值得说的经验补充分享5.1 数据库思维和AI思维其实不冲突很多从数据库转过来做AI的同学会有一种“我是不是得把SQL全扔掉”的错觉。我自己的体验完全相反数据库锻炼出来的数据敏感度、一致性意识、血缘追溯能力在AI链路里全都是核心能力。你懂主键、懂索引、懂join你天然就能理解特征存储为什么要按用户维度建模为什么要控制特征的更新频率为什么要设计合理的过期策略。AI工程里最缺的其实就是这种能把数据逻辑理清楚的人。5.2 埋点设计决定AI的上限这一点我要重点强调因为它是最容易被低估的环节。模型也好特征也好都建立在埋点数据之上。埋点设计得稀烂后面所有环节都在垃圾数据上做文章。我们的项目中期曾经想增加一个“用户滑动速度”的特征结果埋点方案里根本没有这个字段只能重新发版等数据积累一下就是一个多月的空窗期。所以如果你现在还在产品早期请务必重视埋点规范把能想到的关键行为字段都提前埋上这会节省后面巨大的返工成本。5.3 从数据库到AI最难的是链路思维最后说一个贯穿全篇的核心体会从数据库到AI真正难的并不是某一个单独的技术点而是链路思维。数据库时代的开发往往只关心自己的表和接口AI时代的工程要有拉通全链路的能力。你写出一张特征表要清楚它会被谁消费你上线一个向量检索服务要清楚它和特征缓存的依赖关系你训练好一个模型要清楚它上线后哪条数据链路会给它提供反馈。每一环都像齿轮一样嵌在一起任何一环松动都会体现在最终的效果上。我个人在实际操作里养成了一个习惯每做一次链路改动就把整条链路的依赖图画一遍哪怕只是草草的几笔。这个习惯让我在排查问题的时候永远能在五分钟内定位到大概的环节而不是像个无头苍蝇一样到处看日志。这个方法也推荐给你尤其是在数据链路复杂起来之后它的价值会越来越明显。