广告倒排服务极致优化

news2024/11/26 8:55:04

在这里插入图片描述

作者 | XY

导读

漏斗优化是检索系统不变的话题,过去一年来,广告漏斗优化一改往日做“加法”,而通过简化漏斗,提升全系统一致性。如百度这样庞大的广告库规模、高流量规模以及复杂的业务规则,要做到极简的漏斗层次,需要最高效的策略设计和最极致的工程实现。本文重点介绍了百度Geeker们在倒排数据结构上如何“抠细节”达到倒排召回无截断,对大家做高性能系统也将有所启发。

全文6162字,预计阅读时间16分钟。

01 业务背景 - 全系统Limitless

大家都清楚,广告漏斗包括召回、粗排、精排这三部分,理想中的漏斗上宽下窄很规整,而现实中因为种种原因,漏斗已经略显飘逸了,这种不一致性会带来很多业务继续发展的复杂度。我们希望达到:模型一致,精简漏斗,全系统Limitless。

图片

我们对Limitless的认识:细节处见真章,挑战软件工程性能极限,方能漏斗近似无截断。

今天想跟大家聊聊『BS Limitless』项目里我们怎么抠细节的,整个项目其实挑战很大,网络、计算和存储方方面面都涉及到,一篇短文很难讲透,因此我决定选一个数据结构-倒排表,让大家感受到『极致』优化。

02 技术背景 - 倒排表

先介绍一下优化前的倒排表,它的组成比较经典特点,HashMap<keysign, SkipList>。一次检索pv根据触发的N个词(keysign)扫描拉链(SkipList),广告业务投放特点天然会有长链、超长链,为此链表需要有序,做过漏斗的同学知道,在倒排阶段排序能用的信息其实是很少的,这也说明了扫描Limitless对业务的高价值。

图片

这样一个小小的数据结构承担了各方的要求,1、读性能决定有限时间能放出多少量,2、实时插入决定客户投放能不能立即生效,3、单库规模巨大,内存损耗要低。对工程的合理要求,确实是既要…又要…还要…。在Limitless的大背景下,我们要显著提升1达到scan limitless,但是不能损害到2和3

回过头来看,这么简单的数据结构,Limitless的瓶颈到底是什么?大家回忆一下计算机体系结构的内容。cpu并不直接访存,而通过层层缓存到达内存,存储层次越低,它的容量越大但延迟越高,mem和L2、L1之间有量级差距[1]。List这种数据结构显然缺乏空间局部性。

图片

缓存不亲和的List对比缓存友好的Array,在扫描上究竟有多差呢?我们做了如下的评测,其中横轴代表链长或数组长度,纵轴是平均到单条元素的扫描耗时,结果是10+差距。换成数组Array,也是不行的,客户要求实时生效,为了低效拷贝损耗需要O(2N)的增长速度,内存成本要求也不能满足。

图片

03 方案 - HybridIndexTable

我们针对业务的特点和Limitless的要求进行极致设计和优化,推出了新一代的内存数据结构HybridIndexTable,简称HIT。升级后的倒排表

1、用HashMap索引keysign,

2、短链采用连续存储,长链则是一棵叶子连续存储的前缀树,前缀树则参考了业界AdaptiveRadixTree,简称ART,

3、短链和叶子的)连续存储都采用了自研的RowContainer,简称RC。

在简短的数据结构之外,还要和大家分享两个关键细节:

1、Key序路由兜底超长链有序,

2、RC间无序,以RC为单位扫描。

HIT这样的数据结构有如下三点优势,恰到好处地满足了前面的三个要求。

  • 读取性能高,连续存储+前缀树降低cachemiss,线程安全做法reader-friendly,还有面向负载的优化;

  • 更新时效性高,连续存储但append-only/mark-delete;

  • 内存损耗少,应用了大量的自适应算法。

HIT上线后拿到了非常可观的业务收益,Limitless的道路上 技术就是生产力!

图片

04 HIT缘起,内存索引ModernCPU的探索

内存索引领域已在面向Modern Cpu深耕,ModernCpu由于Cpu运行得很快,使得缓存一致性的影响、访存的影响成为高性能的瓶颈。内存索引在2000s也有些阶段性的标志成果,包括FAST[4],它是面向体系结构的数据结构设计的典型,无论是思想还是成果非常适用于静态数据;CSBtree[2][3],它提出数据结构上通过KV分离,使得一次访存能比较多个Key,同时还提出了SMO的无锁解法;还有ART前缀树[5]。有序索引中BTree居多,我们为何注意到了前缀树呢?

