【Postgresql】VACUUM 垃圾回收

Xander發表於2022-11-25

#Postgersql

介紹

原始英文文件:PostgreSQL: Documentation: 15: VACUUM

對應的中文文件:VACUUM (postgres.cn),基本都是機翻建議對照學習,不會迷茫。

VACUUM是什麼?

官方只用了一句話介紹VACUUM:

garbage-collect and optionally analyze a database

VACUUM可以認為手動觸發Postgresql 垃圾回收的原始命令,需要注意查閱的文件版本為:PostgreSql 14

比VACUUM更為重要的是AUTO_VACUUM,放到本文最後討論,當然VACUUM是底層實現。

為什麼叫 VACUUM?

介紹枯燥的文件內容之前,個人先猜測一波起名垃圾回收為VACUUM的原因:

  1. 由於VACUUM機制正常引數下只會把死元組的空間重用,不會把申請的空間歸還給作業系統,所以類似抽真空的感覺。
  2. 如果是VACUUM FULL,此時會把空間重用並且把空間還給作業系統,這時候的抽真空又像是把一塊空間整個“剝離”出去。

當然以上純屬個人瞎扯,也有可能是這個名字對於開發者來說會覺得很COOL

相關語法

VACUUM [ ( _`option`_ [, ...] ) ] [ _`table_and_columns`_ [, ...] ]
VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ _`table_and_columns`_ [, ...] ]

where _`option`_ can be one of:

    FULL [ _`boolean`_ ]
    FREEZE [ _`boolean`_ ]
    VERBOSE [ _`boolean`_ ]
    ANALYZE [ _`boolean`_ ]
    DISABLE_PAGE_SKIPPING [ _`boolean`_ ]
    SKIP_LOCKED [ _`boolean`_ ]
    INDEX_CLEANUP { AUTO | ON | OFF }
    PROCESS_TOAST [ _`boolean`_ ]
    TRUNCATE [ _`boolean`_ ]
    PARALLEL _`integer`_

and _`table_and_columns`_ is:

    _`table_name`_ [ ( _`column_name`_ [, ...] ) ]

注意這種圓括號的用法是官方推薦的,如果要修改引數,就需要使用圓括號,否則會報錯無法透過。

作用

既然是回收垃圾程式那麼要回收什麼東西? 其實很簡單,清理死元組,這裡根據文件一一分析。

清理死元組

VACUUM reclaims storage occupied by dead tuples。

這裡說Postgresql回收的是死元組,元組是Postgresql資料結構的基本組成單位,比較像Mysql的資料頁。

那麼死元組是怎麼來的呢?下面就有一句很關鍵的話:

tuples that are deleted or obsoleted by an update are not physically removed from their table

好傢伙,假更新和假刪除是吧,也就是說刪除是在元組進行標記,而更新可以認為是先標記刪除然後“插入”,是不是覺得非常熟悉?

接下來一句話也比較關鍵:

they remain present until a VACUUM is done

VACUUM執行完垃圾回收之後才會把死元組進行回收。這時候垃圾回收的執行頻率和垃圾回收執行效果就非常關鍵了,這裡接著往下看:

Without a _table_and_columns_ list, VACUUM processes every table and materialized view in the current database that the current user has permission to vacuum. With a list, VACUUM processes only those table(s).

在沒有table_and_columns列表的情況下,VACUUM會處理當前使用者具有清理許可權的資料庫中的每張表和物化檢視。(但是實際用的時候大部分情況需要超級使用者,或者具備較高許可權的系統管理員,一般使用者是沒有這個許可權的)

table_and_columns是啥?
指的是垃圾回收可以指定表以及列,如果不想對所有表做清理,在手動清理的時候可以進行配置。

此外Postgresql針對垃圾回收開發了另一個子命令 VACUUM ANALYZE, 可以透過此命令對於執行的 Postgresql 例項進行分析,也是實現自動垃圾回收的關鍵元件之一。

這裡不過多討論 ANALYZE ,我們可以直接看看效果:
命令:VACUUM VERBOSE ANALYZE (VERBOSE 表示顯示分析進度和詳細資訊)
0 pages are entirely empty.
CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s.
INFO:  vacuuming "pg_toast.pg_toast_88998"
INFO:  index "pg_toast_88998_index" now contains 0 row versions in 1 pages
DETAIL:  0 index row versions were removed.
0 index pages have been deleted, 0 are currently reusable.
CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s.
INFO:  "pg_toast_88998": found 0 removable, 0 nonremovable row versions in 0 out of 0 pages
DETAIL:  0 dead row versions cannot be removed yet, oldest xmin: 3643386
There were 0 unused item identifiers.
Skipped 0 pages due to buffer pins, 0 frozen pages.
0 pages are entirely empty.
CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s.
INFO:  analyzing "public.xxxxxx"
INFO:  "xxxxxx": scanned 837 of 837 pages, containing 11930 live rows and 0 dead rows; 11930 rows in sample, 11930 estimated total rows

