PostgreSQL上的Aggressive Autovacuum


42

我试图让PostgreSQL主动自动清理数据库。我目前已将自动吸尘配置如下:

  • autovacuum_vacuum_cost_delay = 0#关闭基于成本的真空
  • autovacuum_vacuum_cost_limit = 10000#最大值
  • autovacuum_vacuum_threshold = 50#默认值
  • autovacuum_vacuum_scale_factor = 0.2#默认值

我注意到只有在数据库未处于负载状态时才会启动自动清理功能,因此我遇到了这样的情况:死元组比活动元组多得多。有关示例,请参见随附的屏幕截图。其中一张桌子有23个活动元组,但有16845个死元组正在等待真空。太疯狂了!

很多死元组

当测试运行完成并且数据库服务器处于空闲状态时,自动吸尘器将启动,这不是我想要的,因为当失效的元组的数量超过20%的活动元组+ 50时,我希望自动吸尘器启动,因为数据库已经配置。服务器空闲时自动清理对我来说毫无用处,因为生产服务器在持续的时间内有望达到每秒数千次的更新,这就是即使服务器负载时我也需要运行自动清理的原因。

有什么我想念的吗?在服务器负载较重时,如何强制自动吸尘器运行?

更新资料

难道这是一个锁定问题?有问题的表是汇总表,这些表是通过插入后触发器填充的。这些表被锁定为SHARE ROW EXCLUSIVE模式,以防止并发写入同一行。

Answers:


40

Eelke几乎可以肯定,您的锁定阻止了自动真空。Autovacuum旨在让位给用户活动。如果这些表被锁定,则自动真空无法清理它们。

但是,为了后代,我想举一个例子来说明过度攻击性自动抽真空的设置,因为您提供的设置并没有做到这一点。请注意,使自动真空更具侵略性不可能解决您的问题。还要注意,默认的自动真空设置是基于使用DBT2进行200多次测试运行以寻求最佳设置组合的,因此,除非您有充分的理由认为不这样做,或者您的数据库不在外部,否则应该假定默认设置为良好OLTP数据库的主流(例如,每秒可更新10K的小型数据库或3TB数据仓库)。

首先,打开日志记录,以便您可以检查autovacuum是否正在执行您认为的工作:

log_autovacuum_min_duration = 0

然后,让更多的autovac工作人员让他们更频繁地检查表:

autovacuum_max_workers = 6
autovacuum_naptime = 15s

让我们降低自动抽真空和自动分析的阈值以更快地触发:

autovacuum_vacuum_threshold = 25
autovacuum_vacuum_scale_factor = 0.1

autovacuum_analyze_threshold = 10
autovacuum_analyze_scale_factor = 0.05 

然后,让自动真空的中断性降低,使其更快地完成,但是以对并发用户活动产生更大影响为代价:

autovacuum_vacuum_cost_delay = 10ms
autovacuum_vacuum_cost_limit = 1000

您有完整的程序来进行一般性的主动真空处理,这可能适合于小型数据库获得很高的更新率,但是对并发用户活动的影响可能太大。

另外,请注意,可以按表调整自动真空参数,对于需要调整自动真空行为的人来说,这几乎总是一个更好的答案。

同样,尽管如此,也不太可能解决您的实际问题。


36

