25 May 2007

PostgreSQL 簡單備份的筆記

目標:當資料庫完全損毀時(例如 RAID 掛了),能夠靠備份完全回復到最少十分鐘前的資料。

規畫:

  • Ubuntu 7.04
  • PostgreSQL 8.2
  • 每天做一次整個資料庫的完整備份
  • 每十分鐘做一次異動的備份
  • 資料庫本身是放在 RAID1 上,而備份則是放到外部的網路硬碟
  • 每天自動清除過期的異動備份

PostgreSQL 的異動備份最小的 size 是 16MB,不論異動量是多少。按上面的規畫每10 分鐘就備份一次,換算下來,一整天最少就有 16MB * 6 * 24 = 2.3GB 的備份量。如果你的資料相當重要,那間格要壓到 1 分鐘或甚至 30 秒以下,光一天的備份就會爆增到 20GB 以上。

備份的步驟

  • 資料目錄:
      來源 備份路徑
    完整的資料庫 /var/lib/postgresql/8.2/main /backup/main_sync
    異動紀錄 (WAL, write ahead log) /var/lib/postgresql/8.2/main/pg_xlog /backup/archivedir

  • 開啟 posgresql 自動連續備份的機制:請修改 /etc/postgresql/8.2/main/postgresql.conf

    # 異動備份用 cp 複製到 /backup/archivedir 這個目錄裡
    archive_command = 'test ! -f /backup/archivedir/%f && cp %p /backup/archivedir/%f'
    
    # 設定 600 秒(10分鐘) 備份一次
    archive_timeout = 600
    

  • 重新啟動 Postgresql (/etc/init.d/postgresql-8.2 restart ) 後,postgresql 便會開始不斷產生 WAL 檔到 pg_xlog 裡。然後每十分鐘會執行上面設定的 archive_command 將 WAL 檔移動到 /backup/archivedir 裡 (先複製再刪除)。
  • 進行完整的資料庫備份 (用 postgres 這個 unix 使用者執行):

    • psql -ec "SELECT pg_start_backup('complete_backup');"
    • rsync -av --exclude="pg_xlog" /var/lib/postgresql/8.2/main/ /backup/main_sync
    • psql -ec "SELECT pg_stop_backup();"

    完整的備份要使用 pg_start_backup() 和 pg_stop_backup() 兩個函數標示備份的期間,而實體的資料庫備份則是用 rsync 直接同步兩個目錄 (除了 pg_xlog 之外)。待 pg_stop_backup() 執行完後,postgresql 會馬上產生一個新的 WAL 檔,並且將該 WAL 視為下次重建資料的起點。
  • 上述三步驟的範例 script: continuous_archive.sh

    #!/bin/bash
    backup_dir="/backup"
    master_backup="$backup_dir/main_sync"
    mailto="myadmin_mail@gmail.com"
    pg_version="8.2"
    
    started=false
    
    function exitIfFail() {
       ## $? is last command's exit status
       if [ $? -ne 0 ]; then 
          echo 'archive failed' | mutt -s 'continue archive failed' $mailto
    
          # if backup started, we should force stop backup
          if [ started ]; then 
             psql -ec "SELECT pg_stop_backup();"
          fi
    
          exit $? ;
       fi
    }
    
    ## initialize backup
    psql -ec "SELECT pg_start_backup('$master_backup');"
    exitIfFail;
    started=true
    
    ## master backup
    rsync -av --exclude="pg_xlog" /var/lib/postgresql/$pg_version/main/ $master_backup
    
    exitIfFail;
    
    ## create blank dir because we don't sync pg_xlog dir
    mkdir -p $master_backup/pg_xlog/archive_status
    
    ## finalized backup
    psql -ec "SELECT pg_stop_backup();"
    exitIfFail;
    started=false
    
    ## flush
    sync;
    
    purge_ts_file=$backup_dir/purge_ts_file
    
    ## delete fews days ago WAL in archivedir
    if [ -f $purge_ts_file ]; then
       find $backup_dir/archivedir ! -newer $purge_ts_file -type f -delete
       exitIfFail;
    fi
    
    ## udpate next purge date, though it's 2 days ago, we will use this file next day
    ## so actually is deleting 3 days old files
    purge_date="$(date --date='2 days ago' +'%m%d%H%M.%S')"
    touch -t $purge_date $backup_dir/purge_ts_file
    
    exit 0
    

    上面的 script 的後半部是清除過期 (三天前) 的異動備份。而備份過程中發生任何錯誤則直接中斷,並使用 mutt 寄信通知。將此 script 以 crontab 設定每日執行即可。
  • 接下來檢查 /backup/archivedir 和 /var/log/postgresql 裡的 log 看有沒有異狀就 ok 了。

重建的步驟

  • 關閉 postgresql server (/etc/init.d/postgresql-8.2 stop)
  • 將損壞的資料庫移到別處 (mv /var/lib/postgresql/8.2/main /temp)
  • 複製完整的備份 /backup/main_sync 回 /var/lib/postgresql/8.2/main
  • 建立空白目錄 /var/lib/postgresql/8.2/main/pg_xlog/archive_status
  • 建立 /var/lib/postgresql/8.2/main/recovery.conf 內容為:

    # 指定回復時到 /backup/archivedir 裡複製異動 log
    restore_command = 'cp /backup/archivedir/%f %p'
    

  • 啟動 postgresql server,server 會使用 restore_command 複製異動 log,並自動重建資料。檢查 /var/log/postgresql 的 log 看有無異常。全部完成後 recovery.conf 會被改名為 recovery.done
  • 移除已經完成重建的異動備份 (mv /backup/archivedir/* /temp)
  • 手動重建 index (因為目前 8.2 版的 WAL 不包含 index 的內容)

    psql -U mydbuser mydb -ec "reindex mydb"
    

  • 檢查一切都 ok 後,移開之前的備份,從新設定備份的程序。

參考資料:Postgresql 手冊