【大数据管理】数据组织与存储(二)

B 树和 B+ 树索引能够很好支持点查询和范围查询,因此是传统键值系统常用的索引结构。
 
 
但对于数据主要存储于外存的键值存储系统, B/B+ 树在更新单个键值对时,很可能需要更新外存中的整个数据块,产生比较严重的写放大现象,因此写操作性能较差。
 
 
NVM 设备 随机访问性 能相对于 连续访问 性能 差距不大。
 
 
B/B+ 树索引在 NVM 设备上能够避免其写放大的劣势,同时发挥其读性能的优势,其主要思想尽量减少慢速的 NVM 写操作,允许适度增加读操作。
 
 
典型的研究工作如下:
 
Bw -tree : 实现一个高性能的 ARSs 系统,所谓的 ARSs Atomic Record Stores ,提供简单的 key-value 的原子访问接口, key-value 通常提供 put get scan 的访问模型。
 
基于 ARSs 可以很容易实现一个 nosql 系统,也可以基于它加上 事务机制 实现传统数据库。
 

Bw-Tree通过无锁的方式(具体的操作细化成好几个原子操作)来操作B+树,提升随机读和范围读的性能,同时数据以追加的方式写入,提高写的性能。

Bw-tree结构如下:

0a44ea17437d4ac6b23a3847259f0823.png

 

 

NV-Tree 是一个保持一致性、缓存优化的 B+-tree
 
NV-Tree 的结构如下图,把 B+tree 的内部节点放在 DRAM 内存中, 不保障持久化,叶子节点放在 NVM 中进行持久化。
 
如果 系统崩溃 或者重启后,可以通过扫描一遍 NVM 中的叶子结点来重构内部节点。
a6ae2e31a5fa43879918fd508ab0330f.png
 
 
 
 
FPTree 也是一种混合的 NVM-DRAM B+ 树。
 

结构如下图:也是叶子节点存储在NVM中,而内部节点放置在DRAM中。

a18ccdb7a85d4ca58b414798e8faf9a2.png

FPTree 使用 Fingerprint 提高索引性能(如下图), Fingerprint 叶子节点中 单字节哈希值 ,存储在叶子节点开始部分。

 

检索时 首先扫描 它,以避免探测到具有和搜索键不匹配指纹的
3817c44521ce4f0d95f71674c2fa39e4.png
 
 

 

 

 

在大数据管理系统从所支持的应用类型上,可以分为 分析型 事务型 两类。
 
在存储方面, 事务型 系统需要支持 均衡的更新 写入和读取
 
读取 操作以带有精确查询条件的 点查询 为主。
 
存储性能的主要关注点在于每秒钟执行的操作数。
 
而在 分析型 系统中, 数据更新较少 ,数据通常 批量追加式 写入存储系统。
 
查询既有带有精确条件的点查询, 也有 大量的 扫描式查询
 
存储性能的关注点主要在于每秒写入和读取的 记录数 或字节数。
 
随着大数据分析在各行各业的应用越来越广泛, 分析型 的系统在大数据管理系统中占到了很大的比重。
 
在分析型的大数据管理系统中,列存储是非常重要的数据布局。
 

如下图所示,一张关系表中的数据以行为单位连续存储在磁盘上,而是将每个列上的数据分别连续存储

ba0c7a6889e24ea5affa9d0cca2a4090.png

列式的存储布局 避免了 批量数据扫描时的 访问量放大
 
在大数据分析中, 一个关系表 可能由几十、几百,甚至 上千各属性 ),而 每个查询 仅访问其中 很少 的一部分 属性
 
采用 列存储 之后,查询 只需 要从磁盘中 读取需要的列 即可。
 
而如果采用 行存储 ,在每个文件系统的页面 ( 大小通常为 4KB 的整数倍 ) 连续存储每行 的数据。
 

由于页面是I/O的基本单元,为读取每行中的少数属性上的值,不得不整个页面,造成严重读放大问题。

此外,列存储在 数据压缩 方面也有很大的优势。
 
每列上 的数据具有 相同 数据类型 ,并且在数值的分布上通常有一定的规律。
 
例如,在一张商品销售记录表中,订单完成时间这一列上的数据不仅都是 datatime 类型,且在数值上具有较好的连续性。
 