OK
查詢時間: 11.3s

收縮

收縮是設計本身附帶的好處,這裡簡單介紹不作為研究重點。收縮更偏向於 VACUUM FULL 這個指令,因為涉及磁碟IO操作,在清理掉過期索引的同時,為接下來的資料流入預分配新的空間進行儲存,清理掉過期索引可以實現死元組空間複用,減少磁碟空間的佔用浪費。

我們簡單並且快速瞭解 VACUUM 的作用,接著繼續根據文件瞭解更多細節。

引數介紹

個人根據官方文件摘錄了部分引數重要的說明同時做了一張表,這張表不必記憶,只需要簡單瞭解然後實戰當中進行查閱即可。

Optionenglish description中文解釋
FULLSelects “full” vacuum, which can reclaim more space, but takes much longer and exclusively locks the table可以回收更多空間,但是會鎖表並且回收時間會變長
PROCESS_TOASTSpecifies that VACUUM should attempt to process the corresponding TOAST table for each relation, if one exists.指定VACUUM應該嘗試為每個關係處理相應的TOAST表(如果存在的話)。預設是開啟狀態。
個人觀點:通常不建議修改此引數
INDEX_CLEANUPVACUUM will skip index vacuuming when there are very few dead tuples in the table.
The default is AUTO.
If INDEX_CLEANUP is set to ON, VACUUM will conservatively remove all dead tuples from indexes. This may be useful for backwards compatibility with earlier releases of PostgreSQL where this was the standard behavior.
If INDEX_CLEANUP is set to ON, VACUUM will conservatively remove all dead tuples from indexes.
INDEX_CLEANUP can also be set to OFF to force VACUUM to always skip index vacuuming, even when there are many dead tuples in the table.
通常情況下表中死元組比較少會跳過索引掃描。
預設設定為AUTO。
如果設定為ON:則每次清理都會掃描索引,反之如果為OFF,則所有的清理動作都會跳過索引掃描
FREEZESelects aggressive “freezing” of tuples. Specifying FREEZE is equivalent to performing VACUUM with the vacuum_freeze_min_age and vacuum_freeze_table_age parameters set to zero
option is redundant when FULL is specified
此選項表示會激進凍結元組,指定此引數相當於執行VACUUM的同時把並將 vacuum_freeze_min_age和 vacuum_freeze_table_age引數設定為0。
如果是VACUUM FULL此選項無效
ANALYZEUpdates statistics used by the planner to determine the most efficient way to execute a query.使用此引數可以立即重新整理分析器的分析結果,對於某些場景的調優很有幫助。
VERBOSEPrints a detailed vacuum activity report for each table.列印垃圾回收的詳細資訊,觀察垃圾回收的具體情況
DISABLE_PAGE_SKIPPINGNormally, VACUUM will skip pages based on the visibility map.
This option disables all page-skipping behavior, and is intended to be used only when the contents of the visibility map are suspect, which should happen only if there is a hardware or software issue causing database corruption.
看介紹基本是一個無關痛癢的引數,使用此選項會啟動關閉跳頁。通常只有在資料庫出現嚴重問題的情況下此引數才有使用價值
SKIP_LOCKEDSpecifies that should not wait for any conflicting locks to be released when beginning work on a relation: if a relation cannot be locked immediately without waiting, the relation is skipped
Also, while ordinarily processes all partitions of specified partitioned tables, this option will cause to skip all partitions if there is a conflicting lock on the partitioned table.VACUUMVACUUMVACUUM ANALYZEVACUUMVACUUM
指定在開始處理關係時不應等待釋放任何衝突鎖。如果無法在不等待的情況下立即鎖定關係,則跳過該關係
雖然通常會處理指定分割槽表的所有分割槽,但是如果分割槽表上有衝突的鎖,這個選項會導致跳過所有分割槽。
TRUNCATESpecifies that VACUUM should attempt to truncate off any empty pages at the end of the table and allow the disk space for the truncated pages to be returned to the operating system.指定VACUUM應該嘗試截斷表末的任何空頁,並允許將截斷頁的磁碟空間返回給作業系統。這通常是所希望的行為,也是預設的。
個人觀點:通常不建議修改此引數
PARALLEL(13版本之後生效)Perform index vacuum and index cleanup phases of VACUUM in parallel using integer background workers (for the details of each vacuum phase, please refer to Table 28.39)
vacuum which is limited by the number of workers specified with PARALLEL option if any which is further limited by max_parallel_maintenance_workers
An index can participate in parallel vacuum if and only if the size of the index is more than min_parallel_index_scan_size.
Only one worker can be used per index.
These behaviors might change in a future release. This option can't be used with the FULL option.
使用指定的執行緒數在後臺工作器並行地執行VACUUM的索引真空和索引清理階段。
如果有PARALLEL選項的話,真空會受到PARALLEL選項所指定的工作器數量的限制,而這又受到max_parallel_maintenance_workers的限制。
當且僅當一個索引的大小超過min_parallel_index_scan_size時,該索引才能參與並行真空。
每個索引只能用單獨工作器工作。意味著不保證實際執行的時候需要用到指定數量的工作器。
這個引數的工作機制未來有可能會發生變化,並且這個選項不能和FULL一起用。

除了上面的引數之外,接著介紹一些無關緊要的引數:

  • boolean:可以是 true、1,選項如果不做指定預設為 true
  • integer:PARALLEL 指定並行處理器的數量,必須是非負整數。(Postgresql 13 開始生效)
  • table_name:支援指定表的垃圾回收。
  • column_name:支援指定列的垃圾回收。

PARALLEL

這裡特意拿出來說一下,這個引數官方在Postgresql 13版本才加入,個人感覺是受到Mysql的“刺激”加入的,作用是指定垃圾回收執行緒的併發數,使用者可以手動指定非零值,當然這個值不是自由指定的,官方存在對應的“最大值”限制胡亂傳參。

如果引數不存在或者不生效,可以查詢一下當前的Postgresql版本。

select version()

如果想了解不同的執行緒併發數對於實際應用的影響對比,可以看看這篇(英文)文章的引數實踐介紹:

Parallelism comes to VACUUM - 2ndQuadrant | PostgreSQL

個人挑選了相關結論部分:

I’ve evaluated the performance of parallel vacuum on my laptop (Core i7 2.6GHz, 16GB RAM, 512GB SSD). The table size is 6GB and has eight 3GB indexes. The total relation is 30GB, which doesn’t fit the machine RAM. For each evaluation, I made several percent of the table dirty evenly after vacuuming, then performed vacuum while changing the parallel degree. The graph below shows the vacuum execution time.
我已經評估了膝上型電腦(Core i7 2.6GHz,16GB RAM,512GB SSD)上的(多CPU)並行vacuum的效能。Table 大小為 6GB,有八個 3GB Index。Related為 30GB,不適合機器 RAM。對於每次評估,我在垃圾回收後均勻地製造百分之幾的髒元組(類比髒資料頁),然後在改變並行度的同時進行vacuum。下圖顯示了vacuum執行時間。

Result:
In all evaluations the execution time of the index vacuum accounted for more than 95% of the total execution time.
顯然如果設定合適的CPU並行數量,可以大幅度的減少垃圾回收的執行時間。減小Postgresql抖動情況出現。

FREEZE

在文件中提到了FREEZE引數,說實話不知道官方文件在說啥,屬於個人瞭解目前還不夠深入,先翻譯放著留個坑,有需要實戰或者實驗的時候再深入研究。

Translation:
vacuum_freeze_min_age(VACUUM 凍結最小年齡): Specifies the cutoff age (in transactions) that VACUUM should use to decide whether to freeze row versions while scanning a table. The default is 50 million transactions
指定VACUUM用來決定是否在掃描表時凍結行版本的截止年齡(以事務為單位)。預設值是5000萬個事務。
vacuum_freeze_table_age(觸發凍結年齡):VACUUM performs an aggressive scan if the table's pg_class.relfrozenxid field has reached the age specified by this setting. The default is 150 million transactions
如果表的pg_class.relfrozenxid欄位達到這個設定所指定的年齡,VACUUM會執行積極的掃描。預設值是1.5億個記錄。

細節

VACCUM 清理階段介紹

英文文件描述

