应用程序中导致 postgresql 膨胀的典型错误。 安德烈·萨尔尼科夫

我建议你阅读 Andrey Salnikov 2016 年初的报告文字记录“应用程序中导致 postgresql 膨胀的典型错误”

在本报告中,我将分析应用程序在设计和编写应用程序代码阶段出现的主要错误。 我将只考虑那些导致 Postgresql 膨胀的错误。 通常,这是整个系统性能结束的开始,尽管最初没有可见的先决条件。

应用程序中导致 postgresql 膨胀的典型错误。 安德烈·萨尔尼科夫

很高兴欢迎大家! 这份报告不像我同事的上一份报告那么技术性。 本报告主要针对后端系统开发人员,因为我们有相当多的客户。 他们都犯同样的错误。 我会告诉你关于他们的事。 我将解释这些错误会导致什么致命和糟糕的事情。

应用程序中导致 postgresql 膨胀的典型错误。 安德烈·萨尔尼科夫

为什么会犯错误? 这样做有两个原因:随机的,也许它会起作用,并且由于不了解数据库和应用程序之间以及数据库本身之间发生的某些机制。

我将给你举三个例子,并通过可怕的图片来说明事情变得多么糟糕。 我将简要介绍一下那里发生的机制。 以及如何处理它们,何时发生,以及采用什么预防方法来防止错误发生。 我将向您介绍辅助工具并提供有用的链接。

应用程序中导致 postgresql 膨胀的典型错误。 安德烈·萨尔尼科夫

我使用了一个测试数据库,其中有两个表。 一个板块包含客户账户,另一个板块包含这些账户上的交易。 我们会定期更新这些账户的余额。

应用程序中导致 postgresql 膨胀的典型错误。 安德烈·萨尔尼科夫

盘的初始数据:很小,2MB。 数据库和标志的响应时间也非常好。 根据铭牌,负载相当不错——每秒 2 次操作。

应用程序中导致 postgresql 膨胀的典型错误。 安德烈·萨尔尼科夫

通过这份报告,我将向您展示图表,以便您可以清楚地了解正在发生的事情。 总会有两张带有图表的幻灯片。 第一张幻灯片介绍了服务器上通常发生的情况。

在这种情况下,我们看到我们确实有一个小标志。 该索引很小,只有 2 MB。 这是左侧第一张图。

服务器的平均响应时间也稳定且短。 这是右上方的图表。

左下图显示最长的交易。 我们看到交易很快完成。 而且自动真空吸尘器在这里还不起作用,因为这是一个启动测试。 它将继续发挥作用并对我们有用。

应用程序中导致 postgresql 膨胀的典型错误。 安德烈·萨尔尼科夫

第二张幻灯片将始终专用于正在测试的板。 在这种情况下,我们会不断更新客户的账户余额。 我们看到更新操作的平均响应时间非常好,不到一毫秒。 我们看到处理器资源(这是右上图)也被均匀消耗并且相当小。

右下图显示了在更新之前我们为了搜索所需的行而使用了多少操作和磁盘内存。 而根据符号的运算次数是每秒2次,正如我一开始所说的。

应用程序中导致 postgresql 膨胀的典型错误。 安德烈·萨尔尼科夫

现在我们遇到了悲剧。 由于某种原因,有一笔长期被遗忘的交易。 原因通常都很平庸:

  • 最常见的情况之一是我们开始在应用程序代码中访问外部服务。 而这项服务并没有回答我们。 也就是说,我们打开了一个事务,在数据库中进行了更改,然后从应用程序读取邮件或转到我们基础设施内的另一个服务,但由于某种原因,它没有响应我们。 而我们的session就陷入了一个不知道什么时候能解决的状态。
  • 第二种情况是当我们的代码由于某种原因发生异常时。 在例外情况下,我们没有处理交易的结束。 我们最终以悬而未决的交易结束了。
  • 最后一个也是一个相当常见的情况。 这是低质量的代码。 某些框架会打开事务。 它挂起,您可能在应用程序中不知道它已挂起。

这些事情会导致什么结果呢?

以至于我们的表和索引开始急剧膨胀。 这与膨胀效果完全相同。 对于数据库来说,这将意味着数据库响应时间将急剧增加,数据库服务器的负载将增加。 结果,我们的应用程序将受到影响。 因为如果您在代码中花费 10 毫秒来处理对数据库的请求,在逻辑上花费 10 毫秒,那么您的函数需要 20 毫秒才能完成。 现在你的处境将变得非常悲伤。