链表类型的缓存失效发生在每次访问下一个节点,缓存失效复杂度O(n)。排序树类型的缓存失效发生在访问下一层节点,缓存失效复杂度O(lgn)。而对于前缀树来说,缓存失效只和 键长k(len)/扇出s(pan) 有关,缓存失效复杂度O(k/s)。如下图[5],假设k是键长的bit数,随着存储数据量上涨2(k/8)、2(k/4)、…,完全平衡树高不断增加,分别是k/8、k/4、…,而对于一颗前缀树,树高对于给定的span是恒定的,如针对span=8和4,树高分别是k/8、k/4。前缀树更加缓存友好。

图片

这里简单介绍下前缀树,英文是Trie、Prefix tree或Digit tree,左图是维基百科的实例,这是个典型的没有任何优化的前缀树,一方面,只有单分叉的情况下也多分出一级,比如inn;另一方面,由于在分支节点需要分配 2 ^ s * pointer 的内存,对于实际扇出极少的场景,内存损耗非常大。

RadixTree是一种通过合并前缀(PathCompression)优化内存的前缀树。合并前缀是指压缩掉仅有一个孩子的分支节点,这样存在的节点或者是叶子节点,或者是有分叉的分支节点。名字中的radix=2^span,它在radix=2的情况下,也叫做Patricia tree[6]。Linux PageCache页面缓存用的是RadixTree,以太坊币ETH的核心数据结构是Merkle Patricia tree。中间图是按照RadixTree重新组织的。

即便有合并前缀,由于大部分扇出是填不满,浪费了空间。比如上面例子的RadixTree(radix = 256, s=8),那么即便在第一级只有t、A、i,也需要多分配 253 * 8 ~ 1Kbytes。调整radix(span = lg(radix))是一种内存优化方式,但这提高了树高增加了缓存失效[5]。2013年,A(daptive)R(adix)T(ree)[6]用自适应的节点类型来解决问题,用适合数据分布的最紧凑的节点表示,而不是固定的Node256。论文中 InnerNode 分为 Node4、Node16、Node80和Node256这四种,按照实际需要的分叉数选择,通过类分摊算法(Amortization Alg)复杂度分析方法,可证明理论上这棵树内存复杂度是O(52N),其中N是存储数据量。ART论文提供了一种方式,在提高扇出降低树高的同时,还能大幅度降低内存开销。按照ART重新组织上面例子中的RadixTree,如图所示。

图片

05 HIT优化,RC实现ShortList+LongList Leaf的自适应

我们定义 RC_x 为不超过x个元素的连续存储空间,支持Append-Only和Mark-Delete操作,单元素额外成本不超过8byte。接下来看RC的设计要点。

既然RC被设计为只支持Append-Only/Mark-Delete修改的数据类型,为此元数据需有cursor和valids。同时针对不同容量的RC,为了控制理论上单条数据损耗不超过8byte,需要分别设计和实现,我们不希望引入继承virtual指针的内存开销,采用根据type分发的实现方案。

图片

RC1元数据8byte,可轻松容纳cursor和valids,那么下一阶的RC_x,x是几呢?按照分摊分析方法,RC_x至少有2个元素,也就是16byte的预算,在分配前还是要先看选型,RC_x需要变长因此元数据还有留出来8byte给dataptr,这样的话,valids不能采用std::bitset,因为bitset的实现至少需要一个字也就是8byte。valids和cursor用bit field 的方式来做分配看上去还是比较充裕的,存放58个数据元素都没问题,实际上考虑到系统分配器的特点,我们并没有这样做。

主流的系统分配器大部分是slab-based,在实际应用时,除过理论单条数据损耗,还需要考虑因内存池带来的对齐损耗。一方面,RC1所在的链约占整体的80%,这样海量的小对象适合用内存池来管理,为避免检索时候多一次内存池地址转化,我们把vaddr存储在元数据中,释放时再使用。另一方面,分散式地分配 N*sizeof(data),会造成每类slab的内部非充分使用,为此RC16+采取了Array of data_array的组织方式。

RC设计有不少实现细节,包括什么时候一次性多申请多少空间,控制内存成本和操作效率。时间关系就不多介绍了。

06 LeafCompaction优化稀疏

图片

前缀树在缓存失效方面优于BTree,ART进一步地采用自适应节点类型,既能增加扇出优化缓存访问,又能控制内存损耗。然而实际应用中,ART仍然受到key值分布稀疏的影响,这表现为在部分子树扇出很小很深(较Node256),树高无法全面降低,从而影响点查的缓存。HOT[7]提出一种自适应动态span提升平均扇出,进而降低树高的方法,具体来说,定义节点最大扇出k,寻找一种树的划分,在每个划分的分支节点数不大于k-1的前提下,沿着叶子到根的划分数的最大值取最小,这样的实际效果就是对于稀疏段的span足够大,对于密集段的span足够小,整体扇出逼近最大扇出k。如中图所示。

