問題出現
我們在線上巡檢中發現,一個實例的pg_xlog目錄,增長到4G,很是疑惑。剛開始懷疑是日志歸檔過慢,日志堆積在pg_xlog目錄下面,未被清除導致。於是檢查歸檔目錄下的文件,內容如下。但發現新近完成寫入的日志文件都被歸檔成功了(即在pg_xlog/archive_status里面,有對應的xxx.done文件)。
ls -lrt pg_xlog ... -rw------- 1 xxxx xxxx 16777216 Jun 14 18:39 0000000100000035000000DE -rw------- 1 xxxx xxxx 16777216 Jun 14 18:39 0000000100000035000000DF drwx------ 2 xxxx xxxx 73728 Jun 14 18:39 archive_status -rw------- 1 xxxx xxxx 16777216 Jun 14 18:39 0000000100000035000000E0 ls -lrt pg_xlog/archive_status ... -rw------- 1 xxxx xxxx 0 Jun 14 18:39 0000000100000035000000DE.done -rw------- 1 xxxx xxxx 0 Jun 14 18:39 0000000100000035000000DF.done -rw------- 1 xxxx xxxx 0 Jun 14 18:39 0000000100000035000000E0.done
仔細觀察,奇怪的是,pg_xlog里面還有一些日志文件,其文件名對應了還沒產生的日志號!如下所示,當前正在被寫入的日志號為100000035000000E0左右,卻出現了名為1000000360000000C的日志文件名,更蹊蹺的是,其修改時間還在很早以前,就是說不是新近創建或修改過的,如下面的文件修改或創建時間是在當前時間的一個小時之前:
ls -lrt pg_xlog .... -rw------- 1 xxxx xxxx 16777216 Jun 14 17:37 00000001000000360000000C -rw------- 1 xxxx xxxx 16777216 Jun 14 17:37 000000010000003600000014 ....
這是怎么回事呢?難道是“幽靈日志”?下面我們要搞清楚兩個問題:1)為什么會出現”幽靈日志“?2)PG正常情況下日志空間大小是多少?
PG日志創建清理機制
要回答上述問題,需要先摸清PG的日志創建、保持和清理機制。與此直接相關的模塊有:日志寫入(WAL writer)進程和日志歸檔(archiver)進程。其實檢查點(checkpointer)進程和日志發送進程(WAL sender)也與此有關。
WAL writer負責異步把WAL日志刷入磁盤;與此同時,其他普通后台進程,也可能會同步的將WAL日志刷入磁盤,我們先從分析它們入手。從代碼里面不難看出,它們將日志寫入新的日志文件時,有如下函數調用:
XLogWrite -> XLogFileInit ->BasicOpenFile
BasicOpenFile負責打開一個新的日志文件,如果文件不存在,則新建文件。而其代碼注釋里面提到“Try to use existent file (checkpoint maker may have created it already)”,即打開的文件可能已經被checkpointer進程創建。
於是我們將目光轉向checkpointer。其主要函數CheckpointerMain的邏輯如下:
- 檢查是否有checkpoint request信號;
- 檢查是否checkpoint timeout時間已到;
- 調用CreateCheckPoint做檢查點操作;
- 調用WaitLatch等待checkpoint timeout或checkpoint request信號。
重點內容都在CreateCheckPoint
函數中,其邏輯如下:
- 檢查上次檢查點后是否有WAL日志寫入,如果沒有直接返回;
- 調用CheckPointGuts將WAL日志fsync到磁盤;注意其中的CheckPointBuffers函數,會根據checkpoint_completion_target的值做一定的delay,使fsync操作的完成時間占兩個檢查點之間時間間隔的比例,約為checkpoint_completion_target;
- 在WAL中插入檢查點日志信息;
- 取系統前一次檢查點的日志位置指針,即此指針之前的日志文件,都可以刪除了;
- 由KeepLogSeg根據wal_keep_segments和replication slot的情況計算要額外保留的日志;
- 由RemoveOldXlogFiles做真正的日志刪除,而神奇的是RemoveOldXlogFiles並未實際刪除文件,而是將其回收,即將老文件rename成新文件,做了日志文件預分配;
- 完成檢查點返回。
可以看到,在這里出現日志刪除、預分配等邏輯。也就是說PG的日志文件可能是在做檢查點操作時預分配的!預分配的文件名使用了“未來”的目前還不存在的日志號,這就解釋了我們之前遇到的“幽靈日志”情況,也回答了我們的第一個問題。
當然,需要說明的是,日志的保留和刪除還和是否被archiver進程歸檔成功有關。
日志空間大小
繼續看第二個問題。前面提到的日志空間暴增讓我們如臨大敵,那么PG日志到底最多會占用多少空間?我們遇到的漲到3G情況正常嗎?
從日志清理邏輯(重點是KeepLogSeg
和RemoveOldXlogFiles
函數)的分析,我們得到下面的結論:
- 日志的刪除和預分配只在檢查點剛完成時進行;
- 刪除時,保證上一次檢查點之后到現在的日志不會被刪除;
- 保證從目前日志位置往前wal_keep_segments個日志文件不會刪除;
- 預分配的過程是,對所有不再需要的舊文件重命名為一個未來的日志號,直到預分配的文件數量達到XLOGfileslop,即
2*checkpoint\_segments + 1
。checkpoint_segments為一個可配置的參數,控制了兩個檢查點間產生的日志文件數量。
另外,為討論方便,下面我們先做如下假設:
- 有足夠多(即大於2*checkpoint_segments + 1)的不再需要的舊日志文件,可以用於預分配;
- 每次檢查點操作完成的時間,正好占兩個檢查點之間時間間隔的checkpoint_completion_target(線上目前我們設為0.9)。
設某次檢查點操作完成時的時間點為A,則此時做日志預分配的情形如下圖所示:
候選被回收的文件是在時間點C之前的、並且大於wal_keep_segments個文件間隔的文件;這些文件將重命名為預分配文件,文件號為從A對應的日志開始遞增,直到達到2*checkpoint_segments + 1
個文件為止。
做檢查點操作過程中,是不斷產生新日志文件的,而且兩次檢查點之間的日志文件數為一個穩定的值,即checkpoint_segments。因此,在時間點B到A之間產生的日志數約為
checkpoint_segments * checkpoint_completion_target
待A時間點預分配完日志文件,並刪除其他不需要的日志后,新產生的日志將使用預分配空間,日志空間不會增大,日志空間大小達到一個穩定狀態。而此時日志的空間至少為:保留的日志空間 + 預分配空間 + 正在被寫入的那個文件,即為:
max(wal_keep_segments, checkpoint_segments + checkpoint_segments*checkpoint_completion_target) + 2 * checkpoint_segments + 1 + 1
這就是在日志大小達到穩定狀態時,所能達到的最大值。所謂“穩定狀態”是指,一旦達到這個狀態,優先使用預分配空間,一般不會增大;即使日志文件繼續增加,也會被刪除(如果archiver和wal sender都正常工作的話)。而日志大小也不會明顯減少,因為處於預分配狀態的日志數量、前一次檢查點到當前時間點的日志量都沒有大的變化。
回到我們的問題,PG的日志空間占用的正常值,可以用上面的公式計算出來。如果wal_keep_segments為80,checkpoint_segments為64,checkpoint_completion_target為0.9,那么根據公式計算結果為4.02G。即日志空間增加到4G也是正常的。並且可以通過減小checkpoint_segements的值,減少日志空間占用。
幾個問題
通過上面分析得出的公式,我們在處理日志時遇到的一些問題就迎刃而解了,例如:
Q: 增加wal_keep_segments會增大日志空間嗎?
A: 如果增加wal_keep_segments后,其值仍小於(checkpoint_segments + checkpoint_segments * checkpoint_completion_target),則增加wal_keep_segments並不會增大日志占用空間。
Q: checkpoint_segments與日志空間大小有什么關系?
A: 在wal_keep_segments較小時,checkpoint_segments對日志空間占用有至關重要的影響。日志空間大小基本上可以用4倍checkpoint_segments來估算出來。但當wal_keep_segments較大時,比如是checkpoint_segments的10倍,則checkpoint_segments對日志空間大小的影響相對就小很多了。
思考題
上面的分析中,我們做了兩點假設。一個是系統中有足夠多的舊日志可供回收,這種情況會出現嗎(提示:archiver進程或replication slot對日志刪除的影響)?另一個是,檢查點操作會及時完成,那么如果檢查點操作未及時完成,會出現什么情況?會導致日志空間占用比我們的公式更大嗎?
參考:
http://mysql.taobao.org/monthly/2015/06/08/
注:
1、XLOGfileslop:預分配wal文件數量,為:2*checkpoint_segments + 1
2、最后的兩個思考題,還未想出答案。