让我们看看会发生什么。 左下图显示我们有一笔长交易。 如果我们查看左上图,我们会发现表的大小突然从 300 兆字节跃升至 XNUMX 兆字节。 同时,表中的数据量没有变化,即存在相当多的垃圾。

应用程序中导致 postgresql 膨胀的典型错误。 安德烈·萨尔尼科夫

关于平均服务器响应时间的总体情况也发生了几个数量级的变化。 也就是说,服务器上的所有请求开始完全下降。 同时,内部Postgres进程以autovacuum的形式启动,这些进程正在尝试做某事并消耗资源。

应用程序中导致 postgresql 膨胀的典型错误。 安德烈·萨尔尼科夫

我们的标志怎么了? 相同。 根据该标志,我们的平均响应时间跃升了几个数量级。 具体在消耗的资源方面,我们看到处理器的负载大大增加。 这是右上方的图表。 它的增加是因为处理器必须对一堆无用的行进行排序以寻找所需的行。 这是右下图。 结果,我们每秒的调用次数开始显着下降,因为数据库没有时间处理相同数量的请求。

应用程序中导致 postgresql 膨胀的典型错误。 安德烈·萨尔尼科夫

我们需要恢复生机。 我们上网发现长时间的交易会导致问题。 我们找到并终止这笔交易。 对我们来说一切都变得正常了。 一切都按其应有的方式进行。

我们平静下来,但过了一会儿我们开始注意到该应用程序的工作方式与紧急情况之前不同。 请求的处理速度仍然较慢,而且明显较慢。 特别是在我的示例中,速度慢了一倍半到两倍。 服务器上的负载也比事故发生前更高。

应用程序中导致 postgresql 膨胀的典型错误。 安德烈·萨尔尼科夫

还有一个问题:“此时基地发生了什么?” 并且底座会出现以下情况。 交易图表上可以看到已经停止了,确实没有长期交易。 但事故期间该标志的尺寸致命地增大了。 从那时起,它们就没有减少。 基地的平均停留时间已经稳定。 答案似乎正在以我们可以接受的速度充分到来。 autovacuum 变得更加活跃,并开始对标志执行某些操作,因为它需要筛选更多数据。

应用程序中导致 postgresql 膨胀的典型错误。 安德烈·萨尔尼科夫

具体来说,根据我们更改余额的帐户测试板:请求的响应时间似乎已恢复正常。 但实际上它高出一倍半。

并且从处理器上的负载来看,处理器上的负载还没有恢复到崩溃之前所需的值。 原因就在右下图中。 可以看到那里正在搜索一定量的内存。 也就是说,为了找到所需的行,我们在对无用数据进行排序的同时浪费了数据库服务器的资源。 每秒的交易数量已经稳定。

总体来说不错,但情况比以前更糟糕了。 由于我们的应用程序与该数据库一起工作,因此清除了数据库降级。

应用程序中导致 postgresql 膨胀的典型错误。 安德烈·萨尔尼科夫

为了了解其中发生的情况,如果您没有看过之前的报告,现在让我们了解一些理论。 关于内部过程的理论。 为什么要使用汽车吸尘器以及它的作用是什么?

从字面上简短地理解。 在某个时间点我们有一张桌子。 我们在表中有行。 这些线路可以是活跃的、有活力的,也是我们现在所需要的。 它们在图片中被标记为绿色。 还有一些截止日期已经制定、更新,并且出现了新的条目。 并且它们被标记为数据库不再对它们感兴趣。 但由于 Postgres 的功能,它们出现在表中。

为什么需要车载吸尘器? 在某个时刻,autovacuum 出现,访问数据库并询问它:“请给我数据库中当前打开的最旧事务的 ID。” 数据库返回该 id。 autovacuum 依靠它对表中的行进行排序。 如果他看到某些行被更旧的事务更改,那么他有权将它们标记为我们将来可以通过在其中写入新数据来重用的行。 这是一个后台进程。

这时候我们继续操作数据库,继续对表进行一些修改。 在这些可以重用的行上,我们写入新数据。 因此我们得到了一个循环,即总是出现一些死的旧行,而不是我们写下我们需要的新行。 这是 PostgreSQL 工作的正常状态。

应用程序中导致 postgresql 膨胀的典型错误。 安德烈·萨尔尼科夫

事故期间发生了什么? 这个过程是如何发生的?

