有关Postgres track_commit_timestamp(pg_xact_commit_timestamp)

时间:2019-07-09 22:10:58

标签: postgresql transactions rollup

我正在为一个并发安全的增量聚合汇总系统进行设计,track_commit_timestamp(pg_xact_commit_timestamp)听起来很完美。但是我发现对此的评论很少,并且无法从源代码中详细了解它的工作原理。

希望有人知道我的一个或多个问题的答案:

  • 提交时间戳功能是否有可能产生混乱的时间?我所追求的是一种识别自特定时间以来已更改的记录的方法,以便我可以获取以后的任何更改以进行处理。如果有相同的时间戳,则不需要完美的提交顺序。

  • 在最终实现中,每行添加了多少个字节?我看到的讨论似乎是12-24个字节。讨论过添加额外的字节以防万一。这是9.5之前的版本,所以是一个世界之前。

  • 时间戳是否在内部建立索引?有B树吗?我要求进行容量规划。

  • 我已经在StackOverflow和设计讨论中看到,时间戳没有被无限期保留,但是找不到确切的存储时间的详细信息。

  • 是否有任何经验法则对启用track_commit_timestamp的性能产生影响?我不需要所有表上的数据,但听起来好像很正常。

  • 有陷阱吗?我尝试在测试表上运行VACUUM FULL,但pg_xact_commit_timestamp均未更改。像VACUUM这样的物理操作似乎不应该改变任何东西,但是很容易有些我没有想到的东西。而且,老实说,我的快速VACUUM测试甚至都没有任何意义。

非常感谢您的协助!


我已经编辑了问题,以阐明我要完成的工作,我希望基于更新戳记跟踪已处理和未处理的数据。

select max(pg_xact_commit_timestamp(xmin)) from scan;--   2019-07-07 20:46:14.694288+10

update scan set quantity = 5 where quantity = 1; --       Change some data.

select max(pg_xact_commit_timestamp(xmin)) from scan; --  2019-07-10 09:38:17.920294+10

-- Find the changed row(s):
select * 
  from scan 
 where pg_xact_commit_timestamp(xmin) > '2019-07-07 20:46:14.694288+10'; 

这个想法是对行进行增量和定期汇总。因此,

-跟踪上一个汇总的时间戳。 -等待5分钟(或其他时间)。 -查找当前的最大提交时间戳。 -搜索提交时间戳介于最后处理的时间戳和最大值之间的行。 -将它们卷起来。

仅事务ID不能工作,因为它们很容易乱序提交。而且这个时间戳系统不必一定是100%完美的,但是我的目标是非常接近完美的东西。因此,可能会有一点时钟摆动,甚至在开始/结束时间重叠的情况下甚至有些混乱。

该计划是否存在明显缺陷?

3 个答案:

答案 0 :(得分:1)

很多问题。

作为参考,源代码位于src/backend/access/transam/commit_ts.c中。

  1. 我不确定是否可以保证以后的提交日志序列号意味着以后的时间戳。如果系统时钟由于时间调整而向后跳,我肯定不会完全依靠它。

  2. 时间戳记根本不存储在行中,而是存储在数据目录的pg_commit_ts子目录中。每条记录占用10个字节:

    /*
     * We need 8+2 bytes per xact.  Note that enlarging this struct might mean
     * the largest possible file name is more than 5 chars long; see
     * SlruScanDirectory.
     */
    typedef struct CommitTimestampEntry
    {
        TimestampTz time;
        RepOriginId nodeid;
    } CommitTimestampEntry;
    

    在事务日志中还有关于提交时间戳的信息,因此可以恢复。

  3. 不需要索引,因为时间戳的位置由事务号确定(每个事务的提交时间戳都有固定的位置)。参见TransactionIdToCTsPage

  4. 如果我正确理解代码,
  5. 时间戳记将与事务编号一样长。

  6. 我无法确定开销是多少,但可能不算太大。

  7. 为什么VACUUMVACUUM (FULL)更改提交时间戳?那将是一个错误。

现在,我了解了您要使用提交时间戳实现的目标,一句话(希望人们立即提出 real 问题):

提交时间戳不是您合适的工具。您无法为表达式建立索引,因为pg_xact_commit_timestamp不是不可变的。

选择简单明了的解决方案,并使用timestamp with time zone触发器添加一个额外的BEFORE列,并将其设置为current_timestampINSERT上的UPDATE。可以索引。

一个著名的人说过早的优化是万恶之源。

答案 1 :(得分:0)

Laurenz,首先,您是深入研究和帮助我的冠军。 谢谢。作为背景知识,我在一些PG邮件列表中更详细地询问了这个问题,并得到了零答复。我认为这是因为我的完整问题太长了。

我试图在这里简短一些,可悲的是,我并没有清楚地说明重要部分。物理优化不是驱动因素。实际上,由于这是所有表的全局设置,因此commit_timestamp系统将占用我的空间。我的真实表将具有完整的timestamptz(设置为UTC)字段,我将对其进行索引和汇总。我现在要解决的问题(设计阶段)是该方法的准确性。即,我是否一次捕获所有事件?

我需要一个可靠的序号或时间线来标记我处理过的最高/最新行和当前最高/最新行。这样一来,我无需重新选择已处理的行或在添加新行时阻止该表,就可以抓住所有尚未处理的行。在某些情况下,此想法称为“并发ID”。这是从项目的另一部分改编而成的草图,在该部分中可以使用数字代替时间戳(但时间线是数字线的一种):

D'哦!我无法发布图片。在这里:

https://imgur.com/iD9bn5Q

它显示了一条用于跟踪记录的数字行,分为三部分 [完成] [捕获这些] [尾巴]

“完成”是处理的最高/最新计数器中的所有内容。

“捕获这些”是晚于“完成”且小于表中当前最大计数器的所有内容。

“尾部”是在处理“捕获这些”行时由其他输入添加的任何新的更高的计数器。

更容易在图片中看到。

因此,我有一个小的实用程序表,例如:

CREATE TABLE "rollup_status" (
    "id" uuid NOT NULL DEFAULT extensions.gen_random_uuid(), -- We use UUIDs, not necessary here, but it's what we use. 
    "rollup_name" text NOT NULL DEFAULT false,               
    "last_processed_dts" timestamptz NOT NULL DEFAULT NULL); -- Marks the last timestamp processed.

现在想象一个条目:

rollup_name         last_processed_dts
error_name_counts   2018-09-26 02:23:00

因此,我的数字行(时间轴,在提交时间戳的情况下)是从0日期到2018-09-26 02:23:00进行处理的。下次,我从感兴趣的表中获取当前最大值,即“扫描”:

select max(pg_xact_commit_timestamp(xmin)) from scan; -- Pretend that it's 2019-07-07 25:00:00.0000000+10

此值成为我搜索的上限,并且成为rollup_status.last_processed_dts的新值。

-- Find the changed row(s):
select * 
  from scan 
 where pg_xact_commit_timestamp(xmin) >  '2019-07-07 20:46:14.694288+10' and
       pg_xact_commit_timestamp(xmin) <= '2019-07-07 25:00:00.0000000+10

这是我的号码行的“捕获这些”部分。这也是我计划的提交时间戳数据的唯一用途。我们正在从各种来源推送数据,并希望它们的时间戳(已调整为UTC),而不是服务器时间戳。 (服务器时间戳很有意义,就我们的数据而言,它们只是没有发生。)因此,提交时间戳的唯一目的是创建可靠的数字行。

如果您查看图表,它会为同一基表显示三个不同的数字线。表格本身只有一个数字或时间轴,该数字/时间序列有三种不同的用途。因此,有3个rollup_status行,与之前的草图表一起使用。 “扫描”表无需了解任何使用方式。这是该策略的巨大优势。您可以添加,删除和重做操作,而无需完全更改主表或其行。

我还在考虑使用带有过渡表的ON AFTER INSERT / UPDATE选择触发器,该过渡表用于填充timestamptz(设置为UTC),例如row_commmitted_dts。那可能是我的计划B,但是它需要添加触发器,并且看起来可能只比实际事务提交时间精确一些。可能差别不大,但是有了并发性的东西,几乎没有什么问题会很快变成大的错误。

所以,问题是我是否可以依靠提交时间戳系统来产生不会在“过去”出现的准确结果。这就是为什么我不能使用交易ID。它们是在交易开始时分配的,但可以按任何顺序提交。 (据我了解。)因此,“最后处理”和“文件中的当前最大值”的范围边界不起作用。我可以达到该范围,并且挂起的事务可以提交比以前记录的“最大值”更早时间戳为 的数千条记录。这就是为什么我要提交邮票。

再次感谢您的任何帮助或建议。我非常感谢。

P.S我在Postgres世界中唯一遇到过这样的讨论是在这里:

Postgres和Citus上的可伸缩增量数据聚合 https://www.citusdata.com/blog/2018/06/14/scalable-incremental-data-aggregation/

他们以这种方式使用bigserial计数器,但据我了解,这仅适用于INSERT,不适用于UPDATE。而且,老实说,我对Postgres事务和序列了解不多,无法考虑并发行为。

答案 2 :(得分:0)

由于该主题似乎未在档案中出现得太多,因此我想在继续之前添加一些细节。我在几个列表,论坛上以及通过直接沟通询问了相关问题。有几个人很友善地审查了源代码,提供了历史背景,并为我清除了这些内容。希望在这里保留一些细节可以帮助其他人。显然,错误都是我的错误,更不用说更正和改进了。

  • 提交事务的时间戳是在事务的工作完成时分配的,但与提交 的情况不同。 WAL作家不会更新邮票以使其按时间顺序排列。
  • 因此,提交时间戳绝对不是可靠的机制,无法按顺序查找更改行。
  • 多个时钟。自调时钟。哦,人类!
  • 如果您确实希望按顺序更改顺序,则可以选择逻辑解码或复制。 (几周前,我通过实验尝试了逻辑复制。最酷。一切。)
  • 时间戳跟踪的成本是每个事务 12个字节,而不是每行12个字节。所以,还不错。 (时间戳为8个字节,事务ID为4个字节。)
  • 这都是现有交易系统的全部内容,因此交易ID滚动的现实情况也适用于此。 (以我的情况而言,并不可怕。)请参阅:

    https://www.postgresql.org/docs/current/routine-vacuuming.html

  • 为进行记录,您可以通过参数组设置在RDS上启用此选项。只需将track_commit_timestamp设置为1并重新启动即可。 (postgres.conf中的设置为“ on”。)