可以利用 Run-Length Encoding Dictionary Encoding Delta Encoding 等轻量级压缩算法进行列压缩。
 
 
 
列存储在分析型的数据库系统中应用广泛,应用列存储的数据库系统称为列存储数据库。
 
早期的列存储数据库 MonetDB / X100 中将数据表中的每个列单独存储为一个表文件。
 
每个表文件中除了存储对应列上的数据外,还存储着每一条数据对应的 OID
 
当查询 个数据 列时 ,需要根据每个列的表文件中的 OID 来进行 连接 ,将 各列 上的 数据 恢复为 元组

 

由于 每列存储 为一个 单独 表文件 ,当数据表中的数据量非常大时,每个表文件也会很大,故 无法 将一个表文件 一次性 全部 读入内存
 
MonetDB 采用了 内存映射文件技术 ,将文件中的 一段 内容 映射 到进程的 内存 空间中,进程可以像访问内存一样访问文件的内容。
 
另外, MonetDB 还采用向量化执行技术。
 
在处理一个数据序列时, 传统的方法 是采用循环遍历的方式,每次从数据序列的 迭代器中 取出 一条数据 进行 处理

 

迭代方法比较方便和直观,但是在 处理大批量数据 时,反复的迭代操作和数据处理函数的调用会导致反复的 CPU 上下文切换。并导致缓存的命中率较低。
 
向量化 执行 一次性 从数据序列中 一小批数据 ,这一小批数据可以被 CPU 放入高速缓存中,通过一次函数调用完成一批数据的处理,大大提高了内存中数据的处理速度。
 
 
 
 
 
 
另一个比较有名的列存储数据库是 C-Store
 
C-Store 中的列存储实现与 MonetDB 不同。首先,为了 快速 进行 元组恢复 (将多列上的数据连接起来,恢复成元组), C-Store 支持将 经常 被一起 访问 列组成一个 projection
 
一个 projection 中包含 一组列 。同一个 projection 各个列中 的数据 按照相同的 key 排序 ,这实际上是在 projection 上建立了聚簇索引,可以有效提高数据范围查询的性能,也可以一定程度上提高数据压缩的效果。
 
此外,在 C-Store 中,每个 projection 水平划分 存储为 一系列的 segment ,而不是一个完整的表文件。
 
这样在读数据时,可以将一个 segment 一次性读入内存,避免恢复元组时在多个列之间来回跳读。
 
当查询读取多个 projection 中的列时,需要通过连接索引 (join index) 对多个 projection 中的列进行连接。这种连接操作代价很高。
 
故,在 C-Store 之后的 Vertica (C-Store 的一个商业化版本 ) 中,放弃了连接索引,采用一个包含所有列的 super projection
 
当查询在其他任何一个 projection 中无法获得所需列时,则可以访问 super projection 来读取需要的列。
 
 
 
 
 
InfoBright 是基于 MySQL 的列存储数据库,是以 MySQL 的第三方存储引擎的方式工作。
 
InfoBright 中,数据首先被 水平划分为行组 (row group) ,默认每个 行组 中包含 65536 行。
 
行组内 的数据采用 列存储 ,行组中每列上的数据被存储为一个 data pack
 
由于数据是 按行对齐 的,所以查询同时读取 多列 无需进行连接
 
相对于 C-Store, 这样的设计在一些特定的查询上的性能可能差一些( 因为数据没有排序 ), 可以 更好 支持 Ad-hoc 查询 ,具有很好的适应性。
 
 
 
 
 
 
HDFS 是一个分布式文件系统。在 HDFS 中, 一个数据文件被划分为若干个数据块,分布式存储在集群中的每个节点上。
 
很多大数据计算引擎是基于 HDFS ,如 MapReduce Spark 等。
 
为了在 HDFS 上支持 列存储 ,出现了很多流行的列存储文件格式,如:

            -- RCFile

            -- ORC

            -- Parquet

            -- CarbonData

HDFS 列存储如下图。数据文件首先被水平划分为行组 (row group), 行组内的数据按列存储。
 
RCFile 是较早的 HDFS 列存储格式。 RCFile 中默认的行组大小只有 4MB ,因此一个数据块中有多个行组。
 

每个行组中有一个元数据区域,存储行组中各个列位置偏移。

8085225bde8b4e8db58eea853301b836.png

