关于 Postgres 的问题 track_commit_timestamp (pg_xact_commit_timestamp)

Questions about Postgres track_commit_timestamp (pg_xact_commit_timestamp)

我正在设计并发安全的增量聚合汇总系统,track_commit_timestamp (pg_xact_commit_timestamp) 听起来很完美。但是我一般很少找到关于它的评论,也无法从源代码中弄清楚它是如何工作的。

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

非常感谢您的帮助!


我编辑了我的问题以阐明我要完成的任务,我希望根据更新标记跟踪已处理和未处理的数据。

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% 完美,但我的目标是接近完美。因此,可能会容忍一些时钟摆动,甚至是重叠 start/end 时间的一些混乱。

这个计划有什么明显的缺陷吗?

很多问题。

供参考,源代码在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. 为什么要 VACUUMVACUUM (FULL) 更改提交时间戳?那将是一个错误。

既然我明白了你想用提交时间戳实现什么,那就说一句(我希望人们会立即提出 真正的 问题):

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

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

名人说过,过早优化是万恶之源。

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

我试图在这里简短一点,遗憾的是,我没有清楚地解释重要部分。物理优化 不是 主要关注点。事实上,commit_timestamp 系统将花费我 space,因为它是所有 table 的全局设置。我的真实 tables 将具有完整的 timestamptz(设置为 UTC)字段,我将根据这些字段编制索引和汇总。我现在(设计阶段)要解决的是方法的准确性。即,我是否一次且仅一次捕获所有事件?

我需要的是一个可靠的序号或时间线来标记我处理的highest/latest行和当前highest/latest行。这让我可以在没有 re-selecting 已经处理的行的情况下获取任何未处理的行,或者在 table 添加新行时阻止它。在某些情况下,这个想法被称为 "concurrency ID"。这是从我们项目的另一部分改编而来的草图,其中使用数字而不是时间戳是有意义的(但时间轴是一种数字线):

哦!我无法 post 图片。在这里:

https://imgur.com/iD9bn5Q

它显示了一个数字线,用于跟踪记录,分为三部分 [完成][捕获这些][拖尾]

"Done" 是 highest/latest 计数器处理的所有内容。

"Capture these" 是晚于 "Done" 且小于 table.

中的当前最大计数器的所有内容

"Tailing" 是在处理 "capture these" 行时由其他输入添加的任何新的、更高的计数器。

图片更容易看出来

所以,我有一个小工具 table,例如:

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 处理的。下一次,我从我感兴趣的 table 'scan':

中获取当前最大值
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

那是我的数轴的 "capture these" 部分。这也是我为提交时间戳数据计划的唯一用途。我们正在从各种来源推送数据,并希望它们的时间戳(调整为 UTC),而不是服务器时间戳。 (服务器时间戳是有意义的,只是在我们的数据中没有发生。)因此,提交时间戳的 唯一目的 是创建一个可靠的数字行。

如果您查看图表,它会显示同一碱基的三个不同数字线 table。 table 本身只有一个数字或时间线,number/time 系列有三个不同的 uses。所以,三 rollup_status 行,与我之前的草图 table 一致。 "scan" table 需要知道 nothing 它是如何使用的。这是此策略的 巨大 好处。您可以添加、删除和重做操作,而无需更改主 table 或其行。

我也在考虑一个 ON AFTER INSERT/UPDATE 选择触发器,带有一个转换 table 用于填充 timestamptz(设置为 UTC),例如 row_commmitted_dts。这可能是我的 B 计划,但它需要添加触发器,而且它似乎只能比实际的事务提交时间更不准确。可能差别很小,但是对于并发性的东西,小问题很快就会变成大错误。

所以,问题是我是否可以指望提交时间戳系统产生不会出现的准确结果"in the past."这就是我不能使用事务 ID 的原因。它们在事务开始时分配,但可以按任何顺序提交。 (据我了解。)因此,我的 "last processed" 和 "current maximum in file" 范围边界不起作用。我可以获得该范围,并且待处理事务可以提交数千条记录,其时间戳 比我之前记录的 "max value." 这就是为什么我在提交标记之后。

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

P.S 我在 Postgres 世界中 运行 唯一的讨论是这样的:

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

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

由于这个主题似乎很少出现在档案中,我想在继续之前添加一些细节。我在几个列表、论坛和直接交流中询问了相关问题。有几个人很友好地查看了源代码,提供了历史背景,并为我解决了这个问题。希望在这里留下一些细节可以帮助其他人走上正轨。显然,错误都是我的,欢迎更正和改进。

  • 提交时间戳是在事务的工作完成时分配的,但这与提交[=41=时不一样]. WAL writer 不会更新戳记以保持它们按时间顺序排列。

  • 因此,提交时间戳绝对不是按顺序查找更改行的可靠机制。

  • 多个时钟。 Self-adjusting 时钟。人性啊!

  • 如果您确实想要一个 in order-change 序列,可以选择逻辑解码或复制。 (几周前我通过实验尝试了逻辑复制。最酷的东西。曾经。)

  • 时间戳跟踪的成本是每个 事务 12 个字节,而不是每行。所以,还不错。 (时间戳为 8 个字节,事务 ID 为 4 个字节。)

  • 这是现有交易系统的全部内容,因此交易 ID 轮转的现实也适用于此。 (对我来说并不可怕。)参见:

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

  • 郑重声明,您可以通过参数组设置在 RDS 上启用此选项。只需将 track_commit_timestamp 设置为 1 并重新启动。 (设置是 'on' 中的 postgres.conf。)