我们在某些情况下有一个标志,有些是实时的,有些是截止日期。 汽车吸尘器已经到了。 他向数据库询问我们最旧的交易是什么以及它的 id 是什么。 我收到这个 ID,可能是几个小时前,也许是十分钟前。 这取决于数据库的负载有多大。 他开始寻找可以标记为重复使用的线路。 我在我们的表中没有找到这样的行。

但此时我们继续处理该表。 我们在其中做一些事情,更新它,更改数据。 这个时候数据库应该做什么呢? 她别无选择,只能在现有表格的末尾添加新行。 因此我们的桌子尺寸开始膨胀。

事实上,我们需要绿线才能发挥作用。 但在这样的问题中,事实证明,整个表中绿线的百分比非常低。

当我们执行查询时,数据库必须遍历所有行:红色和绿色,才能找到所需的行。 用无用数据使表膨胀的效果称为“膨胀”,这也会耗尽我们的磁盘空间。 还记得吗,原来是 2 MB,后来变成了 300 MB? 现在将兆字节更改为千兆字节,您将很快失去所有磁盘资源。

应用程序中导致 postgresql 膨胀的典型错误。 安德烈·萨尔尼科夫

可能会给我们带来什么后果?

  • 在我的示例中,表和索引增长了 150 倍。 我们的一些客户在磁盘空间不足时就遇到了更多致命案例。
  • 表本身的大小永远不会减少。 在某些情况下,如果只有死行,Autovacuum 可能会切断表的尾部。 但由于不断旋转,一条绿线可能会冻结在末尾并且不会更新,而所有其他绿线将被写在图板开头的某个位置。 但这是一种不太可能发生的情况,以至于你的桌子本身的尺寸会缩小,所以你不应该指望它。
  • 数据库需要对一大堆无用的行进行排序。 我们浪费磁盘资源、处理器资源和电力。
  • 这直接影响我们的应用程序,因为如果一开始我们在请求上花费了 10 毫秒,在代码上花费了 10 毫秒,那么在崩溃期间我们开始在请求上花费 10 秒,在代码上花费 20 毫秒,即一个顺序应用程序性能大幅下降。 当事故解决后,我们开始在一个请求上花费 10 毫秒,在一个代码上花费 XNUMX 毫秒。 这意味着我们的生产力仍然下降了一倍半。 这一切都是因为一笔交易被冻结,也许是我们的错。
  • 还有一个问题:“我们怎样才能恢复一切?”以便我们一切都好,并且请求会像事故发生前一样快地收到。

应用程序中导致 postgresql 膨胀的典型错误。 安德烈·萨尔尼科夫

为此,需要执行一定的工作周期。

首先我们需要找到有问题的臃肿的表。 我们了解到,在某些表中记录较为活跃,而在另一些表中则不太活跃。 为此,我们使用扩展 pgstattuple。 通过安装此扩展,您可以编写查询来帮助您查找非常臃肿的表。

找到这些表后,您需要压缩它们。 已经有这方面的工具了。 在我们公司,我们使用三种工具。 第一个是内置 VACUUM FULL。 他残忍、严厉、无情,但有时却非常有用。 pg_repack и pg紧凑表 - 这些是用于压缩表的第三方实用程序。 他们更仔细地对待数据库。

它们的使用取决于什么对您来说更方便。 但我会在最后告诉你这一点。 最主要的是有三个工具。 有很多可供选择。

当我们纠正了一切并确保一切正常后,我们必须知道如何防止将来出现这种情况:

  • 它可以很容易地预防。 您需要监控主服务器上的会话持续时间。 处于事务状态空闲状态的特别危险的会话。 这些人刚刚打开一个交易,做了一些事情然后离开,或者只是挂起,迷失在代码中。
  • 对于您来说,作为开发人员,在出现这些情况时测试您的代码非常重要。 这并不难做到。 这将是一个有用的检查。 您将避免大量与长事务相关的“幼稚”问题。

应用程序中导致 postgresql 膨胀的典型错误。 安德烈·萨尔尼科夫

在这些图中,我想向您展示在本例中使用 VACUUM FULL 进行符号检查后,数据库的符号和行为如何发生变化。 这不是我的生产。

表大小立即恢复到几兆字节的正常运行状态。 这并没有太大影响服务器的平均响应时间。

应用程序中导致 postgresql 膨胀的典型错误。 安德烈·萨尔尼科夫

