WAL(Write Ahead Logging) †
- Write Ahead Loggingの略で、トランザクションログのことである。
- データベースでは、一般にデータの書き込みの前にログに変更内容を書き出す。
これにより、データベースが何らかの異常でダウンしても、WALログから障害発生前の状態まで復旧することができる。
WALの構造 †
PostgreSQLでは、データベースクラスタの下のpg_wal(ver10以降、以前はpg_xlog)にWALログが書かれる。
論理構造 †
- WALは、通常16MBのファイルで管理される。
- WALレコードは、タイムライン+ログID+ログID内のオフセットで決まる。
WALファイル名は、これらの値で構成される。項目 サイズ tli(タイムラインID) 4byte xlogid(ログID) 4byte xrecoff(オフセット) 4byte タイムラインID:1 セグメント0 00000001 00000000 00000000 start xlogid=0 セグメント1 00000001 00000000 00000001 セグメント2 00000001 00000000 00000002 ... セグメント255 00000001 00000000 000000FE end xlogid=0 セグメント256 00000001 00000001 00000001 start xlogid=1 ... -- ログIDごとのセグメント数 XLogSegmentsPerXLogId(wal_segsz_bytes) = 256 -- WALファイル名 例:タイムライン = 1、LSN = 0/14000028 の時 xlogid = 0 xrecoff = 335544360 オフセットNO = 335544360 - 1 / 16MB = 20 = 0x14 ファイル名 = 000000010000000000000014
参考
- access/xlog_internal.h - https://git.postgresql.org/gitweb/?p=postgresql.git;a=tree;h=refs/heads/REL_12_STABLE
- Write Ahead Logging — WAL - on http://www.interdb.jp/pg/pgsql09.html
WALログ内部構造 †
- ページ(通常8k)ごとに管理されている。ページは、ヘッダ+WALレコードで構成される。
- セグメントファイルの最初のヘッダは、
XLogLongPageHeaderData
。以降は、XLogPageHeaderData
となる。 - WALレコードは、
XLogRecord
とデータである。データは、バイト境界にアライメントされている。
セグメントファイルをまたぐ場合もある。その場合は、レコードヘッダにデータが連続していることを示すビットがセットされる。(XLP_FIRST_IS_CONTRECORD
)/* When record crosses page boundary, set this flag in new page's header */ #define XLP_FIRST_IS_CONTRECORD 0x0001 /* This flag indicates a "long" page header */ #define XLP_LONG_HEADER 0x0002 /* This flag indicates backup blocks starting in this page are optional */ #define XLP_BKP_REMOVABLE 0x0004 /* All defined flag bits in xlp_info (used for validity checking of header) */ #define XLP_ALL_FLAGS 0x0007
参考src/include/access/xlog_internal.h - https://git.postgresql.org/gitweb/?p=postgresql.git;a=tree;h=refs/heads/REL_12_STABLE
XLOGに関するマクロ †
XLOGのバイトポジションやセグメント、ファイル名を求めるためのマクロがある。
以下、代表的なマクロの使用例である。
#define FRONTEND 1 #include "postgres.h" #include "access/xlog_internal.h" int main(int argc, char **argv) { uint32 xrecoff; XLogSegNo segno; XLogRecPtr xlogptr; TimeLineID tli; char fname[MAXFNAMELEN]; // XLogSegmentsPerXLogId(wal_segsz_bytes) printf("XLogSegmentsPerXLogId = %zu\n", XLogSegmentsPerXLogId(DEFAULT_XLOG_SEG_SIZE)); // XLogSegNoOffsetToRecPtr(segno, offset, wal_segsz_bytes, dest) XLogSegNoOffsetToRecPtr(1, 0, DEFAULT_XLOG_SEG_SIZE, xlogptr); printf("XLogSegNoOffsetToRecPtr(1, 0, DEFAULT_XLOG_SEG_SIZE, xlogptr) = %zu\n", xlogptr); // XLogSegmentOffset(xlogptr, wal_segsz_bytes) xlogptr = DEFAULT_XLOG_SEG_SIZE + 24; xrecoff = XLogSegmentOffset(xlogptr, DEFAULT_XLOG_SEG_SIZE); printf("XLogSegmentOffset(DEFAULT_XLOG_SEG_SIZE + 24, DEFAULT_XLOG_SEG_SIZE): xrecoff = %u\n", xrecoff); // XLByteToSeg(xlrp, logSegNo, wal_segsz_bytes) xlogptr = DEFAULT_XLOG_SEG_SIZE * 2 - 1; XLByteToSeg(xlogptr, segno, DEFAULT_XLOG_SEG_SIZE); printf("LByteToSeg(DEFAULT_XLOG_SEG_SIZE * 2 - 1, segno, DEFAULT_XLOG_SEG_SIZE): segno = %zu\n", segno); xlogptr = DEFAULT_XLOG_SEG_SIZE * 2; XLByteToSeg(xlogptr, segno, DEFAULT_XLOG_SEG_SIZE); printf("XLByteToSeg(DEFAULT_XLOG_SEG_SIZE * 2, segno, DEFAULT_XLOG_SEG_SIZE): segno = %zu\n", segno); // XLByteToPrevSeg(xlrp, logSegNo, wal_segsz_bytes) xlogptr = DEFAULT_XLOG_SEG_SIZE * 2; XLByteToPrevSeg(xlogptr, segno, DEFAULT_XLOG_SEG_SIZE); printf("XLByteToPrevSeg(DEFAULT_XLOG_SEG_SIZE * 2, segno, DEFAULT_XLOG_SEG_SIZE): segno = %zu\n", segno); // XRecOffIsValid(xlrp); if (XRecOffIsValid(DEFAULT_XLOG_SEG_SIZE)) printf("%s\n", "XRecOffIsValid(DEFAULT_XLOG_SEG_SIZE) is true"); else printf("%s\n", "XRecOffIsValid(DEFAULT_XLOG_SEG_SIZE) is false"); // XLogFileName(fname, tli, logSegNo, wal_segsz_bytes) XLogFileName(fname, 1, 1, DEFAULT_XLOG_SEG_SIZE); printf("XLogFileName(fname, 1, 1, DEFAULT_XLOG_SEG_SIZE): fname = %s\n", fname); XLogFileName(fname, 2, 2, DEFAULT_XLOG_SEG_SIZE); printf("XLogFileName(fname, 2, 2, DEFAULT_XLOG_SEG_SIZE): fname = %s\n", fname); // XLogFileNameById(fname, tli, log, seg) XLogFileNameById(fname, 1, 0, 1); printf("XLogFileNameById(fname, 1, 0, 1): fname = %s\n", fname); // IsXLogFileName(fname) strcpy(fname, "000000010000000000000001"); if (IsXLogFileName(fname)) printf("IsXLogFileName(\"%s\") is true\n", fname); else printf("IsXLogFileName(\"%s\") is false\n", fname); strcpy(fname, "00000001000000000000000#"); if (IsXLogFileName(fname)) printf("IsXLogFileName(\"%s\") is true\n", fname); else printf("IsXLogFileName(\"%s\") is false\n", fname); // IsPartialXLogFileName(fname) strcpy(fname, "000000010000000000000001.partial"); if (IsPartialXLogFileName(fname)) printf("IsPartialXLogFileName(\"%s\") is true\n", fname); else printf("IsPartialXLogFileName(\"%s\") is false\n", fname); // XLogFromFileName(fname, tli, logSegNo, wal_segsz_bytes) strcpy(fname, "0000000100000000000000FE"); XLogFromFileName(fname, &tli, &segno, DEFAULT_XLOG_SEG_SIZE); printf("XLogFromFileName(fname, &tli, &segno, DEFAULT_XLOG_SEG_SIZE): fname = %s, tli = %u, segno = %zu\n", fname, tli, segno); return EXIT_SUCCESS; } // 実行結果 ------------------ XLogSegmentsPerXLogId = 256 XLogSegNoOffsetToRecPtr(1, 0, DEFAULT_XLOG_SEG_SIZE, xlogptr) = 16777216 XLogSegmentOffset(DEFAULT_XLOG_SEG_SIZE + 24, DEFAULT_XLOG_SEG_SIZE): xrecoff = 24 LByteToSeg(DEFAULT_XLOG_SEG_SIZE * 2 - 1, segno, DEFAULT_XLOG_SEG_SIZE): segno = 1 XLByteToSeg(DEFAULT_XLOG_SEG_SIZE * 2, segno, DEFAULT_XLOG_SEG_SIZE): segno = 2 XLByteToPrevSeg(DEFAULT_XLOG_SEG_SIZE * 2, segno, DEFAULT_XLOG_SEG_SIZE): segno = 1 XRecOffIsValid(DEFAULT_XLOG_SEG_SIZE) is false XLogFileName(fname, 1, 1, DEFAULT_XLOG_SEG_SIZE): fname = 000000010000000000000001 XLogFileName(fname, 2, 2, DEFAULT_XLOG_SEG_SIZE): fname = 000000020000000000000002 XLogFileNameById(fname, 1, 0, 1): fname = 000000010000000000000001 IsXLogFileName("000000010000000000000001") is true IsXLogFileName("00000001000000000000000#") is false IsPartialXLogFileName("000000010000000000000001.partial") is true XLogFromFileName(fname, &tli, &segno, DEFAULT_XLOG_SEG_SIZE): fname = 0000000100000000000000FE, tli = 1, segno = 254
サンプル xlog_macro_test.zip
WALレコードの読み取り †
- WALレコードの読み取りは、xlogreader.cの関数群を使う。
XLogReadRecord()関数で実際に、1レコードを読み取る。 - この関数は、redoやレプリケーション(継続的にredoしていることと同様)でも使われる。
参考リンク †
- 学習用に便利なWALの中身をSQLで出力する拡張機能