Ribbon过滤器整体看是一个矩阵构建与矩阵乘法,RocksDB中对它的实现是进行了合理的空间、时间上的优化的。

符号

整个过滤器都和矩阵计算CS=R相关,C是\(n*n\)矩阵,S是\(n*m\)矩阵,R是\(n*m\)矩阵。

这里为了方便讨论定义:三个哈希函数s(x),c(x),r(x),s是start函数,c是coeff函数,r是result函数。其中c的返回值是一个定长二进制整数,比如c(x)返回0010,那么c(x)返回的整数是长度为w=4的二进制整数。

此外为了方便讨论,我们假定n=8, m=3,h(x)为s(x)个0拼接c(x)拼接n-w-s(x)个0。比如:

key s(x) c(x) r(x) h(x)
u 0 1000 000 1000 0000
v 0 1100 111 1100 0000
w 0 1010 100 1010 0000
x 3 1000 101 000 1000 0
y 0 1001 111 1001 0000

构建过程

构建过程本质上是解一个CS=R的矩阵乘法。C和R是由插入的键构建的,S是C和R解出来的。这里定义的矩阵乘法是:

\[c_{ij} = \bigoplus_{k=1}^{n} (a_{ik} \land b_{kj})
\]

与原本的矩阵乘法非常相似,就是乘法变成逻辑与,加法编程逻辑xor罢了。

\[c_{ij} = \sum_{k=1}^{n} a_{ik} b_{kj}
\]

初始化

初始化,默认所有值为0

插入过程

    def leading_zeros_count(self, vec: np.matrix):
for i in range(vec.shape[1]):
if vec[0, i] != 0:
return i
return vec.shape[1] def banding_add(self, h, resultrow):
print(h, resultrow)
while True:
start = self.leading_zeros_count(h)
if np.all(h[0] == 0):
if np.all(resultrow[0] == 0):
break
else:
raise ValueError("cannot insert this row")
elif np.all(self.coeff[start] == 0): # 如果start这一行全是0,就把这一行赋值为coeffrow
self.coeff[start] = h
self.result[start] = resultrow
break
else:
h = self.row_xor(h, self.coeff[start])
resultrow = self.row_xor(resultrow, self.result[start])
print(self.coeff)
print(self.result)

插入u

插入u的时候,此时它开头的0的数量是0,需要插入到第0行。第0行都是0,直接插入即可。

插入v

插入v的时候,此时它开头的0的数量是1,需要插入到第0行。第0行已经有值了,h(v)=11000000与r(v)=111分别与第0行异或,得到01000000与111。此时它开头的0的数量是1,需要插入到第1行,可以直接插入。

插入w

插入w的时候,h(w)=10100000,r(w)=100,此时它开头的0的数量是0,需要插入到第0行。发现第0行有值了,分别与第0行异或,得到00100000与100,此时它开头的0的数量是2,此时需要插入到第2行。

插入x

插入x的时候,h(x)=00011000,r(x)=100,此时它开头的0的数量是3,需要插入到第3行,插入到第3行。

插入y

插入y的时候,h(w)=10010000,r(w)=111,此时它开头的0的数量是0,需要插入到第0行。

发现第0行有值了,分别与第0行异或,得到00010000与111,此时它开头的0的数量是3,此时需要插入到第3行。

发现第3行有值了,分别与第3行异或,得到00001000与010,此时它开头的0的数量是4,此时需要插入到第4行。

解矩阵S

现在有C和R了,可以用线性代数的方法解S的值。但是由于C是上三角矩阵,且中间长度w=4的01串以外的地方都是0,所以我们可以从下往上计算,且只需要看其中4个值。

解S的第4行

解这一行本质上是计算\(00001000*S=010\),即:

\[0 \and S_{0,0} \oplus 0 \and S_{1,0} \oplus 0 \and S_{2,0} \oplus 0 \and S_{3,0} \oplus 1 \and S_{4,0} \oplus 0 \and S_{5,0} \oplus 0 \and S_{6,0} \oplus 0 \and S_{7,0} = 0
\]
\[0 \and S_{0,1} \oplus 0 \and S_{1,1} \oplus 0 \and S_{2,1} \oplus 0 \and S_{3,1} \oplus 1 \and S_{4,1} \oplus 0 \and S_{5,1} \oplus 0 \and S_{6,1} \oplus 0 \and S_{7,1} = 1
\]
\[0 \and S_{0,2} \oplus 0 \and S_{1,2} \oplus 0 \and S_{2,2} \oplus 0 \and S_{3,2} \oplus 1 \and S_{4,2} \oplus 0 \and S_{5,2} \oplus 0 \and S_{6,2} \oplus 0 \and S_{7,2} = 0
\]

由于C是上三角矩阵,且其中只有连续的4个wit可能为1,其他都为0,所以上面的计算可以化简为:

\[1 \and S_{4,0} \oplus 0 \and S_{5,0} \oplus 0 \and S_{6,0} \oplus 0 \and S_{7,0} = 0
\]
\[1 \and S_{4,1} \oplus 0 \and S_{5,1} \oplus 0 \and S_{6,1} \oplus 0 \and S_{7,1} = 1
\]
\[1 \and S_{4,2} \oplus 0 \and S_{5,2} \oplus 0 \and S_{6,2} \oplus 0 \and S_{7,2} = 0
\]

