[译]我们是如何设计存储4亿个电话号码的

原文:How we store 400M phone number data with fast lookups
译者:杰微刊-张帆

如果你居住在印度,当不希望接受任何电话推销员的骚扰时,你可以在全国客户偏好登记册(National Customer Preference Register,NCPR) 【1】中进行注册。政府维护了这个由用户注册的电话号码组成的数据库。现在,差不多有4亿个注册号码。所有注册的电话推销员必须及时更新数据,以保证他们在进行推销时会参考这个偏好设置进行工作。

这些数据由一捆ZIP文件(当下是40个)提供,每个ZIP文件包含一个10M的CSV文件。这篇文章将会讲述这2.4GB压缩后的数据如何基于一些简单的方式以一种可搜索的格式适配2GB的内存。

数据

下面是CSV文件一瞥(出于隐私原因,有些数据进行了混淆)

关于存储在SQL引擎中的一些说明

在内存为4GB的Linode机房的机器上, PostgreSQL数据表(使用COPY)加载数据约需要10分钟:

real    10m0.159s
user    2m42.243s
sys     0m26.363s

添加一个主键大约耗时1.5到2个小时:

real    118m21.637s
user    0m0.043s
sys     0m0.020s

并使用32GB的硬盘空间:

观察CSV数据

分析了数据之后,我们可以看到:

* 将近400M行数据

* 电话号码全部(phone numbers)是10位

* 服务区域码(service area code)是1-23之间的自然数

* 偏好(preference)依靠`#`来界定,可能是`0`或者是{1,2,3,4,5,7}的组合

* Ops类型(Opstype)用A表示启用,用D表示未启用

* 电话号码类型(Phone Type)是{1,2,3}中的一个

这意味着一行数据可以用2个字节表示:

第一个字节:1位存在标志位(existence flag),5位服务区域码,2位电话号码类型。

第二个字节:7位偏好,1位Ops类型。

数据可以通过2*400MB来表示。存在标志位将会在下面的部分讨论。

使之可搜索

每个条目都会按照电话号码进行频繁的搜索,而目前我们并没有将数据与电话号码进行匹配。我们需要添加字节来存储电话号码。不幸的是,10个数字并不能放入32位中(10 digits won‘t fit in 32 bits),使用5*400MB来存储数字并不是一个乐观的情况,而且根本没办法进行搜索。如果数据按顺序排列(arranged in a sequence),那么索引为 (2*number) 和 (2*number+1)的内存位置便能给出所需的两个字节。空行可以用第一个字节中的存在标志表示。这意味着我们需要20GB的内存(2字节*10B的数字)。我们能进一步压缩吗?该数组看起来很稀疏(只有40%被占用)。

我们的解决方案是:使用两种格式类型。

更进一步

我们还发现对于大多数移动手机号码的数组是密集的【2】 。所以,如果10个数字分成两部分——4位的前缀(我们可以称之为头部)和6位的数字偏移量(尾部)——这样一来,固定的4位前缀的所有可能值按顺序排列时,它们都可以被放入2MB的空间里了。(每个尾部2字节)。现在,搜索变得简单了,因为我们按照尾部进行偏移量计算,直接访问数组即可。

这个稀疏的数列存储在5字节的序列中,3个字节表示尾部,2个字节表示数据。尾部按照升序排列,所以搜索变的简单了(二分搜索)。

对于持久化存储,具有相同前缀的数字存储在一个文件中,该文件的第一个字节是类型的指示框。这些共需1.8GB的空间,这些数据可以存储在内存中,通过webserver进行发布。

加工处理

使用快速Python脚本来转换CSV数据为我们需要的格式是十分耗时的。分析表明,大部分时间花费在迭代处理2M固定头部数据时。我们尝试使用xrange进行优化,但是5小时对于处理整个数据,尤其是PostgreSQL处理仅需要2小时,实在太多了。我们希望能有些快速响应,更符合心理预期。相同的程序选择Rust来实现,处理整个数据仅用20-30分钟。

real    21m4.284s
user    20m58.427s
sys     1m37.607s

查找计时

为了测量该解决方案的速度,我们随机生成了相同序列(固定的头部)的电话号码。结果如下图所示。我们选取“9818”和“9000”开头的号码去分别计算查找密集框(我们称之为类型0)和稀疏框(类型1)的时间。对于SQL解决方案,头部的密集程度并不影响。注意,在本次测量中,尽管我们为了公平起见,计时时包含了磁盘的读写,但是在我们的解决方案中,数据一旦被加载或放入内存中,不再需要磁盘访问,之后由于数据存储格式的优点,这个进程被加快。

所有的测试都是在4GB的Linode机房机器上跑的,机器配置如下:

SSD, 4GB RAM, 4 virtual CPU cores, CPU Model: Intel(R) Xeon(R) CPU E5-2680 v2 @ 2.80GHz

API和开源

在SparkTG,我们尊重客户的偏好设置。尽管我们的客户大部分都是与注册的客户交流,但我们还是保证他们最终不会拨出一个无关的电话。我们已经将该项目【3】开源,并且提供API【4】来查找号码NCPR状态,使得电话推销找不到方式拨打注册用户的电话。

参考

1. https://en.wikipedia.org/wiki/Do_Not_Disturb_Registry

2. https://en.wikipedia.org/wiki/Mobile_telephone_numbering_in_India

3. Github repository

4. NCPR status lookup

原文地址:http://www.jointforce.com/jfperiodical/article/925?f=jf_tg_bky

时间: 2024-08-29 12:42:01

