资讯详情

资讯详情

基于Hologres的达人圈选系统实战:从分钟级到秒级的标签筛选与向量召回

1. 项目背景从“找得到”到“找得准”达人圈选到底难在哪做运营和增长的同学应该都有体会达人圈选这件事听起来就是“从库里捞人”但真正落地的时候完全不是那么回事。早期我们用离线表跑批一个圈选任务动辄跑半小时运营同学拿着提数需求排队等结果等拿到名单的时候热点早过去了。后来换成了 Elasticsearch检索速度快了不少但碰到“有消费能力、美妆垂类、近 30 天活跃、去年同期买过精华但今年没买”这种复杂条件得写一堆 bool 查询性能扛不住不说圈出来的结果也经常不够准。这个项目的起点很朴素帮运营团队把达人圈选从“找得到”升级到“找得准”。“找得到”意味着能从千万级达人池里把满足基本条件的用户捞出来比如性别、年龄、城市这种硬性标签。“找得准”则要复杂得多它要求系统能理解“垂类偏好”“内容调性”“消费潜力”“复购节奏”这些模糊但决定投放效果的特征同时在圈选结果返回的速度上不能拖后腿。我们最终选择用 Hologres 来承载这套系统。说实话最开始团队内部是有争议的因为 Hologres 在很多人印象里是个 OLAP 数仓拿来做圈选场景总觉得有点“不务正业”。但实际做下来它的几个特性对达人圈选场景几乎是量身定做的原生支持向量检索、全文检索、列存高效过滤还能把这些能力在一条 SQL 里融合起来。这套系统的核心思路就是不再区分“清洗层—标签层—圈选层”三个割裂的阶段而是把达人特征、行为序列、向量 embedding 统一沉淀到 Hologres 的一张宽表里圈选就是一次 SQL 交互。适合谁来参考这篇内容如果你是负责用户增长、达人运营的数据开发或后端工程师正被“标签筛选效率低、圈选结果不够精准、实时性跟不上活动节奏”这几个问题困扰那这篇实操记录应该能给你一些直接可落地的思路。整个系统上线后的效果是单次复杂圈选从分钟级降到秒级返回运营自助圈选的覆盖率提升了 60% 以上这是我最初没想到的收获。2. 整体架构与核心思路为什么 Hologres 适合做这件事2.1 圈选系统的传统实现方式问题出在哪在动手写 Hologres 方案之前我觉得有必要把传统方式的三个坑讲透这是后面所有设计决策的背景。第一个坑是数据链路太长。传统的达人圈选链路通常是原始日志入 Kafka经过 Flink 清洗后落到离线数仓 Hive再加工成标签宽表最后同步到 HBase 或者 Elasticsearch 供圈选查询。链路一长数据时效性就差。运营上午要圈“昨晚直播互动过的达人”数据要到下午才能用这还算快的。如果涉及跨月行为对比T1 都未必够用标签数据至少得攒两三天。第二个坑是查询模型割裂。标签筛选在 Elasticsearch 里做行为序列在 ClickHouse 里算文本召回靠 ES 的 match 查询向量召回又单独接一套 Milvus。听起来挺合理但业务上你很难把一个复杂的圈选逻辑拆得这么干净。比如“美妆垂类、粉丝量级在 50 万到 200 万之间、近 30 天视频完播率高于 60%、同时和某品牌历史合作过 3 次以上”这个条件要跨三四个存储引擎做 join。开发成本是一方面更麻烦的是运营同学要的是一个兜底全部条件的最终名单而每个引擎返回的候选集做交并集时很容易出现数据口径不一致的问题。第三个坑是圈选条件无法复用。同一个“高购买力人群”的标签在 A 场景是“客单价高于 300 元”在 B 场景又变成了“近 90 天有 2 次以上千元订单”。每个场景各写各的 SQL 和查询逻辑标签管理混乱最后谁也说不清一套圈选结果是怎么算出来的。2.2 Hologres 在圈选场景的独特定位我第一次接触 Hologres 的时候先是把它当普通数仓用——建几张表、跑跑聚合、出报表没什么特别的感觉。直到后来做到实时特征查询才意识到它的底层存储和索引设计跟传统数仓不一样。Hologres 本质上是一个 HSAP 系统翻译成人话就是它既能当数仓做离线批量写入又能当在线服务引擎支撑高并发点查和实时写入同时还能跑复杂的分析 SQL。这个特性对达人圈选系统来说非常关键因为圈选场景天然就是“离线的数据、在线的查询”。达人标签数据是批量加工的但圈选请求是一个个实时进来的而且每次圈选条件的组合方式都不一样没法预先物化结果集。项目里我们用到的核心能力有三块第一列存和自定义索引的结合。Hologres 底层用列存配合 Bitmap 索引可以高效处理多条件过滤。这在圈选场景里价值很大因为圈选条件往往是一堆等值、范围、IN、LIKE 的混合体。传统数仓对这种查询要么全表扫要么靠分区裁剪而 Hologres 的位图索引能做到“按需”扫数据相当于给每列建了一个倒排。第二原生向量检索。这是后来我们敢把算法团队产出的人设 embedding 放到同一个引擎里的原因。Hologres 内部集成了 Proxima 向量引擎建表时声明一个 vector 字段写入向量数据查询的时候用 order by distance 排序就行。不需要额外部署一套向量数据库也不用维护两套数据的一致性。第三一条 SQL 融合多维检索。标签过滤、全文检索、向量召回可以在同一张表上做交并集优化器会自动选择执行策略。这在系统落地阶段帮了大忙因为不需要在应用层编排多个引擎的查询逻辑所有条件全部下推到 Hologres 这一层完成。2.3 整体架构设计与数据流转整套系统的数据流转分为五层这里我画个简化的逻辑说明数据接入层达人基础信息、内容数据、互动行为日志统一通过 Flink 实时写入 Hologres同时离线数据用 DataWorks 批量同步。标签加工层用 Hologres 本身的 SQL 能力做特征加工产出达人核心标签表包括自然属性、内容属性、商业属性、行为偏好。向量生成层算法团队用预训练模型产出达人内容调性向量、人设相似度向量写入另外一张宽表。圈选服务层对外提供一组 Restful API接收圈选条件拼装成 SQL 后请求 Hologres返回命中达人列表。应用层运营端圈选后台、投放系统、数据分析看板。这套结构和很多公司做用户画像圈选的架构没什么本质区别唯一的差别是我们没拆微服务也没有多引擎 join圈选服务就一个瘦瘦的查询网关核心逻辑全在 SQL 里。3. 核心表结构设计与索引选择3.1 达人宽表把标签做成列圈选系统最核心的物理模型是一张达人宽表我们把达人相关的维度、标签、行为指标、向量全塞进去。这里有一个很重要的设计原则宁可宽不要窄。为什么因为圈选的查询条件组合是无限的如果拆成多张表用户每次圈选都要做 join性能损耗非常大而且 SQL 写起来也很痛苦。把它做成宽表每个标签一列查询的时候只有用到的列会被加载。列存嘛这是基础认知。我们最终的表结构大致长这样CREATE TABLE daren_profile ( daren_id TEXT NOT NULL, gender TEXT, age_group TEXT, city_level TEXT, province TEXT, fans_cnt BIGINT, avg_play_rate FLOAT8, avg_like_cnt BIGINT, avg_comment_cnt BIGINT, avg_share_cnt BIGINT, category_tags TEXT[], -- 垂类标签数组 cooperate_brands TEXT[], -- 合作品牌列表 first_active_date DATE, last_active_date DATE, active_days_30 BIGINT, purchase_ability TEXT, is_verified BOOLEAN, embedding VECTOR(128), -- 内容调性向量 ext_info JSONB, PRIMARY KEY (daren_id) );几个关键的选型思考标签列尽量用数组类型。比如 category_tags、cooperate_brands用 text[] 比用字符串拼接好得多查询时可以直接用 array_contains 之类的语义配合 GIN 索引也能加速。embedding 字段直接放在宽表里不单独建表。这样圈选条件里如果同时有标签过滤和向量召回只需要在同一张表上做一次操作不会跨表扫描。ext_info 用 JSONB 存高度个性化的字段比如不同垂类的专属属性这样不用频繁改表结构。但是要注意JSONB 字段能不用就不用查询性能不如普通列。主键是 daren_idHologres 里会基于主键建行存实现快速点查。但圈选场景更多是分析型查询所以把 segment 属性设成 COLUMNAR 存储模式兼顾两种场景。3.2 行为聚合表跑不动的指标提前算好除了宽表我们还建了一张行为聚合表专门存达人最近 7 天、14 天、30 天、90 天的互动指标。为什么单独建这张表而不是把列加到宽表里因为行为指标是周期性的今天算完明天又变如果都放宽表更新压力会很大每次 Flink 写入都要 rewrite 整行资源浪费很严重。行为聚合表的粒度是“达人 统计周期 指标类型”用行存存储key 是 daren_id按周期聚合成多列。查询的时候先根据圈选条件里的时间范围找到对应的周期列再从宽表里过滤。这样既保证了指标时效性又避免了大宽表的频繁更新。3.3 索引选型位图索引是圈选查询的加速器这里重点说位图索引。Hologres 的列存默认是压缩存储但如果不建索引查询密集值列比如性别、城市等级时还是要做全列扫描。我们测试下来在两千万达人数据量下单列等值过滤需要 200 到 400 毫秒加上两三个这样的条件查询时间轻松超过一秒完全达不到我们要的“秒级返回”。加了位图索引之后单列等值过滤基本能压在 30 毫秒以内多条件组合也只多了不到 50 毫秒。原理很简单位图索引会把每个取值映射成一段位图过滤时直接做位运算相当于把磁盘扫描变成了内存位图运算进度完全不一样。具体建索引时我们踩了一个坑给所有列都建位图索引。后来发现低基数列比如 gender 只有三个值建索引效果还行但高基数列比如 fans_cnt建位图索引反而变慢了因为每个取值对应位图很小但值太多导致位图索引本身膨胀得厉害。经过反复测试我们的策略是低基数列性别、城市等级、认证状态建位图索引范围查询列粉丝数、互动量不建位图用聚类索引数组标签列建 GIN 索引embedding 列走 Proxima 索引。注意位图索引不是建得越多越好低基数、重复度高的列收益最大高基数列可能适得其反。建完索引后一定要用 explain 看执行计划确认优化器真的选中了索引。4. 圈选查询的实现细节把复杂条件翻译成 SQL4.1 基础标签圈选一段真实的圈选 SQL把需求和 SQL 对应起来是圈选系统开发日常占比最高的工作。运营同学提需求的时候不会说 SQL他们说“我想要美妆垂类、女性、粉丝数 50 万以上、近 30 天活跃的达人”。翻译成 SQL 就是SELECT daren_id, daren_name, fans_cnt FROM daren_profile WHERE category_tags ARRAY[美妆] AND gender female AND fans_cnt 500000 AND active_days_30 0 LIMIT 200;这条 SQL 走下来如果 category_tags 建了 GIN 索引、gender 建了位图索引执行时间大概在 100 毫秒左右。但在实际过程中我们发现只做这种基础圈选运营同学并不满意。他们抱怨两点第一圈出来的名单虽然符合条件但看起来“不像达人”很多是发了几条内容就没动静的僵尸号第二条件一松一紧就不好把握松了名单大而杂紧了名单又太小。这就是“找得到”和“找得准”的分水岭。基础标签只能证明“这个人有这些属性”但证明不了“这个人是这类人里值得合作的”。所以后面我们引入了行为质量分和向量召回来回答“凭什么选他不选别人”的问题。4.2 行为质量分排序让圈选结果更有说服力我们定义了一个行为质量分 quality_score综合考虑达人的内容打开率、完播率、互动率、粉丝增长趋势权重由运营团队和算法团队共同商定。打分的 SQL 逻辑如下CREATE OR REPLACE FUNCTION calc_quality_score( avg_play_rate FLOAT8, avg_like_cnt BIGINT, fans_cnt BIGINT ) RETURNS FLOAT8 AS $$ SELECT 0.4 * avg_play_rate 0.3 * ln(avg_like_cnt 1) / ln(fans_cnt 1) 0.3 * CASE WHEN avg_comment_cnt 100 THEN 1.0 WHEN avg_comment_cnt 50 THEN 0.8 ELSE 0.5 END $$ LANGUAGE sql;这个打分不是严格意义上的复杂算法但至少能筛选掉“数据虚高但互动极差”的达人。圈选的时候我们用它做排序优先展示质量分高的达人。改动后的圈选 SQL 变成SELECT daren_id, daren_name, fans_cnt, quality_score FROM daren_profile WHERE category_tags ARRAY[美妆] AND gender female AND fans_cnt 500000 AND active_days_30 0 ORDER BY quality_score DESC LIMIT 200;同一个条件圈出来的名单质量提升非常明显。原来按粉丝量倒序取出来的头部达人虽然粉丝多但很多是早年做微商囤的粉内容互动惨不忍睹。换成质量分排序后前 200 名几乎都是真正能撬动转化的人。4.3 向量召回从“满足条件”到“调性一致”标签圈选解决的是“硬条件”但达人营销里还有一项软指标——内容调性。一个高端护肤品牌要找达人硬条件可以是“女性、粉丝 50 万以上、美妆垂类”但调性上还得匹配不能找天天发九块九包邮种草号的博主。我们把达人的历史内容标题、评论、视频脚本过一遍预训练模型生成一个 128 维的内容调性向量存入 embedding 字段。圈选时用户上传一个参考文本或拿一个种子达人编码成 query 向量用向量相似度找内容调性最接近的一批达人。SELECT daren_id, daren_name, fans_cnt, embedding :query_vector AS distance FROM daren_profile WHERE category_tags ARRAY[美妆] AND fans_cnt 500000 ORDER BY embedding :query_vector ASC LIMIT 200;在 Hologres 里向量距离操作符是代表余弦距离值越小越相似。这个查询表面上看也是 order by但执行计划里会走 Proxima 索引不会全表计算距离所以性能不用太担心。实际测试中在一张一千万行的表里做“标签过滤 向量排序”的组合查询返回前 200 条相似达人耗时大概在 300 毫秒左右。相比纯扫全表做暴力计算——那种方式保守估计也得两秒以上——这个优化效果很理想。4.4 全文检索当圈选条件变成一句描述运营同学的另一种常见操作是不选标签直接输入一句“爱用国货美妆的学生党精致女孩”。这种非结构化的圈选描述以往只能靠运气碰标签组合现在我们借用 Hologres 的全文检索能力来兜底。给达人表增加一个 profile_text 字段内容拼接达人的简介、历史内容标题、热门评论标签建全文检索索引。查询时直接用中文分词检索SELECT daren_id, daren_name FROM daren_profile WHERE profile_text 爱用 国货 美妆 学生 ORDER BY ts_rank(profile_text, query) DESC LIMIT 200;这里关键是中文分词。Hologres 内置的分词器对中文支持还可以但在处理“国货美妆”这种复合词时偶尔会拆得不够好。我们试过挂 IK 分词插件效果有提升尤其是对品牌词和垂类词。如果项目里中文检索需求重建议从第一天就上自定义分词词典把垂类关键词、品牌名、达人常用语加进去。提示全文检索和向量召回是很配的组合。向量召回擅长语义相似全文检索擅长关键词匹配两者结合能充分照顾“语义派”和“关键词派”两种运营习惯。Hologres 一条 SQL 可以同时写两个条件不冲突。5. 性能优化实践从百万级到千万级数据量查询不衰减5.1 预计算和物化视图永远不过时的优化手段如果是固定组合的圈选条件比如“一线城市女性美妆达人”每次都实时跑全量数据收益很低。我们针对运营同学使用频率最高的三十多个固定圈选条件组合建了物化视图Hologres 自动维护刷新。物化视图的本质是空间换时间尤其在数据量持续膨胀的阶段这个策略帮我们把高频查询的耗时从秒级进一步压低到了几十毫秒。低频的长尾条件则直接走实时查询反正数据分层合理的话响应时间也可控。5.2 数据分布优化让查询只在必要的分片上跑Hologres 的分布式架构里数据会根据分布键分散到不同的 shard。分布键选得好不好对查询性能影响非常明显。我们踩过一个坑用 daren_id 做 distribution key结果圈选高频条件都是按 category_tags 和 city_level 来过滤的导致查询要么广播到所有 shard要么得等最慢的 shard 跑完才能汇总性能很磕碜。后来把分布键改成 category_tags 的 hash 值同时把查询条件里最常见的 category_tags 过滤下推到存储层这样每个 shard 只需要扫自己本地的一部分数据不需要全量广播。调整之后查询耗时稳定下降了 40% 左右。5.3 写入链路避坑批量写资源争抢问题Hologres 能扛高并发查询但写入和查询如果同时压在同一批计算资源上容易出现相互抢占。我们上线初期就撞上过Flink 实时写达人行为数据一旦写入峰值上来线上圈选查询的 p99 延迟直接涨了两倍多。解决方法是把读写资源拆开在 Hologres 上建了两组 shard一组承担实时写入和批处理另一组只服务圈选查询。同时控制 Flink 的写入批次大小从原来每两秒刷一次改成每五秒刷一次单批次数据量翻倍整体资源争抢明显缓解。5.4 慢查询排查三板斧我这里整理一份 Hologres 慢查询排查的方法论亲测有效打开慢查询日志Hologres 控制台有查询诊断页面直接看每条查询的耗时和扫描行数。先定位是不是扫描行数太高如果是大概率是索引没生效。用 explain 看执行计划确认每个过滤条件下推到哪个算子有没有走索引有没有出现 TableScan 扫全表。针对性建索引或改 SQL。如果单列过滤慢加位图索引如果 join 慢检查分布键和数据倾斜。6. 常见问题速查表与避坑经验这块我按问题、原因、解决方案三个维度整理都是我们项目里真实碰到过的。现象根本原因解决思路加了多个过滤条件后查询反而变慢优化器选错了索引路径可能走了全列扫描再过滤查看执行计划给高频过滤列建位图或 GIN 索引向量召回结果不相关向量本身质量不行可能模型用错了领域语料重新训练 embedding 模型或者换用下游任务做微调物化视图刷新不及时增量更新的触发条件设置不合理手动触发刷新策略高频表缩短刷新间隔相同 SQL不同时间执行耗时差异巨大查询可能打到了不同的 shard 上数据分布不均匀检查分布键确认高频过滤条件是否和分布键契合中文全文检索效果差自带分词器分词粒度不匹配业务词汇挂 IK 分词器配置自定义词典圈选结果里混入低质达人过于依赖硬性标签没考虑行为质量引入 quality_score 排序结合互动指标同一达人出现在多个圈选名单里造成重复触达圈选条件交叉重叠在应用层维护排除名单或圈选时显式带上排除 ID 列表7. 上线之后的运营反馈优化闭环系统上线之后运营同学的使用方式也在发生变化。刚开始他们习惯从标签面板一层层点选出来的结果就是一张表。后来我们把质量分、向量相似度这些非语义结果做了可视化展示选人逻辑慢慢变成了“先用标签捞池子再用相似度排序找人”。这里有一个从运营侧反馈过来的例子我觉得很有代表性。某护肤品牌要做新品上市推广运营在系统里圈选达人条件大概是“女性、粉丝 50 万以上、美妆垂类、近 30 天有内容更新”。按旧逻辑这批名单大概有 8000 人运营要从里面人工挑 200 个工作量巨大而且挑出来的大多是自己熟悉的那批熟人。用了新系统之后运营基于一个过往合作效果最好的种子达人做向量召回再加上标签过滤直接拿到了内容和调性都贴近的 200 人整个圈选过程三分钟搞定。从“找得到”到“找得准”系统的价值不只是把 SQL 跑快了而是让运营能把“我觉得这个达人合适”变成可复现、可解释的规则。这也是为什么后来我们把每次圈选的条件、排序分数和入选名单全部记录下来沉淀成历史分析数据。现在反过来看这批数据本身就是一套很宝贵的达人分群方法论。8. 一点延伸这套系统的边界和可复用的思考方式说实话Hologres 并不是万能钥匙。我们做这套系统时也评估过 ClickHouse 和专门向量数据库的方案各自都有优势。Hologres 最合适的场景是数据规模在千万到亿级、查询模式复杂多样、希望一份数据服务多种业务的中间态。如果数据量到了十亿级以上或者要支撑千人千面的实时个性化推荐那可能需要引入更底层的分布式存储和计算引擎。但这个项目最让我觉得有价值的地方不完全是技术选型而是把“圈选”从一个单点查询问题拆成了数据组织、索引设计、特征加工、效果排序这样一个有层次的系统工程。在动手写 SQL 之前先把要解决的问题拆到位比选什么引擎重要得多。个人在实际操作中的体会是Hologres 的上手门槛不高但要用好得把它的索引机制和存储模型琢磨透。不要把它当普通 MySQL 用也别把它当纯数仓用。它的正确打开方式是理解它擅长在哪些场景里把“分析”和“在线”融合起来——达人圈选只是一个代表换成商品选品、用户分层、线索打分底层思路都是通用的。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →