天天看點

PostgreSQL 可靠性分析 - 關于redo block原子寫

digoal

2016-10-11

postgresql , redo , redo block 原子寫 , 可靠性分析

postgresql 可靠性與大多數關系資料庫一樣,都是通過redo來保障的。

群裡有位童鞋問了一個問題,為什麼postgresql的redo塊大小預設是8k的,不是512位元組。

這位童鞋提問的理由是,大多數的塊裝置扇區大小是512位元組的,512位元組可以保證原子寫,而如果redo的塊大于512位元組,可能會出現partial write。

那麼postgresql的redo(wal) 塊大小設定為8kb時,靠譜嗎?本文将給大家分析一下。

1. 當開啟了易失緩存時,如果寫資料的塊大小大于磁盤原子寫的大小(通常為512位元組),掉電則可能出現partial write。

例如disk cache,沒有掉電保護,而且作業系統的fsync接口不感覺disk cache,如果你調用了fsync,即使傳回成功,資料其實可能還在disk cache裡面。

當發生掉電時,在disk cache裡的資料會丢失掉,如果程式寫一個8k的資料,因為磁盤的原子寫小于8k,則可能出現8k裡有些寫成功了,有些沒有寫成功,即partial write。

PostgreSQL 可靠性分析 - 關于redo block原子寫

(ps: 某些企業級ssd可以通過電容殘餘的電量,将disk cache裡的資料持久化下來,但是請不要相信所有磁盤都有這個功能)

2. 當開啟了易失緩存時,如果寫資料的塊大小小于或等于磁盤原子寫的大小(即"原子寫"),掉電時也可能出現partial write。

對于mysql來說,redo的寫為512位元組的,其中包含12個位元組的頭資訊,4個位元組的校驗資訊。

這個怎麼了解呢,為什麼沒有對齊則可能出現。

PostgreSQL 可靠性分析 - 關于redo block原子寫

1. 前面提到了,如果沒有對齊,并且開啟了易失緩存,原子寫是沒有用的,同樣會出現partial write。

2. 如果沒有對齊,會造成寫放大,本來寫512位元組的,磁盤上會造成寫1024位元組(将兩個扇區資料讀出來再與要寫的資料合并, 分成兩個扇區回寫)。

1. 開啟易失緩存時,原子寫一樣會丢失易失緩存中的資料。

2. 當未對齊時,原子寫并不是真的原子寫。

資料庫隻靠redo的原子寫,如果不考慮以上兩個因素,起不到保證資料可靠性和一緻性的作用。

1. shared buffer 中的dirty page在write前,必須要保證對應的redo已經持久化(指已經落到非易失存儲媒體)。

2. 在檢查點後出現的髒頁,必須要在redo中寫dirty page的full page。

這2條保證的是資料檔案的一緻性。

3. 在不考慮standby的情況下,當設定為同步送出的事務在事務送出時,必須等待事務産生的redo已持久化才傳回(指已經落到非易失存儲媒體)。

參考

<a href="https://github.com/digoal/blog/blob/master/201610/20161006_02.md">《postgresql 9.6 同步多副本 與 remote_apply事務同步級别》</a>

4. 當設定為異步送出的事務在事務送出時,不需要等待事務産生的redo持久化。

由于有第一條的保護,是以即使使用異步事務,丢失redo buffer中的資料後,也不會出現不一緻(比如一半送出,一半未送出)的情況,僅僅丢失redo buffer中未送出的事務而已。

一緻性由postgresql mvcc的機制來保證,不會讀到髒資料。

1. 在使用cow的檔案系統(如btrfs, zfs)時,可以關閉full page write,因為這種檔案系統可以保證不會出現partial write。

2. 對齊,可以避免寫放大的問題。

3. 不要使用易失緩存,但是可以使用有掉電保護的易失緩存。

postgresql認為系統提供的fsync調用是可靠的,即寫到了持久化的存儲。

如果連fsync都不可靠了,管它是不是原子寫,都是不可靠的。

包括directio在内(postgresql支援redo使用directio),也無法感覺disk cache,是以請慎重。

首先,前面已經分析了,原子寫并不能抵禦易失存儲導緻的丢資料。

1. postgresql redo block是有checksum的,可以保證塊的一緻性,不會apply不一緻的塊。

2. 事務送出時,傳回給使用者前,一定會保證redo已持久化。

是以使用者收到回報的事務,一定是持久化的,不可能存在partial write。

而沒有收到回報或未結束的事務,才有可能包含partial write,那麼問題就簡化了:

這些沒有收到回報或未結束的事務産生的redo 出現partial write會不會導緻資料不一緻?

回答是不會,參考前面 "postgresql如何保證資料庫可靠性",mvcc機制可以保證這些 。

資料庫參數

産生測試資料

模拟壓力測試

觀測到産生了一些xlog,約200秒後,測試過程中強制停庫,下次啟動會進入恢複狀态

記錄接下來要纂改的redo檔案以及之前的檔案最後的内容

纂改的前一個檔案的末尾的一些内容,用于判斷已持久化的記錄

能看到幾筆commit rec就行了

被纂改的檔案的頭部的内容,用于判斷未持久化的記錄

這裡顯示的都是将要纂改掉,對pg來說就是未持久化的事務,資料庫恢複後是不會顯示的.

纂改redo

啟動資料庫,進入恢複狀态,當讀到checksum不一緻的block,停止繼續往前,也就是說資料庫恢複到這裡截至。

未恢複的事務造成的變更,對使用者不可見。

驗證

通過檢驗。

如果每産生一筆redo都要fsync,性能就差了,是以fsync實際上是有排程的。

redo buffer的作用就是減少fsync的次數。

1. 當wal writer sleep超過設定的sleep時間(通常設定為10毫秒)時,觸發fsync,将redo buffer中已寫完整的block持久化到redo file。

2. 當wal writer write(異步寫)的位元組數超過配置的門檻值(wal_writer_flush_after)時,觸發fsync,将redo buffer中已寫完整的block持久化到redo file。

3. 當事務結束時,檢查wal write全局變量,lsn是否已flush,如果沒有落盤,則觸發fsync。

4. 第三種情況,如果開啟了分組送出,則多個正在送出的事務隻會請求一次fsync。

5. 當redo 日志檔案發生切換時,會觸發fsync,確定檔案持久化。

src/backend/postmaster/walwriter.c

src/backend/access/transam/xlog.c

如果要深入了解postgresql redo的内部機制,可以參考以上文檔以及源碼。

PostgreSQL 可靠性分析 - 關于redo block原子寫

<a href="http://info.flagcounter.com/h9v1">count</a>