ORC RCFile 进行了 优化 ,将 默认行组 ( ORC 中称为 Stripe) 的大小 提高 256MB (Hive 中的 ORC 默认 64MB 大小 )
 
提高行组大小 增加 了行组中 大小 ,使得查询可以连续读取一个较大的列,以提高 I/O 的连续性。
 
ORC 支持 嵌套 数据类型 ( Json Map ) 。嵌套的数据类型被分解为二维的数据结构,在二维的数据表进行存储。
 
Parquet 采用与 ORC 类似的技术,用 128MB 的默认行组大小,同样支持列的数据类型和统计信息选择数据的编码压缩算法。
 
CarbonData 是最近出现的 HDFS 列存储格式,由华为主导的一个 Apache 开源项目。
 
CarbonData 支持自适应的数据编码压缩和嵌套数据类型。
 
在数据的存储布局方面进行了更为精细的设计, 更好 支持数据压缩和索引
 
CarbonData 每列上 的每 32,000 个数据项构成一个页面 (page) 页面内 数据 可以单独 排序 ,排序后的数据压缩效果远好于未排序的数据。
 

这样的存储组织方式保证了在页面的粒度上数据是按行对齐的。

 

执行查询时,由于页面的粒度较小, 各个列 上相对应的 一组页面 在查询执行时可以 快速在内存中 进行连接、重组为元组,不会造成很高的元组恢复代价。
 
CarbonData 还支持将 数据表水平 划分为 segment , 每个 segment 中的数据可以按照主键进行全局排序并建立索引,以支持在主键上的高性能数据查询。
 
在编码压缩方面, 支持全局字典,即同一数据表的同一个列上使用同一个字典对数据进行字典编码。
 
在查询执行时,如果需要执行条件过滤或者 Group By 等操作,可以直接在编码后的数据上进行,无需进行解码。
 
有当返回最终结局时,才对数据进行解码。

 

 

 

 

文档存储分两类:

    1) 无结构文档存储,例如,文本、HTML等;

   (2)半结构化文档,比如XMLJSON等。

无结构文档存储。文档具有非结构化的特点。文档存储通常需要高效实现几个基本操作:读取、写入、删除、更新。

其中, 读取操作 通常需要实现对指定文件、指定位置、指定长度的字符串进行快速读取。

 

写入:在指定的文件位置写入相关内容,操作相对复杂。
 
如果 写入 位置 在文档 中间 ,需要将插入位置之后的字符串全部后移;
 
如果 写入 位置 在文档 末尾 ,则只需 插入 需要写的内容。
 
删除也会遇到同样的问题,如果只删除文档中间的一段字符串,需要移动删除位置之后的字符串。
 
对于 更新 ,可以通过 删除 写入操作混合 完成。
 
文档存储在实现文本的基本操作之后,需要进一步建立文档索引。高效的索引结构有助于快速的信息检索。
 
文档中的特定关键词可以通过建立 倒排索引 (inverted index) 将单词映射到一个单词出现过的文件列表,如下图所示:
5c0cc97befa54ab3925e85b42b3d46b6.png
反向索引 用于 大规模文档 信息 检索 中。

 

Shakespeare’s Collected Works 莎士比亚全集 )这本书。假定想知道其中的哪些剧本包含 Brutus Caesar 但不包含 Calpurnia
 
一种办法就是 从头到尾阅读 这本全集,对 每部剧本 都检测它是否包含 Brutus Caesar 且同时 不包含 Calpurnia
 
这种 线性扫描 就是一种最简单的计算机文档检索方式。
 
使用现代计算机,对一个规模不大的文档集( 莎士比亚全集 也就只有不到 100 万个单词)进行线性扫描非常简单,根本不需要做额外的处理。

 

但是,很多情况下只采用上述扫描方式远远不够的,需要做更多的处理。这些情况如下所述:

    1  大规模文档集条件下的快速查找。在线数据量的增长并不低于计算机速度的增长,可能需要在几十亿到上万亿单词的数据规模下进行查找;

    2) 有时我们需要更灵活的匹配方式。比如,在grep 命令下不能支持诸如 Romans NEAR countrymen 之类的查询,这里的NEAR 操作符的定义可能为“ 5 个词之内” 或者“ 同一句子中” ;

  3)需要对结果进行排序。很多情况下,用户希望在多个满足自己需求的文档中得到最佳答案