对于ART+目标的连续性应用场景来说,仅仅提升扇出降低树高是不够的,我们发现存在扇出很高,但扇出后叶子数据很稀疏,同时总数据量也不高,这显然影响了扫描性能。我们提出叶子合并(LeafCompaction),它包括判定器和操作算法。给定一个分支节点,如果它被判定为合并,则用一个叶子节点替换它,该叶子节点的前缀同该树的前缀,内容是该树的数据,后续插入/删除过程遵从叶子的操作方式;如果它被判定为不变,则保持。给定一个叶子节点,如果它被判定为分裂,则用一颗树替换它,该树的前缀同该叶子的前缀,内容通过BulkLoad的方式生成,如果它被判定为不变,则保持。判定器的默认算法依据子树平均和总数,节点压缩率高达90%。如图所示。

图片

实际评测效果,平均到单条数据的扫描性能,稀疏场景下LC版本优于普通版本一倍。

07 RCU面向读者实现读写安全

图片

一般使用深度优化的细粒度锁来保护倒排数据结构的并行操作,但锁竞争带来的性能抖动,完全抹杀了访存优化,我们必须探索出一种无锁(lock-free)安全模式。提到无锁lock-free,大家都觉得是CAS,其实一方面CAS不是银弹,CAS常见的写法是whileloop直到成功,如果有10个线程都在高速修改一个链表尾巴,这时候CAS只是说把陷入内核省掉了,但是还是要不停地重做,这并不能完全释放并行的能力,最好能从业务上打散。另一方面,CAS也有问题,多核下 CPU cache coherence protocol总线仲裁,导致破坏流水线。

Read-Copy-Update 是Linux内核中的一种同步机制,被用来降低读者侧的锁开销,同时提供安全的写机制,具体地来说,多个读者(reader)并行地访问临界资源,写者(writer)在更新临界资源(critical resource)时候,拷贝一份副本作为修改基础,修改后原子性替换掉。当所有读者释放了这个临界资源后(Grace peroid),再释放资源(reclaimer)。

Linux还需要较复杂的机制:

1、探测静止状态 Quiescent Status,当所有CPU都经历过至少一次静止状态时,才认为Grace peroid结束;

2、多写者间同步,避免丢失修改。

对于检索服务来说,它有下面3个特点,这些特点大幅度地降低了我们的设计复杂度:

1、单写者,我们可能有其他的准备线程并行做更重的准备工作,但只有Reload单线程负责物料更新;

2、非事务,检索线程召回的多条数据间没有严格约束;

3、读者持有时间有限,检索线程有严格的超时要求。这些特点大幅度地降低了我们的设计复杂度。

08 LearnedIndex面向Workload自适应

图片

最后,我再介绍下进行中的工作L2I。刚才都是对单链的优化缓存失效,已有不错的效果,但从更宏观全局的视角来看,我们系统还有可挖掘空间:一方面,广告投放天然导致存在较多超短链,另一方面,需要扫描过程跨表查询做各类过滤逻辑等等。这些已不单单是数据分布的问题,而是在线流量和客户投放的匹配,需要用更智能的手段来解决。

行业里面,JeffDean&TimK 联合发表了Learned Index[8]引入RMI、CDF的模型,后续TimK团队又有动态化、多维索引、sagedb等多种方向的发展,本质是构建面向负载的半自动化寻优系统。我们既要引入负载特征,但由于扫描过程很轻量,不能做过重的预测,同时作为对客户有承诺的商业系统,不能产生错误。借鉴L2I的思想,我们还做了两个事情,一方面、通过触发出口离线计算共现关系,用来合并超短链、短链,用上HIT的高性能的连续扫描能力;另一方面,取熵最大的组合<pid,cid>,提取到倒排表的bit位,确定不过1,否则为0,对于后者 fallback到点查计算。

——END——

参考资料:

[1] Software Engineering Advice from Building Large-Scale Distributed Systems, 2002

[2] Cache conscious indexing for decision-support in main memory,1999

[3] Making B±trees cache conscious in main memory,2000

[4] FAST: fast architecture sensitive tree search on modern CPUs and GPUs,2010

[5] The Adaptive Radix Tree: ARTful Indexing for Main-Memory Databases,2013

[6] PATRICIA --Practical Algorithm To Retrieve Information Coded in Alphanumeric,1968