[译]我们是如何设计存储4亿个电话号码的的相关文章

用来解析,格式化,存储和验证国际电话号码:libphonenumber

用来解析,格式化,存储和验证国际电话号码:libphonenumber libphonenumber是Google的公共Java.C++和Javascript库用来解析,格式化,存储和验证国际电话号码. 其中Java版本优化用于运行在智能手机上,并且用在了 Android framework 4.0 (Ice Cream Sandwich)以上的版本中. 在android系统的源码中,可以找到这个项目包的. work_space/external/libphonenumber/ 该项目在gith

设计一个一百亿的计算器

.补码(负数在计算机中的存储) .百亿计算器 负数在计算机中以补码的形式存储.负数的补码表示方法是:将负数表示成二进制原码(负数最高位是1,正数最高位是0)然后将原码取反(1变0,0变1),即反码,将反码加1(最后一位上加1),即转化为补码.如用八位二进制表示-5,第一步,原码10000101,反码01111010,加1变为补码:01111011 首先要明白这道题目的考查点是什么,一是大家首先要对计算机原理的底层细节要清楚.要知道加减法的位运算原理和知道计算机中的算术运算会发生越界的情况,二是要

Redis百亿级Key存储方案

1 需求背景 该应用场景为AdMaster DMP缓存存储需求,DMP需要管理非常多的第三方id数据,其中包括各媒体cookie与自身cookie(以下统称admckid)的mapping关系,还包括了admckid的人口标签.移动端id(主要是idfa和imei)的人口标签,以及一些黑名单id.ip等数据. 在hdfs的帮助下离线存储千亿记录并不困难,然而DMP还需要提供毫秒级的实时查询.由于cookie这种id本身具有不稳定性,所以很多的真实用户的浏览行为会导致大量的新cookie生成,只有

关于大型站点技术演进的思考(四)--存储的瓶颈(4)

假设数据库须要进行水平拆分,这事实上是一件非常开心的事情,由于它代表公司的业务正在迅猛的增长,对于开发者而言那就是有不尽的项目能够做,尽管会感觉非常忙.可是人过的充实,心里也踏实. 数据库水平拆分简单说来就是先将原数据库里的一张表在做垂直拆分出来放置在单独的数据库和单独的表里后更进一步的把本来是一个总体的表进一步拆分成多张表,每一张表都用独立的数据库进行存储.当表被水平拆分后,原数据表成为了一个逻辑的概念,而这个逻辑表的业务含义须要多张物理表协同完毕.因此数据库的表被水平拆分后.那么我们对这张表

航天七三一医院护理电子病历的设计与实施

中图分类号:TP3                                                         论文编号: 专业硕士学位论文   航天七三一医院护理电 子病历的设计与实施 作者姓名 学科专业  软件工程 指导教师 培养院系  软件学院 The Design and Implementation of Aerospace 731 hospital electronic nursing record system   A Dissertation Submitte

MongoDB 进阶模式设计

转载: http://www.mongoing.com/mongodb-advanced-pattern-design 12月12日上午,TJ在开源中国的年终盛典会上分享了文档模型设计的进阶技巧,就让我们来回顾一下吧: —————————————————————————————————————————————————————————- 从很久以前,我就开始接触开源产品:从最开始的使用.受益者到后来的贡献者,到现在的热情推广者.现在,我是MongoDB的技术顾问.我的职责是为MongoDB的客户和

关于大型网站技术演进的思考--存储的瓶颈(转)

(一)第一部分 前不久公司请来了位互联网界的技术大牛跟我们做了一次大型网站架构的培训,两天12个小时信息量非常大,知识的广度和难度也非常大,培训完后我很难完整理出全部听到的知识,今天我换了个思路是回味这次培训,这个思路就是通过本人目前的经验和技术水平来思考下大型网站技术演进的过程. 首先我们要思考一个问题,什么样的网站才是大型网站,从网站的技术指标角度考虑这个问题人们很容易犯一个毛病就是认为网站的访问量是衡量的指标,懂点行的人也许会认为是网站在单位时间里的并发量的大小来作为指标,如果按这些标准那

关于大型网站技术演进的思考(四)--存储的瓶颈(4)

如果数据库需要进行水平拆分,这其实是一件很开心的事情,因为它代表公司的业务正在迅猛的增长,对于开发人员而言那就是有不尽的项目可以做,虽然会感觉很忙,但是人过的充实,心里也踏实. 数据库水平拆分简单说来就是先将原数据库里的一张表在做垂直拆分出来放置在单独的数据库和单独的表里后更进一步的把本来是一个整体的表进一步拆分成多张表,每一张表都用独立的数据库进行存储.当表被水平拆分后,原数据表成为了一个逻辑的概念,而这个逻辑表的业务含义需要多张物理表协同完成,因此数据库的表被水平拆分后,那么我们对这张表的操

关于大型网站技术演进的思考(四)-存储的瓶颈4

如果数据库需要进行水平拆分,这其实是一件很开心的事情,因为它代表公司的业务正在迅猛的增长,对于开发人员而言那就是有不尽的项目可以做,虽然会感觉很忙,但是人过的充实,心里也踏实. 数据库水平拆分简单说来就是先将原数据库里的一张表在做垂直拆分出来放置在单独的数据库和单独的表里后更进一步的把本来是一个整体的表进一步拆分成多张表,每一张表都用独立的数据库进行存储.当表被水平拆分后,原数据表成为了一个逻辑的概念,而这个逻辑表的业务含义需要多张物理表协同完成,因此数据库的表被水平拆分后,那么我们对这张表的操