但特别是对于我们更新帐户余额的测试标志,我们发现更新标志中数据的请求的平均响应时间已缩短至紧急情况前的水平。 处理器完成此请求所消耗的资源也下降到崩溃前的水平。 右下图显示,现在我们立即准确地找到了所需的行,而无需遍历表格压缩之前存在的一堆死行。 平均请求时间保持在大致相同的水平。 但我的硬件出现了错误。

应用程序中导致 postgresql 膨胀的典型错误。 安德烈·萨尔尼科夫

第一个故事到这里就结束了。 这是最常见的。 每个人都会遇到这种情况,无论客户的经验如何以及程序员的资格如何。 这种事迟早会发生。

第二个故事,我们分配负载并优化服务器资源

应用程序中导致 postgresql 膨胀的典型错误。 安德烈·萨尔尼科夫

  • 我们已经长大了,成为了认真的人。 我们知道我们有一个副本,这对我们平衡负载很有好处:写入主服务器,然后从副本读取。 通常当我们要准备一些报告或ETL时,就会出现这种情况。 企业界对此非常高兴。 他确实想要包含大量复杂分析的各种报告。
  • 报告需要花费很多小时,因为复杂的分析无法在毫秒内完成。 我们像勇敢的人一样编写代码。 在插入应用程序中,我们在主服务器上进行记录,并在副本服务器上执行报告。
  • 分配负载。
  • 一切都很完美。 我们很棒。

应用程序中导致 postgresql 膨胀的典型错误。 安德烈·萨尔尼科夫

而这种情况是什么样的呢? 具体来说,在这些图表上,我还添加了来自副本的事务持续时间作为事务持续时间。 所有其他图表仅指主服务器。

这时,我的报告板已经长大了。 还有更多。 我们看到平均服务器响应时间是稳定的。 我们看到在副本上有一个长时间运行的事务,运行了 2 小时。 我们看到 autovacuum 安静地运行,它处理截止线。 我们一切都很好。

应用程序中导致 postgresql 膨胀的典型错误。 安德烈·萨尔尼科夫

具体来说,根据测试的板块,我们继续更新那里的账户余额。 而且我们还有稳定的请求响应时间、稳定的资源消耗。 我们一切都很好。

应用程序中导致 postgresql 膨胀的典型错误。 安德烈·萨尔尼科夫

一切都很好,直到这些报告由于复制冲突而开始反击。 他们会定期还击。

我们上网并开始阅读为什么会发生这种情况。 我们找到了解决方案。

第一个解决方案是增加复制延迟。 我们知道我们的报告持续 3 个小时。 我们将复制延迟设置为 3 小时。 我们正在推出一切,但仍然存在报告有时被取消的问题。

我们希望一切都完美。 我们爬得更远。 我们在互联网上发现了一个很酷的设置——hot_standby_feedback。 让我们打开它。 Hot_standby_feedback 允许我们阻止 Master 上的 autovacuum。 这样,我们就完全摆脱了复制冲突。 报告对我们来说一切都很顺利。

应用程序中导致 postgresql 膨胀的典型错误。 安德烈·萨尔尼科夫

而此时Master服务器发生了什么? 我们的主服务器完全陷入困境。 现在,当我启用这两个设置时,我们可以看到图表。 我们看到副本上的会话以某种方式开始影响主服务器上的情况。 她确实有效果,因为她暂停了自动清理,从而清除了死线。 我们的桌子尺寸又猛增了。 整个数据库的平均查询执行时间也猛增。 自动吸尘器收紧了一点。

应用程序中导致 postgresql 膨胀的典型错误。 安德烈·萨尔尼科夫

具体来说,从我们的板块上,我们看到上面的数据更新也是一跃而上。 CPU消耗同样大幅增加。 我们再次经历大量死气沉沉、无用的线路。 而且这个标志的响应时间和交易数量都下降了。

应用程序中导致 postgresql 膨胀的典型错误。 安德烈·萨尔尼科夫

如果我们不知道我之前在说什么,那会是什么样子?

  • 我们开始寻找问题。 如果我们在第一部分遇到问题,我们知道这可能是由于长时间的事务造成的,然后去找Master。 我们在Master上遇到了问题。 给他做香肠。 它开始升温,其平均负载约为一百。
  • 那里的请求很慢,但我们没有看到任何长时间运行的事务。 我们不明白这是怎么回事。 我们不知道该去哪里寻找。
  • 我们检查服务器设备。 也许我们的袭击失败了。 也许我们的记忆棒烧坏了。 是的,任何事情都可能发生。 但不,服务器是新的,一切正常。
  • 每个人都在奔跑:管理员、开发人员和主管。 没有任何帮助。
  • 在某个时刻,一切突然开始自我纠正。

应用程序中导致 postgresql 膨胀的典型错误。 安德烈·萨尔尼科夫

此时,我们副本上的请求已处理并离开。 我们收到了报告。 生意还是很愉快的。 正如你所看到的,我们的标志又变大了,并且不会缩小。 在带有会话的图表上,我从副本中留下了这个长事务的一部分,以便您可以估计情况稳定下来需要多长时间。

会议结束了。 过了一段时间,服务器才或多或少地恢复正常。 并且Master服务器上请求的平均响应时间恢复正常。 因为,最后,自动清理有机会清理并标记这些死线。 他开始做他的工作。 他做事的速度有多快,我们就能很快地恢复秩序。

应用程序中导致 postgresql 膨胀的典型错误。 安德烈·萨尔尼科夫

根据测试的平板电脑,我们在更新帐户余额时看到完全相同的图片。 平均账户更新时间也逐渐正常化。 处理器消耗的资源也减少了。 并且每秒的交易数量恢复正常。 但我们再次恢复正常,与事故发生前不同。

应用程序中导致 postgresql 膨胀的典型错误。 安德烈·萨尔尼科夫

无论如何,我们都会遇到性能下降,就像第一种情况一样,下降一倍半到两倍,有时甚至更多。

我们似乎一切都做对了。 分配负载。 设备并没有闲着。 我们按照自己的想法划分了请求,但结果仍然很糟糕。

  • 不启用hot_standby_feedback? 是的,如果没有特别充分的理由,不建议打开它。 因为这种扭曲直接影响主服务器并暂停那里的 autovacuum 操作。 通过在某些副本上启用它并忘记它,您可能会杀死主服务器并导致应用程序出现大问题。
  • 增加 max_standby_streaming_delay? 是的,对于报道来说这是真的。 如果您有一个三小时的报告并且您不希望它由于复制冲突而崩溃,那么只需增加延迟即可。 长期报告永远不需要现在已到达数据库的数据。 如果您拥有它三个小时,那么您正在运行它一些旧的数据周期。 而对于你来说,无论是延迟三小时还是延迟六小时都没有什么区别,但你会持续收到报告,不会出现任何掉落的问题。
  • 当然,您需要控制副本上的长会话,尤其是当您决定在副本上启用 hot_standby_feedback 时。 因为任何事情都有可能发生。 我们将此副本提供给开发人员,以便他可以测试查询。 他写了一个疯狂的请求。 他启动了它,然后去喝茶,我们就得到了既定的大师。 或者我们可能放错了应用程序。 情况是多种多样的。 副本上的会话必须像主服务器上一样仔细监控。
  • 如果您对副本有快速且长时间的查询,那么在这种情况下最好将它们拆分以分配负载。 这是streaming_delay 的链接。 对于快速的副本,请拥有一个复制延迟较小的副本。 对于长时间运行的报告请求,请拥有可能延迟 6 小时或一天的副本。 这是完全正常的情况。

我们以同样的方式消除后果:

  • 我们发现桌子臃肿。
  • 我们用适合我们的最方便的工具来压缩它。

第二个故事到这里就结束了。 我们继续讲第三个故事。

应用程序中导致 postgresql 膨胀的典型错误。 安德烈·萨尔尼科夫

对于我们进行迁移的人来说也很常见。

应用程序中导致 postgresql 膨胀的典型错误。 安德烈·萨尔尼科夫

  • 任何软件产品都在增长。 对它的要求正在发生变化。 无论如何,我们都想发展。 碰巧我们需要更新表中的数据,即针对我们在开发过程中引入的新功能进行迁移更新。
  • 旧的数据格式并不令人满意。 假设我们现在转向第二个表,其中我有这些帐户的交易。 假设它们以卢布为单位,我们决定提高准确性并以戈比为单位。 为此,我们需要进行更新:将字段与交易金额乘以一百。
  • 在当今世界,我们使用自动化数据库版本控制工具。 比方说 液化石油气。 我们在那里登记我们的移民。 我们在我们的测试基地上对其进行测试。 一切安好。 更新正在进行中。 它会阻碍工作一段时间,但我们会得到更新的数据。 我们可以在此基础上推出新功能。 一切都经过测试和检查。 一切都得到了证实。
  • 我们进行了计划工作并进行了迁移。