[7] HOT: A height optimized trie index for main-memory database systems, 2018

[8] The Case for Learned Index Structures, 2018

推荐阅读:

为什么 OpenCV 计算的视频 FPS 是错的

百度 Android 直播秒开体验优化

iOS SIGKILL 信号量崩溃抓取以及优化实践

如何在几百万qps的网关服务中实现灵活调度策略

深入浅出DDD编程

百度APP iOS端内存优化实践-内存管控方案

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/90318.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

206367-33-1,抗菌肽pBD-1

pBD-1 是一种来源于猪组织的内源性组成型表达的抗菌肽 (AMP)&#xff0c;尤其在猪黏膜上皮部位表达。pBD-1 具有抗菌作用&#xff0c;并且有助于猪黏膜和系统宿主防御。pBD-1 is an endogenous and constitutively expressed antimicrobial peptide (AMP) from porcine tissues…

rocketmq源码-consumer拉取消息(push模式)

前言 在前面consumer启动的博客中&#xff0c;有说过&#xff0c;在启动过程中&#xff0c;有两个比较重要的逻辑&#xff0c;分别是负载均衡和拉取消息的service&#xff0c;这篇博客&#xff0c;主要记录拉取消息的service&#xff0c;因为前面的demo和这篇笔记中的demo&…

电磁场知识整理------2022/12/14

电磁场知识整理1、数学基础麦克斯韦方程2、麦克斯韦方程组的近似情况2.1 恒定电场2.2 恒定电流场2.3 恒定磁场2.4 动态电磁场2.5 时谐电磁场3、电磁辐射与电磁波写在最后学习资源&#xff1a;慕课上浙大的工程电磁场与波。 工程电磁场与波 1、数学基础 正交坐标系&#xff1a;…

Android 线上卡顿监控

文章目录1. 卡顿与ANR的关系2. 卡顿原理3. 卡顿监控3.1 WatchDog3.2 Looper Printer3.2.1 监控TouchEvent卡顿3.2.2 监控IdleHandler卡顿3.2.3 监控SyncBarrier泄漏4. 小结平时看博客或者学知识&#xff0c;学到的东西比较零散&#xff0c;没有独立的知识模块概念&#xff0c;而…

leetcode 375. 猜数字大小 II-【python3详细图解】递归+记忆化搜索与动态规划

题目 我们正在玩一个猜数游戏&#xff0c;游戏规则如下&#xff1a; 我从 1 到 n 之间选择一个数字。你来猜我选了哪个数字。如果你猜到正确的数字&#xff0c;就会 赢得游戏 。如果你猜错了&#xff0c;那么我会告诉你&#xff0c;我选的数字比你的 更大或者更小 &#xff0c…

二十、JavaScript——逻辑非

! 逻辑非- &#xff01;可以对一个值进行非运算 - 它可以对一个布尔值进行取反操作 true 变成 false false 变成 true - 如果对一个非布尔值进行取反&#xff0c;它会将其先转换为布尔值&#xff0c;再进行取反操作 可以利用这个特点将其他类型转换为布尔值 <script>/*! …

Hybrid模式下,如何实现热更新?

做过开发的小伙伴应该对“热更新”不陌生吧&#xff01;热更新就是指在游戏或软件更新的时候&#xff0c;不用再重新下载安装包进行安装&#xff0c;而是在启动应用程序的时候&#xff0c;在内部进行资源或代码的更新。那么如今&#xff0c;市场为什么越来越多地选择热更新技术…

数据结构——图最全总结(期末复习必备)

目录 图 定义&#xff1a; 基本术语&#xff1a; 图的存储结构 邻接矩阵 邻接表 十字链表 邻接多重表 图的遍历 深度优先搜索(Depth First Search,DFS) 广度优先搜索(Breadth First Search,BFS) 图的应用 最小生成树 普利姆算法 克鲁斯卡尔算法 最短路径 单源最短…

优蓝冲刺港股:上半年期内亏损过亿 主打蓝领人才服务

雷递网 雷建平 12月14日优蓝国际控股股份有限公司&#xff08;简称&#xff1a;“优蓝”&#xff09;日前递交招股书&#xff0c;准备在香港上市。上半年期内亏损1.18亿优蓝是一家蓝领终身服务平台&#xff0c;旨在成为蓝领人才的首选终身服务平台。截至最后实际可行日期&#…

