|
這段時(shí)間對(duì) PostgreSQL 的備份恢復(fù)進(jìn)行了一些研究, 有一些心得和大家分享一下.
我們知道, PostgreSQL 擁有 WAL(預(yù)寫式日志) 已經(jīng)有一段時(shí)間了. WAL 的一個(gè)重要好處就是能在系統(tǒng)崩潰(數(shù)據(jù)庫崩潰甚至操作系統(tǒng)崩潰)的情況下, 仍然能夠保證數(shù)據(jù)的安全. 理想情況下就是恢復(fù)到系統(tǒng)崩潰前一刻的一致狀態(tài).
WAL 是如何實(shí)現(xiàn)這一點(diǎn)的呢? 這里簡單探討一下.
PostgreSQL 數(shù)據(jù)目錄中包括一個(gè)子目錄叫 pg_xlog, 這里包含一些很 "整齊" 的文件 (文件名全都是16進(jìn)制的數(shù), 大小都是16MB(默認(rèn)情況下)). 這些文件是聯(lián)機(jī)重做日志文件, 也就是很多 PostgreSQL 文檔中說的 XLog. 預(yù)寫式日志就表現(xiàn)在對(duì) XLog 操作上. 當(dāng)一個(gè)事務(wù)要提交, 已被修改的數(shù)據(jù) 必須先被寫到(嚴(yán)格來說應(yīng)該是追加)到 XLog 中, 事務(wù)才能標(biāo)識(shí)為 "已提交". 這樣, 就算主數(shù)據(jù)文件在崩潰中已經(jīng)包含不完整的數(shù)據(jù)了, 仍然可以通過下面 的方法恢復(fù)到一致狀態(tài):
1 找到前一個(gè)一致的數(shù)據(jù)庫狀態(tài)點(diǎn)(這稱為CheckPoint) 2 將 XLog 以快進(jìn)的方式重新施加到主數(shù)據(jù)文件上, 直到崩潰前的時(shí)刻.
從這個(gè)角度來將, PostgreSQL 已經(jīng)做得非常好了, 但仍然有一些問題. 比如, 如果介質(zhì)(磁盤)發(fā)生故障, 整個(gè)數(shù)據(jù)庫文件, 包括主數(shù)據(jù)文件和日志 都不能讀取, 又該怎么辦呢?
目前, 我們只能定時(shí)通過 pg_dump 等工具把整個(gè)數(shù)據(jù)庫 dump 下來, 或者 關(guān)閉數(shù)據(jù)庫, 將數(shù)據(jù)目錄整個(gè)復(fù)制到另外的地方. 然后使用這樣的備份來恢復(fù)由介質(zhì)故障引起的數(shù)據(jù)庫災(zāi)難. 很顯然, 這不能滿足我們的要求, 通常, 我們都不可能以非常高 的頻率進(jìn)行 pg_dump. 一旦發(fā)生災(zāi)難, 就最多恢復(fù)到上一次執(zhí)行 pg_dump 的時(shí)刻了.
沒有更好的辦法了嗎?
如果我們能對(duì)數(shù)據(jù)庫的做不間斷的增量備份, 不就可以到達(dá)我們的目的了嗎? 這個(gè)想法到是好, 可怎么捕捉對(duì)數(shù)據(jù)文件做的修改(同時(shí)還不要忘記事務(wù)的原子性)? 對(duì)數(shù)據(jù)文件的修改是分布于整個(gè)數(shù)據(jù)文件各處的, 很難對(duì)它們進(jìn)行 所以, 這個(gè)方法是不現(xiàn)實(shí)的.
現(xiàn)實(shí)的方法還是要通過 WAL 系統(tǒng)來實(shí)現(xiàn). 請(qǐng)注意前面我們已經(jīng)討論過的, 對(duì) XLog 的寫實(shí)際上是追加. 這一點(diǎn)使得要增量備份 XLog 成為可能. 目前, PostgreSQL 為了能限制 XLog 的大小, 采用了多個(gè)段(也就是多個(gè)文件)的方式, 循環(huán)利用磁盤空間 -- 當(dāng)寫滿前一個(gè) XLog 文件, 就產(chǎn)生一個(gè)新的, 并且讓 文件名代表 XLog 的編號(hào), 同時(shí), 如果可能, 刪除過期的 XLog 文件. 如果我們能在 PostgreSQL 刪除過期的 XLog 之前將它們復(fù)制到另外一個(gè)磁盤甚至其他 計(jì)算機(jī), 不就能夠?qū)崿F(xiàn)增量備份日志了嗎? 當(dāng)災(zāi)難發(fā)生的時(shí)候, 就可以在一個(gè)完整備份的基礎(chǔ)上, 連續(xù)施加備份的 XLog 進(jìn)行 redu, 直至恢復(fù)到最后一次歸檔(復(fù)制到其他目錄或計(jì)算機(jī))的日志.
實(shí)際上, 這種方法也正是 Oracle 數(shù)據(jù)庫的歸檔模式所采用的方法.
下面這個(gè)實(shí)驗(yàn)可以加深理解
1 初始化數(shù)據(jù)庫目錄
$ initdb -D db
2 創(chuàng)建數(shù)據(jù)庫
$ pg_ctl -D db start $ createdb test $ psql test test=# create table t(a int); test=# insert into t values(1); test=# insert into t values(2); test=# insert into t values(3); test=# \q
3 全備份數(shù)據(jù)庫: 在不關(guān)閉數(shù)據(jù)庫(也就是說, 不要運(yùn)行 pg_ctl -D db stop) 的情況下復(fù)制數(shù)據(jù)庫目錄. 注意, 這里采用了一種非常規(guī)手段, 僅僅是為了實(shí)驗(yàn), 不要在正式應(yīng)用中使用. 目的是為了讓將來的恢復(fù)能自動(dòng)開始.
$ cp -a db db.backup
4 繼續(xù)修改數(shù)據(jù)庫
$ psql test test=# insert into t values(100); test=# insert into t values(200); test=# insert into t values(300); test=# \q
5 備份日志(XLog)文件 (由于修改量很小, 實(shí)際上只有一個(gè)日志文件)
$ mkdir pg_xlog $ cp db/pg_xlog/* pg_xlog
6 模擬災(zāi)難
$ pg_ctl -D db stop $ rm -rf db # 可以不用真的刪除, 只是認(rèn)為它已經(jīng)不存在了
7 進(jìn)行災(zāi)難恢復(fù)
$ cp -a db.backup db.restore $ cp -f pg_xlog/* db.restore/pg_xlog $ postmaster -D db.restore # 沒有用 pg_ctl 啟動(dòng), # 為了更清楚看到日志(此日志非彼日志)輸出 LOG: database system was interrupted at 2004-04-15 18:12:47 CST LOG: checkpoint record is at 0/9B1058 LOG: redo record is at 0/9B1058; undo record is at 0/0; shutdown TRUE LOG: next transaction ID: 536; next OID: 17142 LOG: database system was not properly shut down; automatic recovery in progress LOG: redo starts at 0/9B1098 LOG: record with zero length at 0/9D4458 LOG: redo done at 0/9D4434 LOG: database system is ready
$ psql test # 另外開一個(gè)控制臺(tái) test=# select * from t; a ----- 1 2 3 100 200 300 (6 rows)
可見, 數(shù)據(jù)庫已經(jīng)已經(jīng)恢復(fù)了到了災(zāi)難發(fā)生前的一刻.
當(dāng)然, 這個(gè)實(shí)驗(yàn)數(shù)據(jù)量很小只產(chǎn)生并復(fù)制了一個(gè)日志文件, 而且復(fù)制的日志文件 還是當(dāng)前正在工作的, 和前面描述的不完全一致. 更深入的實(shí)驗(yàn)大家可以下來做.
實(shí)際的聯(lián)機(jī)熱備份(也叫PITR(Point In Time Recovery))還有很多細(xì)節(jié), 不過 總的來說, PostgreSQL 離實(shí)現(xiàn)聯(lián)機(jī)熱備份已經(jīng)很近很近了! 也許下一個(gè)版本我們就能看到 這個(gè)令人興奮的功能了. 我們熱切地期待著!
她已經(jīng)為我們做了這么多, 我們能為她做點(diǎn)什么呢?
以上內(nèi)容僅代表我自己的理解, 不正之處敬請(qǐng)指出.
kernel 2004.4.15
轉(zhuǎn)自: http://bbs.pgsqldb.com/index.php?t=msg&th=3739&start=0
|