关于 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) 听起来很完美。但是我一般很少找到关于它的评论,也无法从源代码中弄清楚它是如何工作的。
希望有人知道我的一个或多个问题的答案:
提交时间戳功能是否可能产生乱序的时间?我所追求的是一种识别自特定时间以来已更改的记录的方法,以便我可以获得任何以后的更改以进行处理。如果有相同的时间戳,我不需要它们以完美的提交顺序。
最终实现每行增加了多少字节?我看到的讨论好像是12-24字节不等。讨论了为 "just in case." 添加额外字节的问题 这是 9.5 之前的版本,太早了。
时间戳是否在内部索引?用B树?我询问容量规划原因。
我在 Whosebug 和设计讨论中看到时间戳不会无限期保留,但无法找到有关它们存储多长时间的详细信息。
关于启用 track_commit_timestamp 的性能影响有什么经验法则?我不需要所有 table 的数据,但在我需要的地方,听起来它可能工作得很好。
有什么陷阱吗?我尝试 运行 VACUUM FULL 测试 table 和 pg_xact_commit_timestamp 的 none 改变了。看起来像 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% 完美,但我的目标是接近完美。因此,可能会容忍一些时钟摆动,甚至是重叠 start/end 时间的一些混乱。
这个计划有什么明显的缺陷吗?
很多问题。
供参考,源代码在src/backend/access/transam/commit_ts.c
.
我不确定是否可以保证较晚的提交日志序列号意味着较晚的时间戳。如果系统时钟可以因时间调整而向后跳,我当然不会完全依赖它。
时间戳根本不存储在行中,而是存储在数据目录的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;
事务日志中还有关于提交时间戳的信息,因此可以恢复。
不需要索引,因为时间戳的位置是由事务号决定的(每个事务都有固定的提交时间戳位置)。见 TransactionIdToCTsPage
.
时间戳与交易号一样长,如果我理解正确的话。
我不知道开销是多少,但可能不大。
为什么要 VACUUM
或 VACUUM (FULL)
更改提交时间戳?那将是一个错误。
既然我明白了你想用提交时间戳实现什么,那就说一句(我希望人们会立即提出 真正的 问题):
提交时间戳不是适合您的工具。您无法为表达式编制索引,因为 pg_xact_commit_timestamp
不是不可变的。
选择简单明了的解决方案并添加一个额外的 timestamp with time zone
列,并使用 BEFORE
触发器将其设置为 INSERT
和 UPDATE
上的 current_timestamp
.那是可以索引的。
名人说过,过早优化是万恶之源。
Laurenz,首先,您是深入挖掘并帮助我的冠军。 谢谢。 作为背景,我已经在一些 PG 邮件列表中更详细地询问了这个问题,但得到的回复为零。我认为这是因为我的完整问题太长了。
我试图在这里简短一点,遗憾的是,我没有清楚地解释重要部分。物理优化 不是 主要关注点。事实上,commit_timestamp 系统将花费我 space,因为它是所有 table 的全局设置。我的真实 tables 将具有完整的 timestamptz(设置为 UTC)字段,我将根据这些字段编制索引和汇总。我现在(设计阶段)要解决的是方法的准确性。即,我是否一次且仅一次捕获所有事件?
我需要的是一个可靠的序号或时间线来标记我处理的highest/latest行和当前highest/latest行。这让我可以在没有 re-selecting 已经处理的行的情况下获取任何未处理的行,或者在 table 添加新行时阻止它。在某些情况下,这个想法被称为 "concurrency ID"。这是从我们项目的另一部分改编而来的草图,其中使用数字而不是时间戳是有意义的(但时间轴是一种数字线):
哦!我无法 post 图片。在这里:
它显示了一个数字线,用于跟踪记录,分为三部分
[完成][捕获这些][拖尾]
"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。)
我正在设计并发安全的增量聚合汇总系统,track_commit_timestamp (pg_xact_commit_timestamp) 听起来很完美。但是我一般很少找到关于它的评论,也无法从源代码中弄清楚它是如何工作的。
希望有人知道我的一个或多个问题的答案:
提交时间戳功能是否可能产生乱序的时间?我所追求的是一种识别自特定时间以来已更改的记录的方法,以便我可以获得任何以后的更改以进行处理。如果有相同的时间戳,我不需要它们以完美的提交顺序。
最终实现每行增加了多少字节?我看到的讨论好像是12-24字节不等。讨论了为 "just in case." 添加额外字节的问题 这是 9.5 之前的版本,太早了。
时间戳是否在内部索引?用B树?我询问容量规划原因。
我在 Whosebug 和设计讨论中看到时间戳不会无限期保留,但无法找到有关它们存储多长时间的详细信息。
关于启用 track_commit_timestamp 的性能影响有什么经验法则?我不需要所有 table 的数据,但在我需要的地方,听起来它可能工作得很好。
有什么陷阱吗?我尝试 运行 VACUUM FULL 测试 table 和 pg_xact_commit_timestamp 的 none 改变了。看起来像 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% 完美,但我的目标是接近完美。因此,可能会容忍一些时钟摆动,甚至是重叠 start/end 时间的一些混乱。
这个计划有什么明显的缺陷吗?
很多问题。
供参考,源代码在src/backend/access/transam/commit_ts.c
.
我不确定是否可以保证较晚的提交日志序列号意味着较晚的时间戳。如果系统时钟可以因时间调整而向后跳,我当然不会完全依赖它。
时间戳根本不存储在行中,而是存储在数据目录的
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;
事务日志中还有关于提交时间戳的信息,因此可以恢复。
不需要索引,因为时间戳的位置是由事务号决定的(每个事务都有固定的提交时间戳位置)。见
TransactionIdToCTsPage
.时间戳与交易号一样长,如果我理解正确的话。
我不知道开销是多少,但可能不大。
为什么要
VACUUM
或VACUUM (FULL)
更改提交时间戳?那将是一个错误。
既然我明白了你想用提交时间戳实现什么,那就说一句(我希望人们会立即提出 真正的 问题):
提交时间戳不是适合您的工具。您无法为表达式编制索引,因为 pg_xact_commit_timestamp
不是不可变的。
选择简单明了的解决方案并添加一个额外的 timestamp with time zone
列,并使用 BEFORE
触发器将其设置为 INSERT
和 UPDATE
上的 current_timestamp
.那是可以索引的。
名人说过,过早优化是万恶之源。
Laurenz,首先,您是深入挖掘并帮助我的冠军。 谢谢。 作为背景,我已经在一些 PG 邮件列表中更详细地询问了这个问题,但得到的回复为零。我认为这是因为我的完整问题太长了。
我试图在这里简短一点,遗憾的是,我没有清楚地解释重要部分。物理优化 不是 主要关注点。事实上,commit_timestamp 系统将花费我 space,因为它是所有 table 的全局设置。我的真实 tables 将具有完整的 timestamptz(设置为 UTC)字段,我将根据这些字段编制索引和汇总。我现在(设计阶段)要解决的是方法的准确性。即,我是否一次且仅一次捕获所有事件?
我需要的是一个可靠的序号或时间线来标记我处理的highest/latest行和当前highest/latest行。这让我可以在没有 re-selecting 已经处理的行的情况下获取任何未处理的行,或者在 table 添加新行时阻止它。在某些情况下,这个想法被称为 "concurrency ID"。这是从我们项目的另一部分改编而来的草图,其中使用数字而不是时间戳是有意义的(但时间轴是一种数字线):
哦!我无法 post 图片。在这里:
它显示了一个数字线,用于跟踪记录,分为三部分 [完成][捕获这些][拖尾]
"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。)