此时,不能再采用上面的线性扫描方式了。一种 非线性扫描 的方式是 事先给文档建立索引( index
 
仍然回到上述 莎士比亚全集 的例子,并通过这个例子来介绍布尔检索模型的基本知识。
 
给定 词表 莎士比亚全集 中共使用约 32 000 个不同 的词。
 
假定对每篇文档(这里指每部剧本)都事先记录它 是否 包含词表中的某个词,结果就会得到一个由布尔值构成的词项 文档关联矩阵( incidence matrix )。
860d64655c58482fbd9ddf9d639ae7d6.png
为响应查询 Brutus AND Caesar AND NOT Calpurnia ,分别取出 Brutus Caesar Calpumia 对应的行向量,并对 Calpumia 对应的向量求反,然后进行基于位的与操作,得到: 110100 AND 110111 AND 101111 = 100100
 
结果向量中的第 1 和第 4 个元素为 1 ,这表明该查询对应的剧本是 Antony and Cleopatra Hamlet ( 见图 )

 84f0ad7dee2145978f7fea930a05bec1.png

 布尔检索模型接受布尔表达式查询,即通过ANDOR NOT 等逻辑操作符将词项连接起来的查询。

在该模型下,每篇文档只被看成是一系列词的集合。
 
下面考虑一个更真实的场景,同时也引出信息检索中的一些术语和概念。
 
假定有 N=1 000 000 篇文档。所谓“ 文档” (document) 指的是检索系统的检索对象,它们可以是一条条单独的记录或者是一本书的各章。
 
所有的文档组成文档集( collection ),有时也称为语料库( corpus )。
 
假设每篇文档包含约 1 000 个词(相当于 2 3 页书),每个词的平均长度是 6 B (含空格和标点符号),那么就会得到一个大约 6 GB 大小的文档集。
 
通常,这些文档中大概会有 M=500 000 个不同的词项。
 
目标是开发一个能处理 ad hoc 检索( ad hoc retrieval )任务(一种常见的信息检索任务)的系统
 
在这个任务中,任一用户的信息需求通过一次性的、由用户提交的查询传递给系统,系统从文档集中返回与之相关的文档。
 
信息需求( information need )指的是用户想查找的信息主题,它和查询( query )并不是一回事,后者由用户提交给系统以代表其信息需求。
 
如果一篇文档包含对用户需求有价值的信息则认为是相关的( relevant )。
 
上面举的例子当中,信息需求采用多个特定的词语组合来表达,从这点来说,人工构造的痕迹明显。
 
而通常来说,假如用户对 pipeline leaks 感兴趣,在检索时,不管是严格采用这些词还是采用反映这一概念的其他词(如 pipeline rupture )来表达该需求,都能得到相关的结果。

 

对检索系统的效果(effectiveness,搜索结果的质量)进行评价,通常需要知道某个查询返回结果的两方面的统计信息:

    1.正确率(Precision返回的结果中真正和信息需求相关的文档所占的百分比。

    2.召回率(Recall):所有和信息需求真正相关的文档中被检索系统返回的百分比。

回到刚才的例子,显然不能再采用原来的方式来建立和存储一个词项 文档矩阵。
 
由于词项的个数是 50 万,而文档的篇数为 100 万,所以其对应的词项 文档矩阵大概有 5 000 亿( 50 ×100 万)个取布尔值的元素,这远远大于一台计算机内存的容量。
 
另外,不难发现,这个庞大的矩阵实际上具有 高度 稀疏性 ,即大部分元素是 0 ,只有极少部分元素为 1
 
可以对上述例子做个粗略计算,由于每篇文档的平均长度是 1 000 个单词,所以 100 万篇文档在词项 文档矩阵中最多对应 10 亿( 1 000×1 000 000 )个 1 ,也就是在词项 文档矩阵中至少有 99.8% 1 10 亿 /5 000 亿)的 元素为 0 。很显然,只记录原始矩阵中 1 的位置的表示方法比词项 文档矩阵更好。
 
回到刚才的例子,显然不能再采用原来的方式来建立和存储一个词项 文档矩阵。上述思路将引出信息检索中的核心概念 —— 倒排索引( inverted index )。
 
从名称上看,倒排索引中“ 倒排” 二字似乎有些多余,因为一般提到的索引都是从词项反向映射到文档。
 

然而,倒排索引(有时也称倒排文件)已经成为信息检索中的一个标准术语。倒排索引的基本思想参见图1-3。

c3da5f124ca94e37ae3350fe58254994.png

左部称为 词项词典 dictionary ,简称词典,有时也称为 vocabulary 或者 lexicon 本书中 dictionary 指图中的数据结构,而 vocabulary 则指词汇表) 。

 

