
1. 背景团伙欺诈为什么绕过了规则引擎我们团队负责某头部消费金融公司的反欺诈中台日均处理信贷申请约 200 万笔其中线上小额现金贷占 70%。2024 年 Q3 之前风控链路是典型的规则引擎 单点特征架构每笔申请先过 300 多条规则设备指纹、IP 频次、手机号归属地等再进入 XGBoost 评分模型。问题出在团伙欺诈上。黑产用改机工具批量伪造设备指纹用接码平台批量注册手机号单看任何一个特征都正常。但把这些申请放到关系网络里看会发现大量申请共享同一批 IP 段、同一批设备型号、甚至同一批紧急联系人。2024 年 8 月我们做了一次离线回溯用图算法在 1 亿节点、20 亿边的全量关系图上跑连通分量发现约 4.7% 的通过申请能关联到 500 人以上的欺诈团伙而当时线上规则引擎仅拦截了其中不到 30%。更麻烦的是时效。团伙欺诈是打一枪换一个地方黑产今天使用这批手机号明天就更换一批。离线图计算执行一次全量需要 6 小时等结果出来欺诈团伙早已更换阵地。我们需要的是分钟级的关联风险识别将这笔申请和最近 1 小时内的哪些申请共享了异常资源实时计算出来。2. 踩坑与现状单点特征与离线图的两头堵复盘后我们把根因归结为三点第一特征工程是平面的。规则引擎和 XGBoost 的输入都是单笔申请自身的属性设备、IP、手机号没有这笔申请在关系网络里的位置这个维度。团伙欺诈恰恰是单点正常、整体异常。第二图计算是离线的。我们用 Neo4j 3.5 做离线团伙挖掘全量图构建 连通分量计算一次全量要 6 小时只能 T1 出结果。欺诈团伙的存活周期往往只有几小时到几天离线结果天然滞后。第三向量检索和图查询是两张皮。我们尝试过用向量相似度找相似申请比如把设备指纹、IP、行为序列编码成 embedding也尝试过用图查询找关联申请但两者是分开计算的结果没有融合。向量检索能发现特征相似但无显式关联的申请图查询能发现有显式关联但特征不相似的申请只有把两者结合才能覆盖团伙欺诈的两种形态。3. 方案向量召回 图扩展的两阶段融合我们最终选型是pgvector 0.5 Apache AGE 1.4都跑在 PostgreSQL 15 上。选这个组合而不是 Neo4j Milvus核心考量是风控团队已有的数据管道都是 Postgres 生态引入 AGE 和 pgvector 可以复用同一套备份、监控和权限体系运维成本最低。整体架构分两阶段实时申请事件 KafkaFlink 1.18 特征计算生成申请向量 embeddingpgvector 向量召回 Top-K 相似申请取回相似申请的实体ID集合Apache AGE 图扩展查询关联风险评分风控决策引擎第一阶段向量召回。每笔申请进来Flink 1.18 实时计算 128 维特征向量设备指纹 hash、IP 段、行为序列的 embedding写入 pgvector 表。查询时用余弦相似度召回 Top-50 相似申请阈值设为 0.85。第二阶段图扩展。把 Top-50 相似申请的实体 ID手机号、设备、IP、紧急联系人作为种子在 AGE 图里做 2 跳扩展统计这些实体在最近 1 小时内的关联申请数。如果某笔申请通过向量召回命中了 30 个相似申请而这 30 个申请又共享了同一个设备 ID且该设备 ID 在最近 1 小时关联了 200 笔申请那这笔申请的风险评分直接拉满。3.1 环境与依赖组件版本说明PostgreSQL15.4基础数据库pgvector0.5.0向量检索扩展Apache AGE1.4.0图数据库扩展Flink1.18实时特征计算Kafka3.6事件消息队列3.2 建表与索引-- 启用扩展CREATEEXTENSIONIFNOTEXISTSvector;CREATEEXTENSIONIFNOTEXISTSage;LOADage;SETsearch_pathag_catalog,$user,public;-- 申请向量表CREATETABLEapplication_vector(application_idBIGINTPRIMARYKEY,feature_vector VECTOR(128),created_at TIMESTAMPTZDEFAULTnow());-- HNSW 索引m16, ef_construction64CREATEINDEXONapplication_vectorUSINGhnsw(feature_vector vector_cosine_ops)WITH(m16,ef_construction64);3.3 两阶段查询核心 SQL-- 第一阶段向量召回 Top-50WITHsimilarAS(SELECTapplication_id,feature_vector:query_vectorASdistanceFROMapplication_vectorWHEREcreated_atnow()-interval1 hourORDERBYfeature_vector:query_vectorLIMIT50),-- 第二阶段图扩展统计关联实体expandedAS(SELECTCOUNT(*)ASrelated_countFROMsimilar sJOINage_cypher(MATCH (a:Application)-[:SHARES_DEVICE]-(d:Device)-[:SHARES_DEVICE]-(b:Application) WHERE a.id IN $ids RETURN b.id,jsonb_build_object(ids,array_agg(s.application_id)))ASg(b_idBIGINT)ONtrue)SELECTCASEWHENrelated_count100THENhigh_riskWHENrelated_count30THENmedium_riskELSElow_riskENDASrisk_levelFROMexpanded;预期运行结果单笔查询 P95 耗时约 180ms向量召回 120ms 图扩展 60ms相比纯图查询的 2.3 秒性能提升约 12 倍。4. 踩坑记录三个真实报错与解决坑一HNSW 索引构建 OOM。首次灌入 5000 万条向量时CREATE INDEX直接报错ERROR: memory exhausted DETAIL: Failed while allocating 2147483648 bytes.排查发现 pgvector 的 HNSW 构建是内存密集型的默认maintenance_work_mem只有 64MB而 5000 万条 128 维向量需要约 8GB 内存。解决调大maintenance_work_mem到 12GB并分批构建先建 1000 万条再INSERT剩余数据。坑二AGE 的 Cypher 参数化查询报错。最初用字符串拼接传 ID 列表遇到特殊字符直接报错ERROR: cypher query is not a valid Cypher query DETAIL: Invalid input [: expected an identifier排查发现是age_cypher的$ids参数没有正确绑定。解决改用jsonb_build_object显式传参避免字符串拼接。坑三向量漂移导致召回率下降。上线两周后向量召回命中率从 92% 降至 78%。排查发现是特征工程里设备指纹的 hash 算法升级了新旧向量不在同一语义空间。解决在向量表里加feature_version字段查询时只召回同版本向量并做每日增量重算。5. 权衡验证数据与代价上线后我们进行了 A/B 测试对照组是纯规则引擎 离线图实验组是向量 图融合方案运行了 7 天指标对照组实验组提升团伙欺诈识别率28.6%67.3%38.7pp单笔查询 P95 耗时2.3s180ms12.8x误杀率正常用户被拒1.2%1.8%0.6pp分钟级关联风险覆盖0%离线 T1100%实时-团伙欺诈识别率从 28.6% 提升至 67.3%代价是误杀率上升了 0.6 个百分点。我们通过调低向量召回阈值从 0.85 降到 0.82和增加人工复核队列将误杀率降至 1.4%。6. 总结适用边界哪些业务不要照搬适用场景团伙欺诈、设备农场、养号攻击这类单点正常、整体异常的风险。特征是实体之间有显式或隐式的关联且欺诈团伙存活周期短、需要分钟级响应。金融、电商、社交平台的实时风控都适合。不适用场景纯个体欺诈比如单个人伪造收入证明、低频高价值交易一天就几笔向量召回没有统计意义、以及数据量极小少于 10 万节点的场景。这些情况用规则引擎或传统图数据库离线分析就够引入向量 图融合是过度设计。边界条件向量召回依赖 embedding 质量特征工程一旦变更必须做版本管理图扩展的跳数不能太大超过 3 跳会引入大量噪声HNSW 索引的内存开销在亿级数据量下需要提前规划存储。最后说一句向量库和图数据库不是替代关系而是互补关系。向量负责找相似的图负责找关联的两者融合才能覆盖团伙欺诈的完整形态。这套方案我们跑了半年稳定性和收益都验证过了值得在同类场景复用。