在PostgreSQL裡面,為了確保交易被提交之後其變更能夠確實套用在檔案系統上,因此設計了write-ahead log(WAL)的機制。
當Postgres要對檔案進行修改的時候,會將修改位置相對應的block讀進記憶體的shared_buffer緩衝區,然後在其上進行修改。由於緩衝區的變更不一定會馬上套用到磁碟上,同一個block在緩衝區上的內容和磁碟上面的內容會有一段時間是不一致的,這時如果發生意外(例如跳電),transaction對資料所作的變更就會有遺失的風險。
在PostgreSQL上面,WAL以16MB為單位切割成segment,循序寫入WAL紀錄。每一筆WAL紀錄會有一個LSN(log sequence number)編號來記錄操作的先後順序,header(包含作出該筆變更的transaction ID、讀取該筆紀錄所需的resource manager、檢查和以及紀錄長度等)接著是實際的資料。
當Postgres在shared_buffers裡面對data block進行修改,在將變更寫入磁碟之前,每一筆修改都會先寫入write-ahead log,到了我們下transaction COMMIT的時候,Postgres會對系統做fsync()系統呼叫,確保將該筆交易的每一個變更都被確實寫到write-ahead log裡面,才向client回傳transaction committed。
一般情況下Postgres正常退出的話會將在記憶體上的資料變更完整存入磁碟,然後$PGDATA/global/pg_control這個control file會記錄正常關閉的狀態,如果Postgres因為意外停機,下次啟動的時候Postgres讀取這個檔案,就知道需要做WAL的回放。資料庫隨即會進入復原模式,暫時拒絕使用者的操作,直到WAL回放完畢。簡單來說就是:Postgres會讀取Write-ahead log裡面最後一個checkpoint之後的內容來變更磁碟上的資料。
Postgres在適當的時機點會觸發checkpoint,將shared_buffers裡面的內容和磁碟上的資料做同步,這樣可以確定checkpoint做完的時候對資料的變更已經被安全套用,我們就不再需要checkpoint之前的WAL紀錄,可以降低write-ahead log的大小,回放完成的時間也會比較短。
checkpoint執行的時候,首先XACT(在舊版本的Postgres裡面稱為CLOG,紀錄transaction的commit狀態,因為Postgres在回放資料變更的時候不會考慮transaction的commit狀態,可能會有不該出現的資料被寫入硬碟,需要事後過濾)會被立即寫入磁碟。再來由於一下子將所有的dirty buffer寫入磁碟不大可行,所以會先將dirty buffer使用特別的flag標記,之後checkpointer會慢慢將這些被標記的buffer寫到磁碟。其他的server process也會進行這個動作,被寫到磁碟的buffer其flag會被重設,因此每個buffer只會被寫入一次,也不會有checkpoint之後被更動的buffer被寫入。
最後checkpointer會建立一筆WAL紀錄,包含checkpoint開始時的LSN,除此之外最後完成的checkpoint也會被記錄到$PGDATA/global/pg_control裡面。
checkpoint_timeout:在設定的時間超過時執行checkpoint
max_wal_size:在WAL累積超過一定大小之後執行checkpoint
checkpoint_timeout & checkpoint_completion_target:設定這兩個可以讓Postgres延遲將dirty buffer刷入磁碟的時間,降低checkpoint對I/O的衝擊。例如checkpoint_completion_target設為 0.2 以及
checkpoint_timemout設300秒,可以讓Postgres在checkpoint觸發的時候有60秒來把dirty buffers慢慢寫入磁碟。