每个词项都有一个记录出现该词项的所有文档的列表,该表中的每个元素记录的是词项在某文档中的一次出现信息(在后面的讨论中,该信息中往往还包括词项在文档中出现的位置),这个表中的每个元素通常称为倒排记录( posting )。
 
每个词项对应的整个表称为倒排记录表( posting list )或倒排表( inverted list )。所有词项的倒排记录表一起构成 全体倒排记录表( postings
 
图中的 词典 按照 字母顺序 进行 排序 ,而倒排记录表则按照文档 ID 号进行排序。
 
为获得检索速度的提升,就必须要事先建立索引。建立索引的主要步骤如下:
 

  (1)  收集需要建立索引的文档,如:

2191c98b125e49cda2259c151bc2742a.png

 (2) 每篇文档转换成一个个词条(token)的列表,这个过程通常称为词条化(tokenization),如:

7102487899214d908f0451290ca0b7e2.png

  (3) 进行语言学预处理,产生归一化的词条来作为词项,如:

bdc79d2757044af3b662330708f9a90c.png

 (4) 对所有文档按照其中出现的词项来建立倒排索引,索引中包括一部词典和一个全体倒排记录表。

现在,假定前3 步已经执行完毕,我们来讲述如何通过基于排序的索引构建方法(sort-based indexing)来建立一个基本的倒排索引。

给定一个文档集,假定每篇文档都有一个唯一的标识符即编号(docID)。

在索引构建过程中,给每篇新出现的文档赋一个连续的整数编号。
 
在上述的前 3 步处理结束后,对 每篇文档建立索引时 的输入就是一个归一化的词条表,也可以看成二元组(词项,文档 ID )的一个列表。
975bb8a0309f466f9cf6a4ee2c5ad3b2.png

 

建立索引最核心的步骤是将这个列表 按照词项的字母顺序进行排序 ,之后得到图中部显示的结果,其中一个词项在同一文档中的多次出现会合并在一起最后整个结果分成词典和倒排记录表两部分 .
 

由于一个词项通常会在多篇文档中出现,上述组织数据的方法实际上也已经减少了索引的存储空间.

词典中同样可以记录一些统计信息,比如出现某词项的文档的数目,即 文档频率( document frequency ), 这里也就是每个倒排记录表的长度 .
 
该信息对于一个基本的布尔搜索引擎来说并不是必需的,但是它可以在处理查询时提高搜索的效率 .
 
倒排记录表 会按照 docID 进行排序,这为高效的查询处理提供了重要基础。在 ad hoc 文本检索 中, 倒排索引是 其他结构无法抗衡的 高效索引结构
 
在最终得到的倒排索引中, 词典 倒排记录表 都有存储开销。前者往往放在 内存 中,而后者由于规模大得多,通常放在 磁盘 上。
 
由于有些词在很多文档中出现,而另外一些词出现的文档数目却很少,所以,如果采用 定长数组 的方式将会 浪费 很多 空间
 
对于内存中的一个倒排记录表,可以采用两种好的存储方法:一个是 单链表 ,另一个是 变长数组 .

 

单链表 singly linked list 便于文档的插入和更新 (比如,对更新的网页进行重新采集),因此通过增加指针的方式可以很自然地扩展到更高级的索引策略 .
 
变长数组 variable length array )的存储方式一方面可以节省指针消耗的空间,另一方面由于采用连续的内存存储,可以充分利用现代计算机的缓存( cache )技术来提高访问速度 .
 
 如果索引更新不是很频繁的话,变长数组的存储方式在空间上更紧凑,遍历也更快。
 
另外,也可以采用一种混合的方式,即采用 定长数组 链表方式 。当倒排记录表存在磁盘上的时候,它们被连续存放并且没有显式的指针 .

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

WiIsonEdwards

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值