应用程序中导致 postgresql 膨胀的典型错误。 安德烈·萨尔尼科夫

这是呈现在您面前的迁移和更新。 由于这些是我的帐户交易,因此该盘为 15 GB。 由于我们更新了每一行,因此我们通过更新将表的大小加倍,因为我们重写了每一行。

应用程序中导致 postgresql 膨胀的典型错误。 安德烈·萨尔尼科夫

在迁移过程中,我们无法对此板执行任何操作,因为对它的所有请求都已排队并等待此更新完成。 但在这里我想提请您注意垂直轴上的数字。 也就是说,我们迁移前的平均请求时间约为 5 毫秒,并且处理器负载、读取磁盘内存的块操作数小于 7,5。

应用程序中导致 postgresql 膨胀的典型错误。 安德烈·萨尔尼科夫

我们进行了迁移,但又遇到了问题。

迁移成功,但是:

  • 旧功能现在需要更长的时间才能完成。
  • 桌子又变大了。
  • 服务器上的负载再次变得比以前更大。
  • 当然,我们仍在修补那些运行良好的功能,我们对其进行了一些改进。

这又是膨胀,再次毁了我们的生活。

应用程序中导致 postgresql 膨胀的典型错误。 安德烈·萨尔尼科夫

在这里,我演示了该表与前两个案例一样,不会恢复到之前的大小。 平均服务器负载似乎足够。

应用程序中导致 postgresql 膨胀的典型错误。 安德烈·萨尔尼科夫

如果我们转向有帐户的表,我们会看到该表的平均请求时间增加了一倍。 处理器的负载和内存中排序的行数跃升至 7,5 以上,但较低。 在处理器的情况下,它跳跃了 2 倍,在块操作的情况下,跳跃了 1,5 倍,也就是说,我们的服务器性能下降了。 结果是——我们的应用程序的性能下降。 与此同时,来电数量大致保持在同一水平。

应用程序中导致 postgresql 膨胀的典型错误。 安德烈·萨尔尼科夫

这里最主要的是了解如何正确地进行此类迁移。 他们需要完成。 我们非常一致地进行这些迁移。

  • 如此大规模的迁移不会自动发生。 它们必须始终处于控制之下。
  • 需要由有知识的人进行监督。 如果您的团队中有 DBA,那么就让 DBA 来做。 这是他的工作。 如果没有,那就让最有经验的人来做,他们知道如何使用数据库。
  • 一个新的数据库模式,即使我们更新一列,我们总是分阶段准备,即在新版本应用程序推出之前提前准备:
  • 添加了新字段,我们将在其中记录更新的数据。
  • 我们将数据分小部分从旧字段传输到新字段。 我们为什么要这样做呢? 首先,我们始终控制这个过程的过程。 我们知道我们已经转移了这么多批次,还剩下这么多批次。
  • 第二个积极的影响是,在每个这样的批次之间,我们关闭事务,打开一个新事务,这允许自动清理根据盘子工作,标记重复使用的截止日期。
  • 对于应用程序运行时将出现的行(我们仍然运行旧应用程序),我们添加一个触发器,将新值写入新字段。 在我们的例子中,这是旧值乘以一百。
  • 如果我们非常顽固并且想要相同的字段,那么在完成所有迁移并推出新版本的应用程序之前,我们只需重命名这些字段即可。 旧的领域被赋予了一些发明的名称,新的领域被重新命名为旧的领域。
  • 之后我们才推出该应用程序的新版本。

同时我们不会变得臃肿,也不会在性能方面受到影响。

第三个故事到这里就结束了。

应用程序中导致 postgresql 膨胀的典型错误。 安德烈·萨尔尼科夫

https://github.com/dataegret/pg-utils/blob/master/sql/table_bloat.sql

https://github.com/dataegret/pg-utils/blob/master/sql/table_bloat_approx.sql

现在详细介绍一下我在第一个故事中提到的工具。

在搜索 bloat 之前,必须安装扩展 pgstattuple.

为了让您不必提出查询,我们已经在工作中编写了这些查询。 你可以使用它们。 这里有两个请求。

  • 第一个需要相当长的时间才能工作,但它会向您显示表中的准确膨胀值。
  • 第二种工作速度更快,当你需要根据表格快速评估是否有膨胀时,非常有效。 您还应该了解 Postgres 表中始终存在膨胀。 这是其MVCC模型的一个特点。
  • 在大多数情况下,20% 的膨胀对于表来说是正常的。 也就是说,您不应该担心并压缩这个表。