PhaseDescription
initializingVACUUM is preparing to begin scanning the heap. This phase is expected to be very brief.
scanning heapVACUUM is currently scanning the heap. It will prune and defragment each page if required, and possibly perform freezing activity. The heap_blks_scanned column can be used to monitor the progress of the scan.
vacuuming indexesVACUUM is currently vacuuming the indexes. If a table has any indexes, this will happen at least once per vacuum, after the heap has been completely scanned. It may happen multiple times per vacuum if maintenance_work_mem (or, in the case of autovacuum, autovacuum_work_mem if set) is insufficient to store the number of dead tuples found.
vacuuming heapVACUUM is currently vacuuming the heap. Vacuuming the heap is distinct from scanning the heap, and occurs after each instance of vacuuming indexes. If heap_blks_scanned is less than heap_blks_total, the system will return to scanning the heap after this phase is completed; otherwise, it will begin cleaning up indexes after this phase is completed.
cleaning up indexesVACUUM is currently cleaning up indexes. This occurs after the heap has been completely scanned and all vacuuming of the indexes and the heap has been completed.
truncating heapVACUUM is currently truncating the heap so as to return empty pages at the end of the relation to the operating system. This occurs after cleaning up indexes.
performing final cleanupVACUUM is performing final cleanup. During this phase, VACUUM will vacuum the free space map, update statistics in pg_class, and report statistics to the statistics collector. When this phase is completed, VACUUM will end.

中文文件對應的描述,建議湊合著看:

階段描述
初始化VACUUM正在準備開始掃描堆。這個階段很簡短。
掃描堆VACUUM正在掃描堆。如果需要,它將會對每個頁面進行修建以及碎片整理,並且可能會執行凍結動作。heap_blks_scanned列可以用來監控掃描的進度。
清理索引VACUUM當前正在清理索引。如果一個表擁有索引,那麼每次清理時這個階段會在堆掃描完成後至少發生一次。如果maintenance_work_mem不足以存放找到的死亡元組,則每次清理時會多次清理索引。
清理堆VACUUM當前正在清理堆。清理堆與掃描堆不是同一個概念,清理堆發生在每一次清理索引的例項之後。如果heap_blks_scanned小於heap_blks_total,系統將在這個階段完成之後回去掃描堆;否則,系統將在這個階段完成後開始清理索引。
清除索引VACUUM當前正在清除索引。這個階段發生在堆被完全掃描並且對堆和索引的所有清理都已經完成以後。
截斷堆VACUUM正在截斷堆,以便把關係尾部的空頁面返還給作業系統。這個階段發生在清除完索引之後。
執行最後的清除VACUUM在執行最終的清除。在這個階段中,VACUUM將清理空閒空間對映、更新pg_class中的統計資訊並且將統計資訊報告給統計收集器。當這個階段完成時,VACUUM也就結束了。

無FULL條件回收

Plain VACUUM (without FULL) simply reclaims space and makes it available for re-use, This form of the command can operate in parallel with normal reading and writing of the table, as an exclusive lock is not obtained

通常情況下沒有FULL引數的回收,僅僅把那些標記為死元組的空間進行複用。注意整個垃圾回收的操作是沒有任何鎖操作的,所以可以和使用者執行緒 並行,這就意味著遇到緊急問題可以基本沒有副作用的快速對於死元組過多的表清理。

個人補充:雖然官方說這個回收很強並且基本可以無副作用執行,但是依然不建議對整個庫這樣幹,應當先對於當前Postgresql的資料各個表的健康狀況分析然後定位對應的表和列進行清理,其次是VACUUM FULL要給表加獨佔鎖,代價很大不能在負載高的時候手動呼叫。

結論:不建議或者禁止對於全庫做 VACUUM 和 VACUUM FULL,哪怕官方解釋可以和使用者程式並行。

無FULL條件回收會導致這些額外的空間不能歸還給作業系統重新分配,也就是說空間還是被佔用了,只不過裡面的空間等待重用而已。

extra space is not returned to the operating system (in most cases);

官方把這種回收方式叫做 parallel vacuum

應該如何禁用?

要禁用Vacuum功能可以使用PARALLEL選項並將並行執行緒數指定為零。具體可以參考下面的寫法,這樣就不會產生任何工作器進垃圾回收動作。

強調 PARALLEL 0 這種用法是13才開始出現,個人嘗試的時候公司的版本剛好是12就直接模擬命令了。
VACUUM(PARALLEL 0)

當然沒什麼人吃飽了沒事幹去捯飭這東西,忘掉這個用法即可。

有FULL條件回收

