我们正在运行:
user@host:~$ psql -d database -c "SELECT version();"
version
---------------------------------------------------------------------------------------------------------------------------------------------
PostgreSQL 10.7 (Ubuntu 10.7-1.pgdg16.04+1) on x86_64-pc-linux-gnu, compiled by gcc (Ubuntu 5.4.0-6ubuntu1~16.04.11) 5.4.0 20160609, 64-bit
(1 row)
上:
user@host:~$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 16.04.6 LTS
Release: 16.04
Codename: xenial
并具有以下设置:
database=# \d+ schema.table
Table "schema.table"
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
-----------------------------+-----------------------------+-----------+----------+-------------------------------------------------+----------+--------------+-------------
column_1 | bigint | | not null | nextval('table_id_seq'::regclass) | plain | |
column_2 | character varying | | not null | | extended | |
column_3 | character varying | | not null | | extended | |
column_4 | character varying | | not null | | extended | |
column_5 | timestamp without time zone | | not null | | plain | |
column_6 | timestamp without time zone | | | | plain | |
column_7 | character varying | | not null | | extended | |
column_8 | jsonb | | not null | | extended | |
column_9 | jsonb | | | | extended | |
column_10 | character varying | | not null | | extended | |
column_11 | character varying | | not null | | extended | |
column_12 | character varying | | | | extended | |
column_13 | character varying | | | | extended | |
column_14 | timestamp with time zone | | not null | | plain | |
column_15 | timestamp with time zone | | not null | | plain | |
Indexes:
"table_pkey" PRIMARY KEY, btree ( column_1 )
"table_idx_1" btree ( column_11)
"table_idx_2" btree ( column_4, column_2, column_7, column_5, column_6 )
"table_idx_3" btree ( column_7, column_11, column_15 )
"table_idx_4" btree ( column_7, column_11, column_14 )
"table_idx_5" btree ( column_7, column_11, column_5 )
"table_idx_6" btree ( column_7, ( ( column_8 ->> 'string_1'::TEXT )::INTEGER ), column_5 )
"table_idx_7" btree ( column_15 )
"table_idx_8" btree ( column_4, column_2, column_7, column_5, ( ( column_8 ->> 'string_1'::TEXT )::INTEGER ) )
"table_idx_9" btree ( column_4, column_2, column_7, ( ( column_8 ->> 'string_1'::TEXT )::INTEGER) )
"table_idx_a" btree ( column_7, column_4, column_2, ( ( column_8 ->> 'string_1'::TEXT )::INTEGER), ( ( column_8 ->> 'string_2'::TEXT )::INTEGER ) ) WHERE column_7::TEXT = 'string_3'::TEXT
Check constraints:
"table_check_constraints" CHECK ( lower( column_10::TEXT ) <> 'string_4'::TEXT OR column_9 IS NOT NULL AND column_6 IS NOT NULL )
Autovacuum 已打开并配置为:
user@host:~$ psql -d database -c "SELECT name, setting, pending_restart FROM pg_settings WHERE NAME ILIKE '%autovacuum%' ORDER BY name;"
name | setting | pending_restart
-------------------------------------+-----------------------+-----------------
autovacuum | on | f
autovacuum_analyze_scale_factor | 0.002 | f
autovacuum_analyze_threshold | 10 | f
autovacuum_freeze_max_age | 200000000 | f
autovacuum_max_workers | 5 | f
autovacuum_multixact_freeze_max_age | 400000000 | f
autovacuum_naptime | 30 | f
autovacuum_vacuum_cost_delay | 10 | f
autovacuum_vacuum_cost_limit | 1000 | f
autovacuum_vacuum_scale_factor | 0.001 | f
autovacuum_vacuum_threshold | 25 | f
autovacuum_work_mem | -1 | f
log_autovacuum_min_duration | 0 (env 1) /-1 (env 2) | f
(13 rows)
以下事件序列发生在环境 1中,在此期间autovacuum
已按上述方式开启和配置:
- 每晚
VACUUM (VERBOSE, ANALYZE)
添加数据库。 - 一段时间过去,膨胀处于正常运行水平。
- Nightly
VACUUM (VERBOSE, ANALYZE)
的数据库被删除。 table_idx_8
添加了包含 JSONB 数据类型列的索引。table_idx_9
添加了包含 JSONB 数据类型列的索引。- 膨胀的生长突增开始并持续 2 天,直到达到顶峰。
VACUUM (VERBOSE, FULL)
的表。- 膨胀恢复到正常的操作水平并保持在那里。
在这一系列事件中,环境 1中的数据库大小 (GB) 如下所示:
这就是环境 1中膨胀 (GB) 的样子:
环境 1中的活动行数:
环境 1中的死行数:
以下事件序列发生在环境 2中,在所有这些事件中都autovacuum
按上述方式打开和配置:
- 每晚
VACUUM (VERBOSE, ANALYZE)
添加数据库。 - 一段时间过去,膨胀处于正常运行水平。
- Nightly
VACUUM (VERBOSE, ANALYZE)
的数据库被删除。 table_idx_8
添加了包含 JSONB 数据类型列的索引。table_idx_9
添加了包含 JSONB 数据类型列的索引。- 膨胀的增长突增开始并持续 2 天,直到达到峰值并使 DB 下降(磁盘已满)。
TRUNCATE TABLE schema.table
.- 再次填充 schema.table 表。
- 膨胀并没有稳定下来并增长,直到它再次达到顶峰。
TRUNCATE TABLE schema.table
在磁盘再次填满之前。- 数据库的 VACUUM (VERBOSE, FULL)。
- 再次填充 schema.table 表。
- 膨胀继续增长!
在这一系列事件中,环境 2中的数据库大小 (GB)如下所示:
这就是环境 2中膨胀 (GB) 的样子:
这两种环境之间的唯一区别是它们的规格略有不同(2 的功能较弱)。在这些事件序列中,每个环境的写入/读取量都没有变化。我们正在使用此查询来衡量膨胀(以字节为单位)。
我已经交叉检查了 PostgreSQL 日志、监控日志和提交日志 (Git),并确定添加两个索引作为膨胀的触发器,但是:
- 那正确吗?增加一个指数会引发如此膨胀的增长吗?
- 如果确实如此,为什么添加索引会触发膨胀?
- 为什么环境 1 稳定而环境 2 不稳定?
- 我们如何稳定环境 2?
任何回答这些问题的帮助将不胜感激,不用说,我很乐意提供我错过的任何其他可能有用的信息。
我认为考古方法在这里不会很有用。缺少太多信息和混淆变量。例如,人们通常不会无缘无故地添加索引。如果工作负载的变化推动了索引的创建,则可能是独立于索引的工作负载的变化导致了膨胀。
有很多理论可以解释你所看到的,但实际上没有办法根据给出的历史来区分它们。每个索引都为真空提供了更多工作要做,因此您的新索引可能只是因为它已经接近临界点而将其推到了临界点,而不管索引的内容是什么。或者,当表被锁定以创建索引时,可能会堆积很多工作,然后一旦锁定被释放,活动的狂热就会把它推到边缘。不仅仅是更多的索引为真空创造了更多的工作——膨胀也是如此。这可能导致恶性循环,更多的膨胀会减缓真空,导致更多的膨胀。这可能是 VACUUM FULL 后环境 1 稳定的原因,它打破了恶性循环,以至于常规真空现在可以跟上。
乍一看,这些设置似乎相当荒谬。他们有理由吗?它可能会花费大量时间清理和分析并不真正需要它们的表,以至于它无法跟上需要清理的表(但如果你只有一个大表,那可能不是很多关心)。降低比例因子是有意义的,但通常只能与阈值的增加结合使用。
我经常将“vacuum_cost_page_hit”设置为零“vacuum_cost_page_miss”设置为零。以我的经验,autovac引起的并发性能问题一般是由写入引起的,而不是由读取引起的,因此在读取端进行节流是没有意义的。当您拥有已经膨胀的表和索引时,这一点尤其重要,因为您可以进行更多的阅读而不是写作。
设置 log_autovacuum_min_duration=0 的输出可以帮助区分各种理论。此外,使用pg_freespacemap来查看 PostgreSQL 认为该表有多少 sum(avail),虽然它处于膨胀状态,但可以提供信息。