查看原文
其他

具有全局唯一索引的跨分区唯一性保证

Cary Huang IvorySQL开源数据库社区
2024-09-30

01


简介

我的同事David最近发表了一篇文章全局索引,一种不同的方法”,描述了我们正在做的实现全局唯一索引的工作,该方法不会改变当前PostgreSQL的分区框架,同时允许跨分区唯一性约束。为了实现这一点,我们必须首先知道 PostgreSQL 当前如何确保具有唯一索引的单个表的唯一性,然后在此逻辑之上扩展以支持跨分区唯一性检查。

😄我的这个博客在这里粗略地概述了独特索引在PG中的工作原理。在这篇博客中,我想介绍一下我们采取的方法,以确保在串行和并行构建的索引创建过程中进行跨分区唯一性检查。

02


串行全局唯一索引构件中的跨分区唯一性检查

如本博客中所述,通过对表进行堆扫描并对一个或两个 BTSpool 结构中的元组进行排序来保证唯一性。如果两个具有相同扫描键的元组彼此相邻排序,则会发现唯一性违规并排除系统错误。例如,在包含 6 个分区的分区表上构建全局唯一索引,至少将填充 6 个不同的 BTSpool,并用于确定每个分区创建中的唯一性冲突。

因此,如果另一个分区中存在重复项,PG 当前无法检测到。因此,从理论上讲,如果我们在全局范围内引入另一个对所有分区可见的 BTSpool,并且直到扫描完所有分区,我们可以将所有分区的所有索引元组放入此全局假脱机中,并确定跨分区唯一性,只需在上次分区扫描完成时对其进行排序即可。


下图说明了新的全局级别 BTSpool(称为 spool3)的位置,以及如何使用它来确定跨分区唯一性。

跨分区唯一性检查的实际操作:

create table globalidxpart (a int, b int, c text) partition by range (a);
CREATE TABLE

create table globalidxpart1 partition of globalidxpart for values from (0) to (100000);
CREATE TABLE

create table globalidxpart2 partition of globalidxpart for values from (100001) to (200000);
CREATE TABLE

create table globalidxpart3 partition of globalidxpart for values from (200001) to (300000);
CREATE TABLE

create table globalidxpart4 partition of globalidxpart for values from (300001) to (400000);
CREATE TABLE

create table globalidxpart5 partition of globalidxpart for values from (400001) to (500000);
CREATE TABLE

create table globalidxpart6 partition of globalidxpart for values from (500001) to (600000);
CREATE TABLE

insert into globalidxpart (a, b, c) values (42, 572814, 'inserted first on globalidxpart1');
INSERT 0 1

insert into globalidxpart (a, b, c) values (150000, 572814, 'inserted duplicate b on globalidxpart2');
INSERT 0 1

insert into globalidxpart (a, b, c) values (550000, 572814, 'inserted duplicate b on globalidxpart6');
INSERT 0 1

create unique index on globalidxpart (b) global; 
ERROR:  could not create unique index "globalidxpart1_b_idx"
DETAIL:  Key (b)=(572814) is duplicated.

delete from globalidxpart where a = 150000 and b = 572814;
DELETE 1

create unique index on globalidxpart (b) global;
ERROR:  could not create unique index "globalidxpart1_b_idx"
DETAIL:  Key (b)=(572814) is duplicated.

delete from globalidxpart where a = 42 and b = 572814;
DELETE 1

create unique index on globalidxpart (b) global; 
CREATE INDEX
一些注意事项

这个新的全局 BTS pool 可以容纳多少个索引元组❓由于它需要保存所有分区中的元组,因此是否需要分配大量内存空间❓❓


它使用maintenance_work_mem from postgresql.conf ,与 BTSpool1 相同。当它接近容量时,它将开始将元组作为临时文件写入磁盘(也称为PostgreSQL中的逻辑磁带,稍后会详细介绍)而不是内存中。因此,线轴实际上可以容纳比我们想象的更多的元组。在最终排序之前,如果内存不足以容纳所有元组,我们将不得不对 PG 在磁盘上写出的所有逻辑磁带进行最终排序,然后进行最终排序以确定唯一性。

03

并行全局唯一索引构建中的跨分区唯一性检查


在串行索引构建案例中,使用全局规模假脱机进行跨分区唯一性检查非常简单。


PG 当前的并行排序要复杂得多,因为它用于logical tapes共享和合并每个工作线程作为临时文件写入磁盘的中间排序结果。在最终排序时,领导者进程接管工作线程写出的所有逻辑磁带,并执行最终合并排序以确定唯一性。

例如,如果请求 3 个工作线程(其中一个是领导者)来构建单个分区的索引,则会在磁盘上写出 3 个逻辑磁带(或 3 个临时文件)(每个工作线程在写入之前由每个工作线程进行中间排序)。工作线程使用共享内存相互协调,以便它们不会写入相同的磁带文件并相互覆盖。当所有工作人员完成后,领导者将“接管所有逻辑磁带”,合并磁带并执行最终排序。完成后,PG 将销毁所有并行工作线程,进而销毁磁盘上的所有逻辑磁带文件,然后再转到下一个分区。


因此,要并行实现跨分区检查,我们必须在完成对一个分区的排序后保留这些逻辑磁带文件。目前,PG 会在分区的索引构建并行完成时销毁它们。如果生成的工作线程数为 X,分区数为 Y,则在最后一个分区构建完成时,磁盘上应该有 X * Y 逻辑磁带,我们需要对其进行合并排序。我们仍然使用单独的 spool3 来管理磁带并保留它们,直到所有分区都完成。


下图说明了 spool3 的位置以及如何使用它来并行确定跨分区唯一性。

继续滑动看下一个
IvorySQL开源数据库社区
向上滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存