我们找到了如何识别充满无用数据的表。

现在关于如何修复膨胀:

  • 如果我们有一个小平板电脑和良好的磁盘,也就是说,在一个千兆字节的平板电脑上,很有可能使用 VACUUM FULL。 他会从你那里拿走桌子上的独占锁几秒钟,好吧,但他会快速而严厉地做所有事情。 真空完全有什么作用? 它在表上获取独占锁,并将旧表中的活动行重写到新表中。 最后他取代了他们。 它删除旧文件并用新文件替换旧文件。 但在其工作期间,它需要对表进行独占锁定。 这意味着您无法对该表执行任何操作:既不能写入它,也不能读取它,也不能修改它。 而VACUUM FULL需要额外的磁盘空间来写入数据。
  • 下一个工具 pg_repack。 从原理上讲,它与 VACUUM FULL 非常相似,因为它也是将旧文件中的数据重写到新文件中并替换到表中。 但同时,它不会在工作一开始就对表获取独占锁,而是仅在已经准备好数据以替换文件时才获取它。 其磁盘资源要求与 VACUUM FULL 类似。 您需要额外的磁盘空间,如果您有 TB 表,这有时至关重要。 而且它非常需要处理器,因为它积极地使用 I/O。
  • 第三个实用程序是 pg紧凑表。 它对资源更加谨慎,因为它的工作原理略有不同。 pgcompacttable 的主要思想是,它使用表中的更新将所有活动行移动到表的开头。 然后它在此表上运行真空,因为我们知道开头有活行,末尾有死行。 真空本身会切断这个尾巴,即它不需要太多额外的磁盘空间。 同时,在资源方面仍然可以受到挤压。

一切都有工具。

应用程序中导致 postgresql 膨胀的典型错误。 安德烈·萨尔尼科夫

如果您发现膨胀主题对进一步深入研究很有趣,这里有一些有用的链接:

我更多地尝试向开发人员展示一个恐怖故事,因为他们是我们数据库的直接客户,必须了解什么以及操作会导致什么。 我希望我成功了。 感谢您的关注!

问题

感谢您的报告! 您谈到了如何识别问题。 如何警告他们? 也就是说,我遇到了请求挂起的情况,不仅仅是因为它们访问了某些外部服务。 这些只是一些疯狂的连接。 有一些微小的、无害的请求挂了一天,然后开始做一些无意义的事情。 也就是说,与您所描述的非常相似。 如何追踪这个? 坐下来不断观察哪个请求被卡住了? 如何防止这种情况发生?

在这种情况下,这是公司管理员的任务,而不一定是 DBA 的任务。

我是管理员。

PostgreSQL 有一个名为 pg_stat_activity 的视图,用于显示悬空查询。 你可以看到它挂在那里多久。

我必须每 5 分钟进来查看一次吗?

设置 cron 并检查。 如果您有长期要求,请写一封信即可。 也就是说,你不需要用眼睛看,它可以自动化。 您将收到一封信,您对此做出反应。 或者你可以自动拍摄。

发生这种情况有什么明显的原因吗?

我列出了一些。 其他更复杂的例子。 而且可以进行很长时间的对话。

感谢您的报告! 我想澄清一下 pg_repack 实用程序。 如果她不做独占锁,那么……

她做了排他锁。

... 那么我可能会丢失数据。 我的应用程序在这段时间内不应该记录任何内容吗?

不,它可以顺利地与表配合使用,即 pg_repack 首先传输所有存在的实时行。 自然地,表中的某种条目会发生在那里。 他只是把这个马尾辫扔掉了。

也就是说,他最后真的做到了?

最后,他使用独占锁来交换这些文件。

它会比 VACUUM FULL 更快吗?

VACUUM FULL,一启动就立即拿了独占锁。 在他完成一切之前,他不会放开她。 并且 pg_repack 仅在文件替换时获取独占锁。 此时你不会在那里写入,但数据不会丢失,一切都会好起来的。

你好! 您谈到了汽车吸尘器的操作。 有一张带有红色、黄色和绿色记录单元的图表。 也就是说,黄色的 - 他将它们标记为已删除。 那么,是不是可以写入一些新的东西呢?

是的。 Postgres 不会删除行。 他有这样的特殊性。 如果我们更新了一行,我们会将旧行标记为已删除。 更改此行的事务 ID 出现在那里,我们写入一个新行。 我们有可能会阅读它们的会议。 到了某个时候,他们就会变得很老了。 autovacuum 工作原理的本质是它遍历这些行并将它们标记为不必要的。 您可以覆盖那里的数据。