其實大致區別也可以猜出來,VACUUM FULL會將表的整個內容重寫到一個新的磁碟檔案中,但是因為存在物理磁碟IO所以開銷比較大,並且清理過程需要加一個表級的排他鎖,此時其他使用者執行緒無法進行讀寫

而與之相對的VACUUM FULL的好處是不包含額外的空間,這使得沒有被使用的空間被還給作業系統

注意事項

  1. 執行垃圾回收的操作的使用者必須擁有相關表的許可權。
  2. VACUUM 只允許非事務執行。
  3. 對具有GIN索引的表,VACUUM(任何形式)也會透過將待處理索引項移動到主要GIN索引結構中的合適位置來完成任何待處理的索引插入。
  4. 建議經常清理生產資料庫(至少每晚在系統低活躍量的時候執行一次),以保證移除失效的行。(比較套路的方案是定時任務,這裡就不過多討論了)
  5. 日常使用時,不推薦FULL選項,但在特殊情況時它會有用。舉個例子是當你刪除或者更新了一個表中的絕大部分行時,如果你希望在物理上收縮表以減少磁碟空間佔用並且允許更快的表掃描,則該選項是比較合適的。
  6. PARALLEL選項控制並行啟動的垃圾回收執行緒數量,如果此選項與ANALYZE選項一起指定,則不會影響ANALYZE
  7. VACUUM會導致I/O流量的大幅度增加,這可能導致資料庫其他程式活動受到影響。(涉及底層資料結構的變動)。
  8. 建議使用基於成本的VACUUM延遲特性,這一個點放到下文補充說明。
  9. 對於並行清理,建議按照上述討論設定為CPU的核心數量,此外不建議並行數量超過CPU核心數量。
  10. PostgreSQL包括了“autovacuum”守護程式,它可以自動垃圾回收來實現定期維護表。但是注意垃圾回收程式的優先順序很低,只在必要的時候出來工作,這和很多高階程式語言有相似之處。
  11. 每個執行VACUUM但沒有FULL選項的後端將在pg_stat_progress_vacuum檢視中報告其進度。執行VACUUM FULL的後端將在pg_stat_progress_cluster檢視中報告它們的進度。

補充:如果線上的垃圾自動回收無法滿足資料庫業務增長要求,需要手動調整自動VACUUM引數。

補充

基於成本的VACUUM延遲

During the execution of VACUUM and ANALYZE commands, the system maintains an internal counter that keeps track of the estimated cost of the various I/O operations that are performed.

The intent of this feature is to allow administrators to reduce the I/O impact of these commands on concurrent database activity.

This feature is disabled by default for manually issued commands. To enable it, set the variable to a nonzero value .VACUUM`vacuum_cost_delay`

在執行VACUUMANALYZE命令期間,系統維護一個內部計數器,用於跟蹤所執行的各種 I/O 操作的估計成本。此功能的目的是允許管理員減少這些命令對併發資料庫活動的 I/O 影響。預設情況下,對於手動發出的命令,此功能處於禁用狀態,預設情況下,對於手動發出的命令,此功能處於禁用狀態.

vacuum_cost_delay (floating point)
The amount of time that the process will sleep when the cost limit has been exceeded.

當超過成本限制時,程式將休眠的時間量。(預設值是零)

vacuum_cost_page_hit (integer)
The estimated cost for vacuuming a buffer found in the shared buffer cache.

對共享緩衝區快取中發現的緩衝區進行vacuuming的估計成本。

vacuum_cost_page_miss (integer)
The estimated cost for vacuuming a buffer that has to be read from disk.

對一個必須從磁碟上讀取到緩衝區進行vacuuming的預估成本

vacuum_cost_page_dirty (integer)
The estimated cost charged when vacuum modifies a block that was previously clean.

當vacuum修改一個之前是純淨的塊時所需要的的預估成本

vacuum_cost_limit (integer)
The accumulated cost that will cause the vacuuming process to sleep. The default value is 200.

預設為200,指的是導致VACUUM休眠的累計成本。

上面這些內容都可以在postgresql.conf檔案中找到:

# - Cost-Based Vacuum Delay -

#vacuum_cost_delay = 0            # 0-100 milliseconds (0 disables)
#vacuum_cost_page_hit = 1        # 0-10000 credits
#vacuum_cost_page_miss = 2        # 0-10000 credits
#vacuum_cost_page_dirty = 20        # 0-10000 credits
#vacuum_cost_limit = 200        # 1-10000 credits

pg_stat_all_tables(重要)