[附源码]Nodejs计算机毕业设计基于博客系统的UI手机界面展示Express(程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程。欢迎交流 项目运行 环境配置&#xff1a; Node.js Vscode Mysql5.7 HBuilderXNavicat11VueExpress。 项目技术&#xff1a; Express框架 Node.js Vue 等等组成&#xff0c;B/S模式 Vscode管理前后端分…

vue3插槽(匿名插槽-具名插槽-插槽作用域-动态插槽)

文章目录容器布局匿名插槽具名插槽作用域插槽动态插槽容器布局 &#x1f468;&#x1f3fb; parent.vue <script setup lang"ts"> import { ref, useAttrs, defineProps } from "vue"; import children from ./children.vue</script><tem…

界面控件DevExpress WinForm——HTML-CSS感知控件介绍

DevExpress WinForm拥有180组件和UI库&#xff0c;能为Windows Forms平台创建具有影响力的业务解决方案。DevExpress WinForm能完美构建流畅、美观且易于使用的应用程序&#xff0c;无论是Office风格的界面&#xff0c;还是分析处理大批量的业务数据&#xff0c;它都能轻松胜任…

疫情防控|Springboot+小程序+校园疫情防控系统设计与实现

作者主页&#xff1a;编程指南针 作者简介&#xff1a;Java领域优质创作者、CSDN博客专家 、掘金特邀作者、多年架构师设计经验、腾讯课堂常驻讲师 主要内容&#xff1a;Java项目、毕业设计、简历模板、学习资料、面试题库、技术互助 收藏点赞不迷路 关注作者有好处 文末获取源…

【刷题笔记】之牛客面试必刷TOP101(二分查找-I + 二维数组中的查找 + 寻找峰值 + 数组中的逆序对 + 旋转数组的最小数字 + 比较版本号)

目录 1. 二分查找-I 2. 二维数组中的查找 3. 寻找峰值 4. 数组中的逆序对 5. 旋转数组的最小数字 6. 比较版本号 1. 二分查找-I 题目链接&#xff1a;二分查找-I_牛客题霸_牛客网 (nowcoder.com) 题目要求&#xff1a; 上代码 import java.util.*;public class Solut…

Spring MVC学习 | 视图RESTFul

文章目录一、视图1.1 视图对象View1.2 ThymeleafView1.3 转发视图1.4 重定向视图1.5 视图控制器二、RESTFul2.1 简介2.2 PUT和DELETE请求的实现2.2.1 HiddenHttpMethodFilter过滤器2.2.2 实现PUT请求2.2.3 实现DELETE请求学习视频&#x1f3a5;&#xff1a;https://www.bilibil…

Python 元组(Tuple)操作详解

Python的元组与列表类似&#xff0c;不同之处在于元组的元素不能修改,元组使用小括号,列表使用方括号,元组创建很简单,只需要在括号中添加元素,并使用逗号隔开即可 一、创建元组 代码如下: 1 2 3 tup1 (physics, chemistry, 1997, 2000); tup2 (1, 2, 3, 4, 5 ); tup3 &qu…

Redis实现朋友圈,微博等Feed流功能,实现Feed流微服务(业务场景、实现思路和环境搭建)

文章目录业务场景Feed流相关概念Feed流特征Feed流分类实现思路环境搭建数据库表结构新建Feeds功能微服务ms-feeds配置类 RedisTemplateConfigurationREST配置类 RestTemplateConfigurationFeeds 实体类FeedsVO 响应类业务场景 在互联网领域&#xff0c;尤其现在的移动互联网时…

Linux环境下MySQL的安装与使用

目录 一&#xff1a;安装MYSQL说明 1.1 查看是否安装过MySQL 1.2 MYSQL的卸载 二&#xff1a;MySQL在Linux下的安装 三&#xff1a;MYSQL登录 3.1 首次登录 3.2 修改密码 3.3 设置远程登录 一&#xff1a;安装MYSQL说明 1.1 查看是否安装过MySQL 检查rpm安装包 rpm -…

JAVA毕业设计——基于ssm高校共享单车管理系统 (源代码+数据库)604

代码地址 https://github.com/ynwynw/webike-public 毕业设计所有选题地址 https://github.com/ynwynw/allProject 基于ssm高校共享单车管理系统 (源代码数据库)604 一、系统介绍 用户管理&#xff0c;服务点管理&#xff0c;单车管理&#xff0c;分类管理&#xff0c;学生管…

基于java+springboot+mybatis+vue+mysql的大学生体质测试管理系统

项目介绍 随着我国大学生数量的不断增加&#xff0c;个个高校对大学生的体质也开始高度的进行重视&#xff0c;只有拥有了高强健康体质的大学生才能够全身心的投入到学习和工作中&#xff0c;为了能够更好的对大学生的体质进行检测我们通过java编程语言&#xff0c;后端采用sp…