由于C的最后三行是0,所以进一步化简为:

\[1 \and S_{4,0} = 0
\]
\[1 \and S_{4,1} = 1
\]
\[1 \and S_{4,2} = 0
\]

很容易解出来S的第四行

解S的第3行

计算

\[0 \and S_{0,0} \oplus 0 \and S_{1,0} \oplus 0 \and S_{2,0} \oplus 1 \and S_{3,0} \oplus 1 \and S_{4,0} \oplus 0 \and S_{5,0} \oplus 0 \and S_{6,0} \oplus 0 \and S_{7,0} = 1
\]
\[0 \and S_{0,1} \oplus 0 \and S_{1,1} \oplus 0 \and S_{2,1} \oplus 1 \and S_{3,1} \oplus 1 \and S_{4,1} \oplus 0 \and S_{5,1} \oplus 0 \and S_{6,1} \oplus 0 \and S_{7,1} = 0
\]
\[0 \and S_{0,2} \oplus 0 \and S_{1,2} \oplus 0 \and S_{2,2} \oplus 1 \and S_{3,2} \oplus 1 \and S_{4,2} \oplus 0 \and S_{5,2} \oplus 0 \and S_{6,2} \oplus 0 \and S_{7,2} = 1
\]

由于C是上三角矩阵,且其中只有连续的4个bit可能为1,其他都为0,所以上面的计算可以化简为:

\[1 \and S_{3,0} \oplus 1 \and S_{4,0} \oplus 0 \and S_{5,0} \oplus 0 \and S_{6,0} = 1
\]
\[1 \and S_{3,1} \oplus 1 \and S_{4,1} \oplus 0 \and S_{5,1} \oplus 0 \and S_{6,1} = 0
\]
\[1 \and S_{3,2} \oplus 1 \and S_{4,2} \oplus 0 \and S_{5,2} \oplus 0 \and S_{6,2} = 1
\]

容易算出S的第3行是101

以此类推,计算完S

查询过程

查询过程即判断表达式\(h(x)S=r(x)\)。

查询\(h(a)=11000000\)与\(r(a)=110\),\(h(a)S=111\),与r(a)不同,判定为不存在,查询结果正确

查询\(h(y)=10010000\)与\(r(y)=111\),\(h(y)S=101\),与r(y)不同,出现假阴性

查询\(h(b)=01000000\)与\(r(b)=111\),得到101,与r(b)不同,出现假阳性

查询\(h(u)=11000000\)与\(r(u)=111\),得到111,与r(u)相同,判定为存在,结果正确

优化

矩阵存储优化

可以发现,ribbon过滤器完全可以用一个\(w*n\)的矩阵表示整个C,并且构建完S后完全可以丢弃C和R,所以只会在构建过中存在临时的空间消耗。

构建、查询耗时优化

可以发现h(x)只有中间连续w个bit是有用的,所以查询时可以遍历S中的w行即可。

假阳性率定制

假阳性率是\(2^{-w}\),至于如何定制,这是论文中的内容了,如图,我们将w个 bit 连续存放,每m个w bit 为一组。所以整体看是 column-major 的,而每一组实际上是 row-major 的。这种混合方式称为 ICML(Interleaved Column-MajorLayout)

代码中,我们还需要特殊处理跨组(图中红点部分)的问题。对于 ICML 开始一段不足m列的特殊情况(图中蓝色斜线部分),我们可以忽略它。这样我们就可以存储 7、9、10bit 一行的矩阵。平均 3.4bit 一行的矩阵也是可行的,图中m = 4,但是前面三段都是使用m = 3,这样平均就是 3.4bit 一行了。

提防假阴性

从前面查询的例子我们可以发现,过滤器存在假阴性,即实际插入位置不在$$[s(x),s(x)+w-1]$$范围内,这是要避免的。为了确保假阴性率极低,需要多分配一些内存。比如插入t个key,那么实际要分配的行数是需要多于t的。具体的假阴性率真不知道怎么算,在代码中是设置为一个值来规避的。这个值是

\[ 1-0.0251 + ln(t) * 1.4427 * 0.0083或1-0.0176 + ln(t) * 1.4427 * 0.0038;
\]

算法导论有关于线性探测哈希表的讨论。如果w=64,那么期望探测到64次,load-factor需要是\(64=1/(1-a)\),解出来loadfactor为63/64,即0.98,而这个公式在\(t=30,000,000\)时算出来是0.83左右