我明白。 但这不是问题的重点。 我没说完。 假设我们有一张桌子。 它具有可变大小的字段。 如果我尝试插入新的东西,它可能根本不适合旧的单元格。

不,无论如何,整行都会在那里更新。 Postgres 有两种数据存储模型。 它从数据类型中进行选择。 有直接存储在表中的数据,也有tos数据。 这些是大量数据:文本、json。 它们存储在单独的盘子中。 根据这些平板电脑,发生了与膨胀相同的故事,即一切都是一样的。 它们只是单独列出。

感谢您的报告! 使用语句超时查询来限制持续时间是否可以接受?

非常可以接受。 我们到处都用这个。 由于我们没有自己的服务,因此我们提供远程支持,因此我们拥有各种各样的客户。 每个人都对此完全满意。 也就是说,我们有 cron 作业来检查。 会议的持续时间只是与客户商定,在此之前我们不同意。 可能是一分钟,也可能是十分钟。 这取决于底座上的负载及其用途。 但我们都使用pg_stat_activity。

感谢您的报告! 我正在尝试将您的报告应用到我的申请中。 看起来我们在任何地方都开始了一笔交易,并且在任何地方都清楚地完成了它。 如果出现异常,仍然会发生回滚。 然后我开始思考。 毕竟,事务可能不会显式启动。 这大概是对女孩的暗示吧。 如果我只是更新一条记录,事务是否会在 PostgreSQL 中启动并仅在连接断开时完成?

如果您现在谈论应用程序级别,那么它取决于您正在使用的驱动程序以及正在使用的 ORM。 那里有很多设置。 如果启用了自动提交,则事务将从此处开始并立即关闭。

也就是说,更新后就立即关闭?

这取决于设置。 我命名了一项设置。 这是自动提交。 这很常见。 如果启用,则事务已打开和关闭。 除非您明确地说“开始事务”和“结束事务”,而只是向会话中发起请求。

你好! 感谢您的报告! 假设我们有一个不断膨胀的数据库,然后服务器上的空间耗尽了。 有什么工具可以解决这种情况吗?

需要正确监控服务器上的空间。

例如,DBA 去喝茶、去度假村等。

创建文件系统时,至少会在不写入数据的地方创建某种备份空间。

如果它完全低于零怎么办?

在那里它被称为保留空间,即它可以被释放,并且根据它创建的大小,您将获得可用空间。 默认情况下我不知道有多少个。 在另一种情况下,交付磁盘以便您有空间执行重建操作。 您可以删除一些您保证不需要的表。

还有其他工具吗?

它总是手工制作的。 在本地,最好做什么就变得很清楚,因为有些数据很关键,有些数据则不关键。 对于每个数据库和与其配合使用的应用程序,这取决于业务。 它总是由当地决定。

感谢您的报告! 我有两个问题。 首先,您展示的幻灯片显示当事务被卡住时,表空间大小和索引大小都会增长。 报告进一步介绍了一系列打包平板电脑的实用程序。 那指数呢?

他们也打包了。

但是真空不影响指数吗?

有些使用索引。 例如,pg_rapack、pgcompacttable。 真空重新创建索引并影响它们。 VACUUM FULL 的想法是覆盖一切,即它适用于每个人。

第二个问题。 我不明白为什么有关副本的报告如此依赖于复制本身。 在我看来,报告是读取的,复制是写入的。

是什么导致复制冲突? 我们有一个发生流程的大师。 我们正在使用汽车吸尘器。 自动真空吸尘器实际上有什么作用? 他正在剪掉一些旧台词。 如果此时我们在副本上有一个读取这些旧行的请求,并且在主服务器上发生了 autovacuum 将这些行标记为可以覆盖的情况,那么我们就会覆盖它们。 我们收到一个数据包,当我们需要在副本上重写请求所需的那些行时,复制过程将等待您配置的超时。 然后 PostgreSQL 将决定什么对它来说更重要。 复制对他来说比请求更重要,他会发出请求以便在副本上进行这些更改。

安德烈,我有一个问题。 您在演示中展示的这些精彩图表是您的某种实用工具的工作成果吗? 图表是如何制作的?

这是一项服务 欧克米特.

这是商业产品吗?

是的。 这是一个商业产品。

来源: habr.com

添加评论