位图索引
实现概述
此页面详述了为 PostgreSQL 开发的磁盘位图索引访问法。
历史
虽然磁盘位图索引访问法最初是为 Greenplum 的 Bizgres 系统开发的,但它已移植并修改以满足 Postgres 标准。
- Bizgres 实现
- Ayush Parashar (Greenplum)
- Jie Zhang (Greenplum)
- Mark Kirkwood (Greenplum)
- Gavin Sherry (Greenplum)
- PostgreSQL 实现
- Gavin Sherry (Greenplum)
- 8.3 初始移植
- 博士Gianni Ciolli 和 Gabriele Bartolini (2ndQuadrant Italia)
- 8.4 的错误修复、重构和一般清理
- Jonah H. Harris (myYearbook.com) 和 Simon Riggs (2ndQuadrant)
- 8.5 的错误修复、重构、优化和一般清理
- Gavin Sherry (Greenplum)
待办事项
- 添加待办事项... :-)
概念
一般
与基于树的索引算法相比,磁盘位图索引为低基数、以读为主的数据提供了巨大的空间和性能优势。这种优势主要归因于 I/O 和 CPU 方面的节省。
在我们的实现中,为每个不同的键值创建一个压缩位图向量。这些位图向量不仅可用于查找表中单个键值的所有出现,还可以使用按位运算查找多个键值。
first_name | last_name | gender |
---|---|---|
John | Smith | M |
David | Jones | M |
Michael | Johnson | M |
Chris | Lee | M |
Mike | Brown | M |
Mark | Williams | M |
Paul | Rodriguez | M |
Daniel | Garcia | M |
James | Gonzalez | M |
Maria | Lopez | F |
Sarah | Martinez | F |
Laura | Martin | F |
Robert | Perez | M |
Lisa | Miller | F |
Jennifer | Taylor | F |
Andrea | Thomas | F |
Steve | Wilson | M |
Peter | Davis | M |
Kevin | Khan | M |
Jason | Ali | M |
Jessica | Singh | F |
Michelle | Sanchez | F |
Karen | Anderson | F |
Joe | Hernandez | M |
Brian | Chan | M |
Alex | Ahmed | M |
Richard | White | M |
Linda | Wong | F |
Julie | Thompson | F |
Anna | Jackson | F |
Andrew | Kumar | M |
Mary | Moore | F |
Eric | Gomez | M |
Tom | Diaz | M |
Thomas | Walker | M |
Martin | James | M |
Scott | Green | M |
Matt | Robinson | M |
Jim | Clark | M |
Ali | Young | M |
Tony | Scott | M |
Carlos | Chen | M |
Jeff | Hall | M |
Marco | Wright | M |
Ryan | Adams | M |
Bob | Allen | M |
Dave | Hill | M |
Jose | Rossi | M |
性别与 first_name 和 last_name 这两个属性不同,其基数非常低,只有 2(M 或 F)。因此,它非常适合使用位图索引。
不同的键值
不同的键值由值列表 (LOV) 组件处理...
不同的键位图
如上所述,一个单一比特向量与每个不同的键值相关联...
每个 LOV 项目(M 和 F)的未压缩位图如下
M = 11111111 10001000 11110001 11100010 11111111 11111111 F = 00000000 01110111 00001110 00011101 00000000 00000000
压缩
我们的实现基于混合游程长度压缩算法,该算法用两个组件(即头和内容部分)表示比特向量。
头部分 (头单词)
头部分包含比特,每个比特都对应内容部分中的一个单词。如果头部分中的某个比特为 1,则对应的内容部分中的单词就是一个压缩单词;如果比特为 0,则对应的单词不是压缩单词。
内容部分 (内容单词)
对于内容部分中的压缩单词,该单词中的第一个比特表示压缩的是 1 或 0。其余比特表示“<位数>/<单词大小>”的含义。
示例
考虑 LOV 项目 M 的未压缩位图向量
11111111 10001000 11110001 11100010 11111111 11111111
如果一个单词的大小设置为 8,则此位图向量的 HRL 压缩形式如下
header section: 00001 content section: 11111111 10001000 11110001 11100010 10000010
第一个单词未压缩。
第二个单词未压缩。
第五个单词已被压缩,并且其第一个比特设置为 1。因此,对 1 进行压缩。由于 10 计算结果为 2,这个压缩单词表示 16 个比特的 1(2 * 8 = 16)。
考虑 LOV 项目 F 的未压缩位图向量
00000000 01110111 00001110 00011101 00000000 00000000
如果一个单词的大小设置为 8,则此位图向量的 HRL 压缩形式如下
header section: 00001 content section: 00000000 01110111 00001110 00011101 00000010
第一个单词未压缩。
第二个单词未压缩。
第五个单词已被压缩,并且其第一个比特设置为 0。因此,对 0 进行压缩。由于 10 计算结果为 2,这个压缩单词表示 16 个比特的 0(2 * 8 = 16)。
基于排序的优化
由于位图索引通常用于数据仓库系统,因此在 ETL 阶段对值进行预排序可以提供更好的压缩。
示例
考虑 LOV 项目 M 的未压缩位图向量
00000000 00000111 11111111 11111111 11111111 11111111
如果一个单词的大小设置为 8,则此位图向量的 HRL 压缩形式如下
header section: 001 content section: 00000000 00000111 10000100
第一个单词未压缩。
第二个单词未压缩。
第三个单词已被压缩,并且其第一个比特设置为 1。因此,对 1 进行压缩。由于 100 计算结果为 4,这个压缩单词表示 32 个比特的 1(4 * 8 = 32)。
考虑 LOV 项目 F 的未压缩位图向量
11111111 11111000 00000000 00000000 00000000 00000000
如果一个单词的大小设置为 8,则此位图向量的 HRL 压缩形式如下
header section: 001 content section: 11111111 11111000 00000100
第一个单词未压缩。
第二个单词未压缩。
第三个单词已被压缩,并且其第一个比特设置为 0。因此,对 0 进行压缩。由于 100 计算结果为 4,这个压缩单词表示 32 个比特的 0(3 * 8 = 24)。
在各种用例下的操作
以下部分描述了针对不同情况位图索引所遵循的各种过程。
索引创建
元组插入
元组更新
元组删除
索引扫描
VACUUM
运行时行为
预写式日志记录
位图索引的更改已被 WAL 记录,可以防止崩溃。
锁定
插入/更新处理
VACUUM
性能
索引创建
索引大小
索引性能
基准测试套件
页面检查
一般
bm_page_headers()
元页面
bm_metap()
LOV 页
bm_lov_page_stats()
位图页
bm_bmv_page_stats()
页面布局
磁盘上位图索引实现包括多个页面布局
元页面
此页面包含有关索引的元数据,始终存储为第零页,并且被称为 BM_METAPAGE。
+----------------+---------------------------------+ | PageHeaderData | BMMetaPageData | +----------------+---------------------------------+ | ^ pd_lower | | | | | | | | | | | | | | | | | +--------------------------------------------------+ pd_upper/pd_special ^
值列表 (LOV) 页
值列表 (LOV) 页用来存储要索引的属性的唯一值列表,一些与其对应的位图向量相关的元数据,以及指向其位图向量的指针。对于每个唯一的值,都有一个与其关联的 BMLOVItemData。
LOV 页维护一个称为 LOV 项的 BMLOVItemData 实例数组。为了快速查找给定值的 LOV 项,我们创建一个堆来维护所有唯一键值以及 LOV 页中 LOV 项的块号和偏移号。也就是说,这个新堆中有 <属性数量> + 2 个属性。在此堆中,我们还使用属性作为键在此堆上创建一个新的 B 树索引。这样,对于给定的值,我们搜索此 B 树以快速找到给定值的对应 LOV 项的适用块号和偏移号。
第一个 LOV 页保留给 NULL 键值,被称为 BM_LOV_STARTPAGE。
+----------------+---------------------------------+ | PageHeaderData | linp1 linp2 linp3 ... | +-----------+----+---------------------------------+ | ... linpN | | +-----------+--------------------------------------+ | ^ pd_lower | | | | v pd_upper | +-------------+------------------------------------+ | | BMLOVItemN ... | +-------------+------------------------------------+ | ... BMLOVItem3 BMLOVItem2 BMLOVItem1 | +--------------------------------------------------+ pd_upper/pd_special ^
位图向量页
此页面布局表示压缩位图向量。
每个位图页存储两部分信息:标头词和内容词。标头词中的每一比特对应于内容词中的一个词。如果标头词中的某一位是 1,那么其对应的内容词是一个压缩词。否则,它是一个原义词。
如果一个内容词是一个填充词,这意味着有一串 0 位或 1 位。这个内容词中最重要的一位表示这串位是 0 位还是 1 位。其余的位存储值“位数 / BM_WORD_SIZE”。
在此块中,使用了两个数据结构:BMBitmapVectorPageData 和 BMPageOpaqueData。BMBitmapVectorPageData 包含两个固定大小的数组:标头词和内容词;两个数组的大小基于编译时的 Postgres 块大小定义。BMPageOpaqueData 包含与该页面相关的信息,包括使用的词数量、位图中的下一页以及该页面中设置的最后一个 TID。
+----------------+---------------------------------+ | PageHeaderData | BMBitmapVectorPageData | +----------------+---------------------------------+ | ^ pd_lower | | | | | | | | | | | | v pd_upper | +-------------------------------+------------------+ | ... | BMPageOpaqueData | +-------------------------------+------------------+ ^ pd_special
源代码详细信息
文件
由于按磁盘方式编制的位图索引是另一种访问方法,因此会相应地布置新文件
- src/backend/access/bitmap/
- bitmapattutil.c--属性级别 List-of-Values (LOV) 处理
- bitmap.c--顶级访问方法接口函数
- bitmapinsert.c--位图插入
- bitmappages.c--特定于位图的页面处理
- bitmapsearch.c--位图搜索/迭代
- bitmaputil.c--通用位图实用函数/VACUUM 处理
- bitmapxlog.c--WAL 处理
- src/include/access/
- bitmap.h--按磁盘方式编制访问方法的位图标题文件
命名约定
函数
对于 b-tree 和哈希访问方法,不用下划线前缀命名的函数,位图索引函数的命名类似于 gin/gist 中使用 lowerCamelCase,前缀为 bm。示例如下
- bmInitLOVPage
- bmReadBuffer
- bmWriteAndReleaseBuffer
- bmSetTIDBit
结构/类型定义/变量
C 结构和类型定义使用 UpperCamelCase 命名。结构成员和变量使用 lowerCamelCase 命名,除非邮政已对该变量使用了一致性名称。
例如,content words 的结构成员应为“contentWords”,而不是 content_words 或 contentwords。
评论
为了进一步提升PostgreSQL 源代码文档,所有评论与 doxygen 兼容。来自 bitmappages.c
/** * Reads, locks, and returns a given relation's block as a buffer. * * @param[in] reln The relation whose block to read * @param[in] blockNum The block number to read * @param[in] lockMode The buffer content lock type to use * * @return A locked buffer containing the requested block of the requested relation. */ Buffer bmReadBuffer (Relation reln, BlockNumber blockNum, int lockMode) {
编译时选项
位图索引追踪
为了帮助进行调试和准确性验证,已对位图索引访问方法添加了大量追踪。如果需要追踪,则必须在编译时通过定义(取消注释)src/include/access/bitmap.h 中的 TRACE_BITMAP_INDEX 来启用追踪。启用时,所有相关的内部位图索引信息都将在消息级别 DEBUG1-DEBUG5 中发出。
追踪的示例如下
DEBUG: bminsert (rel => "bitmapx1", DEBUG: ht_ctid => "(0,1)") DEBUG: bmPerformInsert (rel => "bitmapx1", DEBUG: ht_ctid => "(0,1)") DEBUG: bmReadBuffer (reln => "bitmapx1", DEBUG: blockNum => 0, DEBUG: lockMode => 1) DEBUG: bmOpenLOVHeapAndIndex (metaPage->bm_lov_heapId => 66151, DEBUG: metaPage->bm_lov_indexId => 66154, DEBUG: lockMode => 3) DEBUG: bmInsertTuple (reln => "bitmapx1", DEBUG: metaBuffer => 0, DEBUG: tidBit => 1, DEBUG: ht_ctid => "(0,1)", DEBUG: lovHeap => "pg_bm_66150", DEBUG: lovIndex => "pg_bm_66150_index", DEBUG: use_wal => "TRUE") ... DEBUG: bmSetTIDBit (reln => "bitmapx1", DEBUG: lovBuffer => 1, DEBUG: lovOffset => 2, DEBUG: tidBit => 1, DEBUG: use_wal => "TRUE")
目录更改
- 位图索引已新增为 WAL 的资源管理器(RM_BITMAP_ID)
- 位图索引已新增至 pg_am、pg_amop、pg_proc 及类似项中。
- 已创建 pg_bitmapindex,这是一用于包含数据库范围的位图索引特定数据的新名称空间。