簡單來講這個表儲存了當前所有表的“健康狀態”,不過比較疑惑的是官方沒有在VACUUM文件頁面引用這個表的相關內容,個人蒐集一些文章才得知這個強的離譜的表,真的是非常坑。

pg_stat_all_tables 表展示了當前系統內所有資料表的健康狀態,透過此表可以檢查當前某個表被索引掃描次數,插入記錄條數,被刪除記錄條數,更新記錄條數等,是一個非常用助於線上問題排查的表(奈何藏得不起眼而且層級也藏得比較深)。

這裡不一一介紹引數了,直接給一個SQL和具體效果更加直觀:

SELECT
    relname 表名,
    seq_scan 全表掃描次數,
    seq_tup_read 全表掃描記錄數,
    idx_scan 索引掃描次數,
    idx_tup_fetch 索引掃描記錄數,
    n_tup_ins 插入的條數,
    n_tup_upd 更新的條數,
    n_tup_del 刪除的條數,
    n_tup_hot_upd 熱更新條數,
    n_live_tup 活動元組估計數,
    n_dead_tup 死亡元組估計數,
     last_vacuum 最後一次手動清理時間,
    last_autovacuum 最後一次自動清理時間,
    last_analyze 最後一次手動分析時間,
    last_autoanalyze 最後一次自動分析時間,
    vacuum_count 手動清理的次數,
    autovacuum_count 自動清理的次數,
     analyze_count 手動分析此表的次數,
    autoanalyze_count 自動分析此表的次數,
    ( CASE WHEN n_live_tup > 0 THEN n_dead_tup :: float8 / n_live_tup :: float8 ELSE 0 END ) :: NUMERIC ( 12, 2 ) AS "死/活元組的比例"
FROM
    pg_stat_all_tables
WHERE
    schemaname = 'public'
ORDER BY n_dead_tup::float8 DESC;

pg_stat_progress_cluster

Progress for VACUUM FULL commands is reported via pg_stat_progress_cluster because both VACUUM FULL and CLUSTER rewrite the table

VACUUM FULL命令的進度是透過pg_stat_progress_cluster報告的,因為VACUUM FULL和CLUSTER命令都會重寫(這張)表。

所以我們可以透過pg_stat_progress_cluster表檢視當前的垃圾回收進度報告。如果我們在執行垃圾回收的時候開啟日誌引數,也是相當於查詢這張表的相關資料。

具體引數這裡就不一個個翻譯了,對於開發人員來說實戰的時候對這個表查一下資料長什麼樣基本就清楚是幹啥的,印象也會更深(沒錯我就是懶)。

Table 28.40. pg_stat_progress_vacuum View

對應原文連結:PostgreSQL: Documentation: 15: 28.4. Progress Reporting

Column TypeDescription
pid integerProcess ID of backend.
datid oidOID of the database to which this backend is connected.
datname nameName of the database to which this backend is connected.
relid oidOID of the table being vacuumed.
phase textCurrent processing phase of vacuum. See Table 28.41.
heap_blks_total bigintTotal number of heap blocks in the table. This number is reported as of the beginning of the scan; blocks added later will not be (and need not be) visited by this VACUUM.
heap_blks_scanned bigintNumber of heap blocks scanned. Because the visibility map is used to optimize scans, some blocks will be skipped without inspection; skipped blocks are included in this total, so that this number will eventually become equal to heap_blks_total when the vacuum is complete. This counter only advances when the phase is scanning heap.
heap_blks_vacuumed bigintNumber of heap blocks vacuumed. Unless the table has no indexes, this counter only advances when the phase is vacuuming heap. Blocks that contain no dead tuples are skipped, so the counter may sometimes skip forward in large increments.
index_vacuum_count bigintNumber of completed index vacuum cycles.
max_dead_tuples bigintNumber of dead tuples that we can store before needing to perform an index vacuum cycle, based on maintenance_work_mem.
num_dead_tuples bigintNumber of dead tuples collected since the last index vacuum cycle.

Autovacuum(重要)

這裡中英文件差距挺大的,建議看英文原版連結。理解自動垃圾回收的一些特點是有必要的,但是個人不太理解為什麼這個東西在文件裡面被藏到了一個層級目錄比較深的角落裡面。

確實是角落,被放到了 Routine Vacuuming 最後一個小節簡單提了一下。也許是不想透露過多細節讓閱讀人員難以理解?

