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。

(ps: 某些企業級ssd可以通過電容殘餘的電量,将disk cache裡的資料持久化下來,但是請不要相信所有磁盤都有這個功能)
2. 當開啟了易失緩存時,如果寫資料的塊大小小于或等于磁盤原子寫的大小(即"原子寫"),掉電時也可能出現partial write。
對于mysql來說,redo的寫為512位元組的,其中包含12個位元組的頭資訊,4個位元組的校驗資訊。
這個怎麼了解呢,為什麼沒有對齊則可能出現。
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的内部機制,可以參考以上文檔以及源碼。
<a href="http://info.flagcounter.com/h9v1">count</a>