Ribbon过滤器原理解析的更多相关文章

  1. Web APi之过滤器执行过程原理解析【二】(十一)

    前言 上一节我们详细讲解了过滤器的创建过程以及粗略的介绍了五种过滤器,用此五种过滤器对实现对执行Action方法各个时期的拦截非常重要.这一节我们简单将讲述在Action方法上.控制器上.全局上以及授 ...

  2. Web APi之过滤器创建过程原理解析【一】(十)

    前言 Web API的简单流程就是从请求到执行到Action并最终作出响应,但是在这个过程有一把[筛子],那就是过滤器Filter,在从请求到Action这整个流程中使用Filter来进行相应的处理从 ...

  3. python实现布隆过滤器及原理解析

    python实现布隆过滤器及原理解析     布隆过滤器( BloomFilter )是一种数据结构,比较巧妙的概率型数据结构(probabilistic data structure),特点是高效地 ...

  4. Volley 实现原理解析(转)

    Volley 实现原理解析 转自:http://blog.csdn.net/fengqiaoyebo2008/article/details/42963915 1. 功能介绍 1.1. Volley ...

  5. 撸一撸Spring Cloud Ribbon的原理-负载均衡器

    在上一篇<撸一撸Spring Cloud Ribbon的原理>中整理发现,RestTemplate内部调用负载均衡拦截器,拦截器内最终是调用了负载均衡器来选择服务实例. 接下来撸一撸负载均 ...

  6. Nginx 原理解析和配置摘要

    前言 Nginx 作为高性能的 http 服务器,知名度不必多言,相似产品中无出其右.本篇随笔记录我认为较为重要的原理和配置. 1. 原理解析 1.1 结构 以上是 Nginx 的结构图,其包含一个 ...

  7. (转)Apache和Nginx运行原理解析

    Apache和Nginx运行原理解析 原文:https://www.server110.com/nginx/201402/6543.html Web服务器 Web服务器也称为WWW(WORLD WID ...

  8. Spring Security 解析(六) —— 基于JWT的单点登陆(SSO)开发及原理解析

    Spring Security 解析(六) -- 基于JWT的单点登陆(SSO)开发及原理解析   在学习Spring Cloud 时,遇到了授权服务oauth 相关内容时,总是一知半解,因此决定先把 ...

  9. 三、Nginx原理解析

    Nginx原理解析 一.反向代理 工作流程 用户通过域名发出访问Web服务器的请求,该域名被DNS服务器解析为反向代理服务器的IP地址: 反向代理服务器接受用户的请求: 反向代理服务器在本地缓存中查找 ...

  10. [原][Docker]特性与原理解析

    Docker特性与原理解析 文章假设你已经熟悉了Docker的基本命令和基本知识 首先看看Docker提供了哪些特性: 交互式Shell:Docker可以分配一个虚拟终端并关联到任何容器的标准输入上, ...

随机推荐

  1. C++中线程同步与互斥的四种方式介绍及对比详解

    引言 在C++中,当两个或更多的线程需要访问共享数据时,就会出现线程安全问题.这是因为,如果没有适当的同步机制,一个线程可能在另一个线程还没有完成对数据的修改就开始访问数据,这将导致数据的不一致性和程 ...

  2. 【数值计算方法】蒙特卡洛方法积分的Python实现

    原理不做赘述,参见[数值计算方法]数值积分&微分-python实现 - FE-有限元鹰 - 博客园,直接上代码,只实现1d,2d积分,N维积分的蒙特卡洛方法也类似. 代码 from typin ...

  3. 大模型基础补全计划(二)---词嵌入(word embedding)

    PS:要转载请注明出处,本人版权所有. PS: 这个只是基于<我自己>的理解, 如果和你的原则及想法相冲突,请谅解,勿喷. 环境说明   无 前言   本文是这个系列第二篇,它们是: &l ...

  4. JavaScript 获取鼠标点击位置坐标(转载自https://www.cnblogs.com/dolphinX/archive/2012/10/09/2717119.html )

    在一些DOM操作中我们经常会跟元素的位置打交道,鼠标交互式一个经常用到的方面,令人失望的是不同的浏览器下会有不同的结果甚至是有的浏览器下没结果,这篇文章就上鼠标点击位置坐标获取做一些简单的总结,没特殊 ...

  5. nrm

    nrm npm install -g nrm nrm ls nrm use taobao Tips:不要使用cnpm,会有些奇怪的问题,导致npm install失败. 参考

  6. 使用benchmarksql测试数据库处理能力

    我们所处行业的核心应用业务,当前还是传统的OLTP业务,应用系统使用 java 开发,并且不建议使用存储过程,使用 benchmarksql 压测数据库最公平,既可以测试数据库性能,也可以测试JDBC ...

  7. Python科学计算系列1—方程和方程组

    1.一元方程求解 例1:求下列一元二次方程的解 代码如下: # 定义数学符号 from sympy import symbols, solve x = symbols('x') f = x ** 2 ...

  8. 【自用】restful api 常用状态码

    GET(SELECT):从服务器取出资源(一项或多项). POST(CREATE):在服务器新建一个资源. PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源). PATCH(UPD ...

  9. office for mac 16.79 破解版安装教程

    教程声明 本人电脑系统:macOS Sonoma,安装版本为office for mac 16.79.本教程旨在学习分享.资源均为从网络处下载,安装破解版有风险,请自己权衡.不会安装的朋友可评论区探讨 ...

  10. mysql免密登录

    开启mysql免密登录, vi /etc/my.cnf [mysqld]下添加 skip-grant-tables , 保存后重启mysql服务:service mysqld restart