如何降低 Postgres 中 set_bit 的成本?

Posted

技术标签:

【中文标题】如何降低 Postgres 中 set_bit 的成本?【英文标题】:How do I reduce the cost of set_bit in Postgres? 【发布时间】:2018-09-22 18:46:54 【问题描述】:

我正在运行 PostgreSQL 9.6,并且正在对以下表结构进行实验:

CREATE TABLE my_bit_varying_test (
  id SERIAL PRIMARY KEY,
  mr_bit_varying BIT VARYING
);

为了了解如果我同时在 100,000 位数据上重置位,我可以期待多少性能,我编写了一个小的 PL/pgSQL 块,如下所示:

DO $$
DECLARE
  t  BIT VARYING(100000) := B'0';
  idd INT;
BEGIN
  FOR I IN 1..100000
  LOOP
    IF I % 2 = 0 THEN
      t := t || B'1';
    ELSE
      t := t || B'0';
    end if;
  END LOOP ;

  INSERT INTO my_bit_varying_test (mr_bit_varying) VALUES (t) RETURNING id INTO idd;
  UPDATE my_bit_varying_test SET mr_bit_varying = set_bit(mr_bit_varying, 100, 1) WHERE id = idd;
  UPDATE my_bit_varying_test SET mr_bit_varying = set_bit(mr_bit_varying, 99, 1) WHERE id = idd;
  UPDATE my_bit_varying_test SET mr_bit_varying = set_bit(mr_bit_varying, 34587, 1) WHERE id = idd;
  UPDATE my_bit_varying_test SET mr_bit_varying = set_bit(mr_bit_varying, 1, 1) WHERE id = idd;

  FOR I IN 1..100000
  LOOP
    IF I % 2 = 0 THEN
      UPDATE my_bit_varying_test
      SET mr_bit_varying = set_bit(mr_bit_varying, I, 1)
      WHERE id = idd;
    ELSE
      UPDATE my_bit_varying_test
      SET mr_bit_varying = set_bit(mr_bit_varying, I, 0)
      WHERE id = idd;
    end if;
  END LOOP ;
END
$$;

但是,当我运行 PL/pgSQL 时,它需要几分钟才能完成,我已将其范围缩小到更新表的 for 循环。由于BIT VARYING 列上的压缩,它运行缓慢吗?有什么方法可以提高性能吗?

编辑 这是一个模拟的简化示例。这实际上是因为我有数以万计的作业正在运行,每个作业都需要报告它们的状态,每隔几秒钟更新一次。

现在,我可以对其进行规范化,并创建一个“运行状态”表来保存所有工作人员及其状态,但这将涉及存储数万行。所以,我的想法是我可以使用位图来存储客户端和状态,并且掩码会按照顺序告诉我哪些已运行,哪些已完成。前面的位将用作“错误位”,因为我不需要确切知道哪个客户端失败了,只知道存在失败。

例如,您可能有 5 名工人从事一份工作。如果它们都完成了,那么状态将为“01111”,表示所有作业都已完成,并且没有一个失败。如果 2 号工人失败,则状态为“111110”,表示出现错误,除最后一名工人外,所有工人都已完成。

因此,您可以将其视为处理大量作业状态的一种人为方式。当然我有其他想法,但即使我走那条路,对于未来,我仍然想知道如何快速更新变量,因为我很好奇。

【问题讨论】:

循环更新在性能方面几乎从来都不是一个好主意。我也不明白你为什么一遍又一遍地更新同一行 这是一个模拟的最小示例。生产中实际发生的情况是,将有数千个线程同时更新该可变位行。如果完成这样一个简单的循环需要几分钟,那么我什至不想考虑它对数千个线程的作用。 一个简单循环可能会杀死任何软件。您应该知道循环创建了 100000 行并删除了其中的 99999。你是说你将同时拥有 100000 个客户吗? 我是说我可能有 10 或 15 个客户,但他们需要更新该行数千次。为了完成这项工作,每个客户端的性能必须比我看到的更新性能好得多。 让我编辑问题,看看我是否可以提供更多上下文。 【参考方案1】:

如果您的问题确实是 TOAST 压缩,您可以简单地为该表禁用它:

ALTER TABLE my_bit_varying_test SET STORAGE EXTERNAL;

【讨论】:

【参考方案2】:

您可以尝试使用基于集合的方法来替换第二个循环。基于集合的方法通常比循环更胖。使用generate_series() 获取索引。

UPDATE my_bit_varying_test
       SET mr_bit_varying = set_bit(mr_bit_varying, gs.i, abs(gs.i % 2 - 1))
       FROM generate_series(1, 100000) gs(i)
       WHERE id = idd;

如果您还没有索引,也可以考虑在 my_bit_varying_test (id) 上创建索引。

【讨论】:

my_bit_varying_test 上的索引是否真的有助于更新性能?请参阅上面的评论线程。会有很多线程试图更新同一行。 @Brad:除非表很小,否则索引可能有助于检索需要更快更新的行,是的。当然,如果有帮助,您需要亲自尝试一下。

以上是关于如何降低 Postgres 中 set_bit 的成本?的主要内容,如果未能解决你的问题,请参考以下文章

postgres流复制环境下pg_xlog日志优雅的清理

特殊权限set_uid;set_gid;set_bit

如何修改整数中的位?

Docker - 如何在postgres容器中运行psql命令?

如何将现有的 postgres 数据文件夹复制并使用到 docker postgres 容器中

如何计算grafana / postgres中不同记录的列值之间的差异