本文也根據PostgreSQL: Documentation: 14: 25.1. Routine Vacuuming接進行簡單翻譯和理解,會跳過一些內容,抽取關鍵部分介紹:

PostgreSQL has an optional but highly recommended feature called autovacuum, whose purpose is to automate the execution of VACUUM and ANALYZE commands.

Postgresql強烈推薦開啟可選功能autovacuum(其實是預設開啟的),他的底層工作原理是定期執行 VACUUM(VACUUM)ANALYZE 對於當前資料庫例項的情況進行分析。

The “autovacuum daemon” actually consists of multiple processes. There is a persistent daemon process, called the autovacuum launcher, which is in charge of starting autovacuum worker processes for all databases.

autovacuum daemon "實際上由多個程式組成。內部實際是持久的守護程式,叫做autovacuum launcher,它負責為所有資料庫啟動autovacuum工作程式。

這裡有點像是執行緒池產生執行緒分配給請求者使用的思路。

Each worker process will check each table within its database and execute and/or as needed. log_autovacuum_min_duration can be set to monitor autovacuum workers activity.autovacuum_naptimeautovacuum_max_workersVACUUMANALYZE

每個工作程式將檢查其資料庫中的每個表並根據需要執行或者不執行。可以設定log_autovacuum_min_duration來監控autovacuum工作者的活動。

PS:最後的引數部分在官方文件模式是有markdown格式的BUG,這裡不糾結,不十分影響閱讀。

If several large tables all become eligible for vacuuming in a short amount of time, all autovacuum.
Note that the number of running workers does not count towards max_connections or superuser_reserved_connections limits.

如果有好幾個大表在很短的時間內都有被自動垃圾回收選中,那麼很可能導致自動垃圾回收的工作進度被拉長。

此外注意因為Worker執行緒的數量是有限的,所以對這些大表做清理的時候可能會導致其他的表垃圾無法被及時回收。

需要注意正在執行的worker的數量不計入max_connections or superuser_reserved_connections限制。

Tables whose relfrozenxid value is more than autovacuum_freeze_max_age transactions old are always vacuumed, Otherwise, if the number of tuples obsoleted since the last VACUUM exceeds the “vacuum threshold”, the table is vacuumed.

這裡實際解釋可以被拆分為兩個點:

  1. relfrozenxid值超過autovacuum_freeze_max_age ,存在舊事務(記錄)的表總是被VACUUM。
  2. 如果自上一次VACUUM以來,淘汰的元組數量超過了 "vacuum threshold" 設定的閾值,則表將被執行VACUUM。

根據這兩個點,可以關聯出兩個計算公式:

  1. 垃圾回收閾值的計算公式
  2. 本次淘汰的元組觸發VACUUM的閾值計算公式

垃圾回收閾值計算公式

下面討論閾值的計算公式。

The vacuum threshold is defined as:

vacuum threshold = vacuum base threshold + vacuum scale factor * number of tuples

清理閾值 = 清理基本閾值 + 清理縮放係數 * 元組數

引數解釋:

threshold(閾值)autovacuum_vacuum_threshold

vacuum scale factor(vacuum 比例係數)autovacuum_vacuum_scale_factor

number of tuples:元組(翻譯叫圖元,個人比較接受元組這個翻譯)數量。關聯變數pg_class`reltuples`

如果自上一次VACUUM以來,淘汰的元組數量超過了 "vacuum threshold" 設定的閾值,則表將被執行VACUUM。

同樣有對應的計算公式

vacuum insert threshold = vacuum base insert threshold + vacuum insert scale factor * number of tuples

清理插入閾值 = 清理基礎插入閾值 + 清理插入縮放係數 * 元組數

autovacuum_vacuum_insert_threshold:基本閾值

autovacuum_vacuum_insert_scale_factor:比例係數

這樣的引數考慮是允許部分的表被標識為 all visible,並且也可以允許元組被凍結,可以減小後續清理的工作需要。

中間部分省略大量內容,太長不看。

分析閾值計算

For analyze, a similar condition is used: the threshold, defined as:

對於垃圾回收的分析,Postgresql 也提供對應的計算公式

analyze threshold = analyze base threshold + analyze scale factor * number of tuples

分析閾值 = 分析基本閾值 + 分析縮放係數 * 元組數

is compared to the total number of tuples inserted, updated, or deleted since the last .ANALYZE

比較的依據是最後一次執行VACUUM之後插入、更新或刪除的元組總數。

其他注意事項

Partitioned tables are not processed by autovacuum.

