直接按块范围索引(BRIN)标识符查询 Postgres 表
Posted
技术标签:
【中文标题】直接按块范围索引(BRIN)标识符查询 Postgres 表【英文标题】:Query Postgres table by Block Range Index (BRIN) identifier directly 【发布时间】:2016-04-21 22:48:10 【问题描述】:我有 N 台客户端机器。我想用 BRIN 索引的不同分区加载每台机器。
这需要:
使用预定义的分区数创建 BRIN - 等于客户端计算机的数量 从在 BRIN 分区标识符上使用 WHERE 而非在索引列上过滤的客户端发送查询主要目标是在将单个表从 postgres 加载到分布式客户端机器时提高性能,保持客户端之间的行数相等 - 或者如果 rows count 不除以 machines 则接近相等数。
我目前可以通过维护新列来实现它,该列将我的表分成等于客户端机器数量的桶(或即时使用row_number() over (order by datetime) % N
)。这种方式在时间和内存方面效率不高,而 BRIN 索引看起来是一个不错的功能,可以加快此类用例的速度。
3 台客户端机器的最小可重现示例:
CREATE TABLE bigtable (datetime TIMESTAMPTZ, value TEXT);
INSERT INTO bigtable VALUES ('2015-12-01 00:00:00+00'::TIMESTAMPTZ, 'txt1');
INSERT INTO bigtable VALUES ('2015-12-01 05:00:00+00'::TIMESTAMPTZ, 'txt2');
INSERT INTO bigtable VALUES ('2015-12-02 02:00:00+00'::TIMESTAMPTZ, 'txt3');
INSERT INTO bigtable VALUES ('2015-12-02 03:00:00+00'::TIMESTAMPTZ, 'txt4');
INSERT INTO bigtable VALUES ('2015-12-02 05:00:00+00'::TIMESTAMPTZ, 'txt5');
INSERT INTO bigtable VALUES ('2015-12-02 16:00:00+00'::TIMESTAMPTZ, 'txt6');
INSERT INTO bigtable VALUES ('2015-12-02 23:00:00+00'::TIMESTAMPTZ, 'txt7');
预期输出:
客户端 12015-12-01 00:00:00+00, 'txt1'
2015-12-01 05:00:00+00, 'txt2'
2015-12-02 02:00:00+00, 'txt3'
客户端 2
2015-12-02 03:00:00+00, 'txt4'
2015-12-02 05:00:00+00, 'txt5'
客户端 3
2015-12-02 16:00:00+00, 'txt6'
2015-12-02 23:00:00+00, 'txt7'
问题: 如何使用预定义的分区数量创建 BRIN 并运行过滤分区标识符而不是过滤索引列的查询? 可选地,BRIN(或其他 pg 好东西)可以加快从单个表并行加载多个客户端的任务的任何其他方式?
【问题讨论】:
这可能在dba.stackexchange.com上更成功 多个客户端意味着多个会话。我认为您不能(几乎)以并发安全的方式(使用任何索引)均匀地拆分表。或者这不在桌面上?顺便说一句,为什么不是经典的partitioning? (在 PostgreSQL 中,您实际上可以查询单个分区。)——没关系,我明白了:客户端数量可能会有所不同。 @pozs 硬假设是在查询表时没有写入表,因为这两个进程在工作流中按顺序运行。 BRIN 看起来非常适合该用例:加载数据、创建 brin、分析表,然后运行查询。 问:所有(客户端)机器都知道你开始的那一刻应该有多少个分区吗?换句话说,假设您在第 n 天使用 x 台机器开始该过程,您希望您的表均匀地分布在 x 个分区上,对吗?但是在第 x+1 天,您可能拥有 y 台机器,因此希望将其拆分为 y 个分区。那么我的问题是:当客户端机器连接时,它是否知道它想要 1/x 或 1/y 的表? @deroby 不,在客户端内部只有一个唯一的整数序列,所以他们只知道选择哪个存储桶。需要在数据库端提供存储桶。主客户端进程知道有多少节点,因此可以创建表作为选择和计算存储桶,但这与使用底层 brin 分区相比似乎效率低下。 【参考方案1】:基本上,您只需要知道加载后关系的大小,然后应将pages_per_range
存储参数设置为提供所需分区数的除数。
不需要引入人为的分区ID,因为已经支持足够的类型和运算符。物理表布局在这里很重要,所以如果你坚持分区 ID 是关键,并最终在自然加载顺序和人工分区 ID 之间引入乱序映射,请确保在创建 BRIN 之前按照该列的排序顺序对表进行聚类。
但是,与此同时,请记住,更多离散值比更少的值更有可能命中索引,因此高基数更好 - 人工分区标识符将具有自然键的基数的 1/n,其中 n 是每个分区的不同值的数量。
更多here和here。
【讨论】:
无需引入人工分区 ID - 那么如何在不猜测索引列的值的情况下将数据查询成相等的块呢?或它们的分布,所以我可以猜出大块的范围?我熟悉 pg 文档,但它没有回答我的问题,你的回答也没有。 那么答案是 BRIN 不是你想要的。就值而言,它是非歧视性的,它只是将整个范围分成相等大小的块。它将帮助您在顺序扫描中访问更少的页面(就像任何索引一样),但仅此而已。它不能告诉你要搜索什么。 但是从逻辑的角度来看是可以的,它基本上是为分区 id 导出一个 API 的问题。也许内部可以在某处获得但尚未记录。 您可以尝试通过广泛使用 ctid 伪列(它返回表中任何记录的(页面,记录)位置)来理解它。不过,您可能会发现,您的解决方案与 BRIN 无关,除了关键条件 - 拥有自然聚集的数据(您这样做,时间戳)。我猜在表中有 x 页时,n 的分区 m 可能正在执行查询“ctid 在 ((m-1)*(x/n)) 和 (m*(x/n)) 之间的位置”。由于这与表结构直接相关,因此您不需要额外的对象。 请注意 where 子句中来自 ctid 的 page number has to be extracted。【参考方案2】:听起来您想在多台机器上对表进行分片,并让每个本地表(全局表的一个分片)都有一个 BRIN 索引,其中只有一个存储桶。但这没有任何意义。如果单个 BRIN 索引范围覆盖整个(本地)表,那么它永远不会很有帮助。
听起来您正在寻找使用可用于分区排除的 CHECK 约束的分区。 PostgreSQL 长期以来一直支持表继承(尽管不是每个分区都在单独的机器上)。使用此方法,必须为每个分区显式设置 CHECK 约束所涵盖的范围。这种明确指定边界的能力听起来就像您正在寻找的那样,只是使用了不同的技术。
但是,分区排除约束代码不适用于模数。代码足够聪明,知道WHERE id=5
只需要检查CHECK (id BETWEEN 1 and 10)
分区,因为它知道 id=5 意味着 id 介于 1 和 10 之间。更准确地说,它知道它的反义词。
但编写代码时从未知道WHERE id=5
暗示id%10 = 5%10
,即使人类知道这一点。因此,如果您在模运算符(如 CHECK (id%10=5)
而不是范围)上构建分区,如果您希望它利用约束,则必须使用 WHERE id = $1 and id % 10= $1 %10
散布所有查询。
【讨论】:
物理分区是解决这个问题的最好方法,有没有一种动态的方法来创建(分区)表作为选择?所以我可以使用row_number() over ()
在数据库端准备分区?【参考方案3】:
根据您的描述和 cmets,我会说您看错了方向。您想预先拆分表,以便访问快速而简单,但不必预先拆分,因为这需要您预先知道节点的数量,如果我理解正确的话,这是一种变量。无论如何,拆分东西也需要相当多的处理。
说实话,我会以不同的方式解决您的问题。我宁愿建议在给定范围内为每条记录分配一个伪随机值,而不是将每条记录分配给存储桶。我不了解 Postgres,但在 MSSQL 中我会使用 BINARY_CHECKSUM(NewID())
而不是 Rand()
。主要原因是随机函数在那里更难使用基于 SET 的函数。相反,您也可以使用一些返回合理工作空间的哈希代码。无论如何,在我的 MSSQL 情况下,结果值将是一个有符号整数,位于 -2^31 到 +2^31 范围内的某个位置(给或取,检查文档以了解确切的边界!)。因此,当主机决定分配 n 台客户端机器时,可以为每台机器分配一个精确的范围——考虑到随机化器/散列算法的属性——将包含一个相当接近的工作负载除以 n 的近似值。
假设您在选择字段上有一个索引,这应该相当快,无论您决定将表拆分为一千块还是一百万块。
PS:请注意,只有当要处理的行数(大大)超过将进行处理的机器数量时,这种方法才会“正常”工作。如果数量较少,您可能会看到几台机器没有得到任何东西,而其他机器却可以完成所有工作。
【讨论】:
你理解正确。它可能需要与创建 brin 一样多的处理,不是吗?您提出的解决方案看起来并不高效,至少与创建 brin 一样。按索引选择会很快,但不能像直接访问分区数据那样扩展——我想用 brin 来实现。 请随意不同意,但恕我直言,按索引进行任意选择很容易胜过您花费在拆分表上的额外时间(从我在 BRIN 索引的帮助中读到的内容就是您的内容)需要提前做)。我没有使用 BRIN 索引的经验,但从帮助来看,它们看起来很像某种(有损)分区。我都是专业分区的,但不要指望一眨眼就将一个表从 20 个分区拆分为 25 个分区,反之亦然,你会非常失望。 AFAIK 你可以直接在桌子上制作布林,无需任何前期准备。 就像我说的,我不熟悉 BRIN 索引,但您希望索引能够处理“您是第 5 块的一部分”的情况,对吧?然后,您需要以某种方式预先定义哪些记录是块 5 的一部分(通过基于某些字段值创建索引)。如果今天会有 X 台客户端机器,但明天会有 Y 台,这意味着您需要重新定义分桶,从而“一夜之间”重新创建索引。如果 BRIN 真的在磁盘上“保存所有相关记录”,这可能会导致需要对数据进行一些移动。稍后的获取可能很快,准备不会。以上是关于直接按块范围索引(BRIN)标识符查询 Postgres 表的主要内容,如果未能解决你的问题,请参考以下文章