只是为了查看哪些表完全符合自动抽空条件,可以使用以下查询(基于http://www.postgresql.org/docs/current/static/routine-vacuuming.html)。但是请注意,查询不会查找表特定的设置:

 SELECT psut.relname,
     to_char(psut.last_vacuum, 'YYYY-MM-DD HH24:MI') as last_vacuum,
     to_char(psut.last_autovacuum, 'YYYY-MM-DD HH24:MI') as last_autovacuum,
     to_char(pg_class.reltuples, '9G999G999G999') AS n_tup,
     to_char(psut.n_dead_tup, '9G999G999G999') AS dead_tup,
     to_char(CAST(current_setting('autovacuum_vacuum_threshold') AS bigint)
         + (CAST(current_setting('autovacuum_vacuum_scale_factor') AS numeric)
            * pg_class.reltuples), '9G999G999G999') AS av_threshold,
     CASE
         WHEN CAST(current_setting('autovacuum_vacuum_threshold') AS bigint)
             + (CAST(current_setting('autovacuum_vacuum_scale_factor') AS numeric)
                * pg_class.reltuples) < psut.n_dead_tup
         THEN '*'
         ELSE ''
     END AS expect_av
 FROM pg_stat_user_tables psut
     JOIN pg_class on psut.relid = pg_class.oid
 ORDER BY 1;

11

是的,这是一个锁定问题。根据此页面(非完整),VACUUM需要SHARE UPDATE EXCLUSIVE访问,该访问被您使用的锁定级别阻止。

您确定需要此锁吗?PostgreSQL是ACID兼容的,因此并发写入在大多数情况下都不是问题,因为如果发生序列化违规,PostgreSQL将中止其中一个事务。

您也可以通过使用锁定行SELECT FOR UPDATE锁定行而不是整个表。

不锁定的另一种选择是使用可序列化的事务隔离级别。但是,这可能会影响其他事务的性能,因此您应该为更多的序列化失败做好准备。


这是由于锁定了摘要表,因为这些摘要表是使用SHARE ROW EXCLUSIVE MODE锁定的。没有锁定的并发写入可能会成功,但是最肯定的是最终将以错误的值结束。想象一下,我正在维护N个类型X的行。如果我同时插入2个类型X的行,而没有锁定,我将在汇总表中得到N + 1而不是N + 2。有一个cron作业,可以手动清理数据库中的摘要表。它运作良好,似乎是推荐的方法,但对我来说,感觉实在太像了。
CadentOrange

6

增加自动真空处理的数量并减少午睡时间可能会有所帮助。这是我在存储备份信息的服务器上使用的PostgreSQL 9.1的配置,因此得到很多插入活动。

http://www.postgresql.org/docs/current/static/runtime-config-autovacuum.html

autovacuum_max_workers = 6              # max number of autovacuum subprocesses
autovacuum_naptime = 10         # time between autovacuum runs
autovacuum_vacuum_cost_delay = 20ms     # default vacuum cost delay for

我还将尝试降低,cost_delay以使吸尘更具侵略性。

我也可以使用pgbench测试自动清理。

http://wiki.postgresql.org/wiki/Pgbenchtesting

高争夺示例:

创建bench_replication数据库

pgbench -i -p 5433 bench_replication

运行pgbench

pgbench -U postgres -p 5432 -c 64 -j 4 -T 600 bench_replication

检查自动抽真空状态

psql
>\connect bench_replicaiton
bench_replication=# select schemaname, relname, last_autovacuum from pg_stat_user_tables;
 schemaname |     relname      |        last_autovacuum        
------------+------------------+-------------------------------
 public     | pgbench_branches | 2012-07-18 18:15:34.494932+02
 public     | pgbench_history  | 
 public     | pgbench_tellers  | 2012-07-18 18:14:06.526437+02
 public     | pgbench_accounts | 

6

现有的“自动抽空资格”脚本非常有用,但是(正确说明)缺少表特定的选项。这是考虑到这些选项的修改后的版本:

WITH rel_set AS
(
    SELECT
        oid,
        CASE split_part(split_part(array_to_string(reloptions, ','), 'autovacuum_vacuum_threshold=', 2), ',', 1)
            WHEN '' THEN NULL
        ELSE split_part(split_part(array_to_string(reloptions, ','), 'autovacuum_vacuum_threshold=', 2), ',', 1)::BIGINT
        END AS rel_av_vac_threshold,
        CASE split_part(split_part(array_to_string(reloptions, ','), 'autovacuum_vacuum_scale_factor=', 2), ',', 1)
            WHEN '' THEN NULL
        ELSE split_part(split_part(array_to_string(reloptions, ','), 'autovacuum_vacuum_scale_factor=', 2), ',', 1)::NUMERIC
        END AS rel_av_vac_scale_factor
    FROM pg_class
) 
SELECT
    PSUT.relname,
    to_char(PSUT.last_vacuum, 'YYYY-MM-DD HH24:MI')     AS last_vacuum,
    to_char(PSUT.last_autovacuum, 'YYYY-MM-DD HH24:MI') AS last_autovacuum,
    to_char(C.reltuples, '9G999G999G999')               AS n_tup,
    to_char(PSUT.n_dead_tup, '9G999G999G999')           AS dead_tup,
    to_char(coalesce(RS.rel_av_vac_threshold, current_setting('autovacuum_vacuum_threshold')::BIGINT) + coalesce(RS.rel_av_vac_scale_factor, current_setting('autovacuum_vacuum_scale_factor')::NUMERIC) * C.reltuples, '9G999G999G999') AS av_threshold,
    CASE
        WHEN (coalesce(RS.rel_av_vac_threshold, current_setting('autovacuum_vacuum_threshold')::BIGINT) + coalesce(RS.rel_av_vac_scale_factor, current_setting('autovacuum_vacuum_scale_factor')::NUMERIC) * C.reltuples) < PSUT.n_dead_tup
        THEN '*'
    ELSE ''
    END AS expect_av
FROM
    pg_stat_user_tables PSUT
    JOIN pg_class C
        ON PSUT.relid = C.oid
    JOIN rel_set RS
        ON PSUT.relid = RS.oid
ORDER BY C.reltuples DESC;
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.