分割槽表不會被自動 VACUUM 處理。如果需要分割槽表清理,需要手動分析和手動清理。

Temporary tables cannot be accessed by autovacuum. Therefore, appropriate vacuum and analyze operations should be performed via session SQL commands.

臨時表不能被自動 vacuum 處理,vacuum 和 analyze 操作應該使用Session SQL 命令進行處理,這裡說的是直接使用非事務的會話操作。

When multiple workers are running, the autovacuum cost delay parameters (see Section 20.4.4) are “balanced” among all the running workers, so that the total I/O impact on the system is the same regardless of the number of workers actually running.  However, any workers processing tables whose per-table autovacuum_vacuum_cost_delay or autovacuum_vacuum_cost_limit storage parameters have been set are not considered in the balancing algorithm.

這段話意味著如果存在併發Worker工作器並行工作,因為內部使用相同的引數,所以內部會自動進行“重平衡”,所以無論Worker執行緒數量多,對系統的總I/O影響是相同的。

如果在配置檔案中對於某個被清理表設定了autovacuum_vacuum_cost_delay 或者 autovacuum_vacuum_cost_limit引數,那麼自動VACUUM的重平衡的機制不會涉及這些存在配置的表。

If a process attempts to acquire a lock that conflicts with the lock held by autovacuum, lock acquisition will interrupt the autovacuum.

此外如果一個程式試圖獲取與autovacuum持有的鎖相沖突的鎖,autovacuum 將會自動中斷自己獲取的鎖。

下面這一段內容比較重要,建議反覆閱讀,尤其是加鎖的一段內容。

Autovacuum workers generally don't block other commands The default thresholds and scale factors are taken from postgresql.conf, ; see Storage Parameters for more information, If a setting has been changed via a table's storage parameters, that value is used when processing that table; otherwise the global settings are used. See Section 20.10 for more details on the global settings.

自動垃圾回收執行緒不會干擾其他使用者執行緒的正常工作,預設的閾值和比例因子取自postgresql.conf,更多資訊可以檢視 Storage Parameters 。如果透過表的儲存引數改變了某個設定,那麼在處理該表時將使用該值;否則會使用預設的設定,全域性設計可以閱讀: Section 20.10

最後關於鎖衝突的相關概念可以閱讀下面的連結:PostgreSQL: Documentation: 14: 13.3. Explicit Locking

Requested Lock ModeCurrent Lock Mode
FOR KEY SHAREFOR SHAREFOR NO KEY UPDATEFOR UPDATE
FOR KEY SHARE X
FOR SHARE XX
FOR NO KEY UPDATE XXX
FOR UPDATEXXXX

實踐

VACUUM (VERBOSE, ANALYZE) onek;

當然不止這一些內容,後續會單獨寫一篇短文介紹利用垃圾回收和分析器去解決一些生產問題,這裡暫時留坑:

todo [[【Postgresql】Postgresql 資料庫 INSERT 或 UPDATE 大量資料時速度慢的原因分析]]

小結

理解 VACUUM 機制對於排查大資料量批次修改、插入、刪除資料等問題至關重要,Postgresql 在資料清理這一塊模仿了現代程式語言比較容易理解的垃圾回收機制(至少淺層上只要稍加學習可以理解),所以這部分文件個人以目前認知水平還能接受的內容給“意譯”了。

仔細觀察這部分原文會發現涉及了大量的引數配置,這些配置基本上是DBA或者對於Postgresql底層十分感興趣才需要去探究的,當然有可能在某些特殊業務場景下需要調優引數,所以這裡也算是打個預防針等問題來臨的時候有個思路索引來排查問題。

個人英文水平摳腳,很多術語按照自己的認知進行翻譯了,如果有錯誤歡迎指出。如果有什麼地方不懂歡迎一起討論,因為我也不是很懂,哈哈,資料實在是太少了,老外討論這玩意似乎也不多,難頂。

不知道為啥Postgresql這幾年就像是坐火箭一樣更新換代,但是國內使用者問題反饋少的可憐,不過也算是好事情,比隔壁Mysql原地踏步強太多。

寫在最後

Postgresql的學習一直是比較頭痛的東西,參考資料和書籍都比較老,大多數時候只能以官方文件學習和“猜測”為主,遇到一些專案問題不好排查。

PostgreSql十分優秀,也十分受到大廠歡迎,然而實際上“維護成本”非常高,所以更建議多研究研究Mysql,雖然它在國外甚至連Mysql開發者離職後也稱“越做越垃”。

相關文章