본문 바로가기

PostgreSQL

21.PostgreSQL - WAL(Write Ahead Log)

WAL 필요 이유

WAL에는 DB에서 발생한 모든 변경사항들이 기록됨

그래서 DB가 갑자기 죽더라도 DB를 다시 올릴 때 WAL에 기록된 변경사항을 replay하여 data 유실이 발생하지 않도록 함.

또한, PITR(Point-In-Time Recovery)와 Streaming Replication을 위해서도 사용

 

WAL에 기록되는 내용

insert, delete, commit과 같은 변경 작업으로 인해 생성된 XLOG record(or WAL data)가 메모리에 있는 WAL buffer에 기록.

transaction commit or aborts될 때 WAL segment file에 기록됨.

 

XLOG record의 LSN(Log Sequence Number)은 XLOG record가 transaction log에 기록된 위치를 나타냄.

즉, record의 LSN은 XLOG record의 unique ID로 사용됨.

 

DB를 recovery할 때 복구 지점을 REDO point라고 하는데 REDO point는 checkpoint가 시작되는 순간에 checkpoint record라 불리는XLOG record를 wal segment file에 기록함.

 

SELECT pg_walfile_name(pg_current_wal_lsn());
     pg_walfile_name      
--------------------------
 0000000B0000000200000095


#### insert 수행 후 pg_waldump로 기록된 로그 확인
# insert into test(aa) values(1);

$ pg_waldump  0000000B0000000200000095 | tail -8
rmgr: XLOG        len (rec/tot):     49/  5937, tx:          0, lsn: 2/95CD39C8, prev 2/95CD3990, desc: FPI_FOR_HINT , blkref #0: rel 1663/24775/1249 blk 59 FPW
rmgr: Heap        len (rec/tot):     59/    59, tx:        966, lsn: 2/95CD5118, prev 2/95CD39C8, desc: INSERT+INIT off: 1, flags: 0x08, blkref #0: rel 1663/24775/24842 blk 0
rmgr: Transaction len (rec/tot):     46/    46, tx:        966, lsn: 2/95CD5158, prev 2/95CD5118, desc: COMMIT 2024-12-28 09:19:13.479845 KST
rmgr: Standby     len (rec/tot):     54/    54, tx:          0, lsn: 2/95CD5188, prev 2/95CD5158, desc: RUNNING_XACTS nextXid 967 latestCompletedXid 965 oldestRunningXid 966; 1 xacts: 966


#### delete 수행 후 pg_waldump로 기록된 로그 확인
delete from test;

$ pg_waldump  0000000B0000000200000095 | tail -8
rmgr: Standby     len (rec/tot):     50/    50, tx:          0, lsn: 2/95CD51C0, prev 2/95CD5188, desc: RUNNING_XACTS nextXid 967 latestCompletedXid 966 oldestRunningXid 967
rmgr: XLOG        len (rec/tot):    114/   114, tx:          0, lsn: 2/95CD51F8, prev 2/95CD51C0, desc: CHECKPOINT_ONLINE redo 2/95CD51C0; tli 11; prev tli 11; fpw true; xid 0:967; oid 32804; multi 1; offset 0; oldest xid 722 in DB 1; oldest multi 1 in DB 1; oldest/newest commit timestamp xid: 0/0; oldest running xid 967; online
rmgr: Standby     len (rec/tot):     50/    50, tx:          0, lsn: 2/95CD5270, prev 2/95CD51F8, desc: RUNNING_XACTS nextXid 967 latestCompletedXid 966 oldestRunningXid 967
rmgr: XLOG        len (rec/tot):     49/   109, tx:          0, lsn: 2/95CD52A8, prev 2/95CD5270, desc: FPI_FOR_HINT , blkref #0: rel 1663/24775/24842 blk 0 FPW
rmgr: Heap        len (rec/tot):     54/    54, tx:        967, lsn: 2/95CD5318, prev 2/95CD52A8, desc: DELETE xmax: 967, off: 1, infobits: [KEYS_UPDATED], flags: 0x00, blkref #0: rel 1663/24775/24842 blk 0
rmgr: Transaction len (rec/tot):     46/    46, tx:        967, lsn: 2/95CD5350, prev 2/95CD5318, desc: COMMIT 2024-12-28 09:22:29.435054 KST
rmgr: Standby     len (rec/tot):     50/    50, tx:          0, lsn: 2/95CD5380, prev 2/95CD5350, desc: RUNNING_XACTS nextXid 968 latestCompletedXid 967 oldestRunningXid 968

#### select는 txid를 사용하지 않음.
#### 참고 https://www.postgresql.org/docs/current/transaction-id.html


#### checkpoint 수행
# checkpoint;

$ pg_waldump  0000000B0000000200000095 | tail -8
rmgr: Standby     len (rec/tot):     50/    50, tx:          0, lsn: 2/95CD5548, prev 2/95CD5510, desc: RUNNING_XACTS nextXid 968 latestCompletedXid 967 oldestRunningXid 968
rmgr: XLOG        len (rec/tot):    114/   114, tx:          0, lsn: 2/95CD5580, prev 2/95CD5548, desc: CHECKPOINT_ONLINE redo 2/95CD5548; tli 11; prev tli 11; fpw true; xid 0:968; oid 32804; multi 1; offset 0; oldest xid 722 in DB 1; oldest multi 1 in DB 1; oldest/newest commit timestamp xid: 0/0; oldest running xid 968; online
rmgr: Standby     len (rec/tot):     50/    50, tx:          0, lsn: 2/95CD55F8, prev 2/95CD5580, desc: RUNNING_XACTS nextXid 968 latestCompletedXid 967 oldestRunningXid 968
rmgr: Standby     len (rec/tot):     50/    50, tx:          0, lsn: 2/95CD5630, prev 2/95CD55F8, desc: RUNNING_XACTS nextXid 968 latestCompletedXid 967 oldestRunningXid 968
rmgr: XLOG        len (rec/tot):    114/   114, tx:          0, lsn: 2/95CD5668, prev 2/95CD5630, desc: CHECKPOINT_ONLINE redo 2/95CD5630; tli 11; prev tli 11; fpw true; xid 0:968; oid 32804; multi 1; offset 0; oldest xid 722 in DB 1; oldest multi 1 in DB 1; oldest/newest commit timestamp xid: 0/0; oldest running xid 968; online
rmgr: Standby     len (rec/tot):     50/    50, tx:          0, lsn: 2/95CD56E0, prev 2/95CD5668, desc: RUNNING_XACTS nextXid 968 latestCompletedXid 967 oldestRunningXid 968

#### transaction 처리 시
# begin;
# insert into test(aa) values(4);
# insert into test(aa) values(5);
# commit;

$ pg_waldump  0000000B0000000200000095 | tail -12
rmgr: Standby     len (rec/tot):     50/    50, tx:          0, lsn: 2/95CD5900, prev 2/95CD58D0, desc: RUNNING_XACTS nextXid 971 latestCompletedXid 970 oldestRunningXid 971
rmgr: Transaction len (rec/tot):     43/    43, tx:        972, lsn: 2/95CD5938, prev 2/95CD5900, desc: ASSIGNMENT xtop 971: subxacts: 972
rmgr: Heap        len (rec/tot):     59/    59, tx:        972, lsn: 2/95CD5968, prev 2/95CD5938, desc: INSERT off: 5, flags: 0x08, blkref #0: rel 1663/24775/24842 blk 0
rmgr: Standby     len (rec/tot):     58/    58, tx:          0, lsn: 2/95CD59A8, prev 2/95CD5968, desc: RUNNING_XACTS nextXid 973 latestCompletedXid 970 oldestRunningXid 971; 1 xacts: 971; 1 subxacts: 972
rmgr: Heap        len (rec/tot):     64/    64, tx:        973, lsn: 2/95CD59E8, prev 2/95CD59A8, desc: INSERT off: 6, flags: 0x08, blkref #0: rel 1663/24775/24842 blk 0
rmgr: Standby     len (rec/tot):     62/    62, tx:          0, lsn: 2/95CD5A28, prev 2/95CD59E8, desc: RUNNING_XACTS nextXid 974 latestCompletedXid 970 oldestRunningXid 971; 1 xacts: 971; 2 subxacts: 972 973
rmgr: Standby     len (rec/tot):     62/    62, tx:          0, lsn: 2/95CD5A68, prev 2/95CD5A28, desc: RUNNING_XACTS nextXid 974 latestCompletedXid 970 oldestRunningXid 971; 1 xacts: 971; 2 subxacts: 972 973
rmgr: XLOG        len (rec/tot):    114/   114, tx:          0, lsn: 2/95CD5AA8, prev 2/95CD5A68, desc: CHECKPOINT_ONLINE redo 2/95CD5A68; tli 11; prev tli 11; fpw true; xid 0:974; oid 32804; multi 1; offset 0; oldest xid 722 in DB 1; oldest multi 1 in DB 1; oldest/newest commit timestamp xid: 0/0; oldest running xid 971; online
rmgr: Standby     len (rec/tot):     62/    62, tx:          0, lsn: 2/95CD5B20, prev 2/95CD5AA8, desc: RUNNING_XACTS nextXid 974 latestCompletedXid 970 oldestRunningXid 971; 1 xacts: 971; 2 subxacts: 972 973
rmgr: Transaction len (rec/tot):     58/    58, tx:        971, lsn: 2/95CD5B60, prev 2/95CD5B20, desc: COMMIT 2024-12-28 10:25:02.183325 KST; subxacts: 972 973
rmgr: Standby     len (rec/tot):     50/    50, tx:          0, lsn: 2/95CD5BA0, prev 2/95CD5B60, desc: RUNNING_XACTS nextXid 974 latestCompletedXid 973 oldestRunningXid 974

 

full-page write

PostgreSQL은 변경된 data를 shared buffer에서 disk로 background process가 기록할 때 OS 이슈로 해당 page가 corrupted되는것을 방지하고자 full-page wirte를 사용함.

 

full-page write는 checkpoint 이후 각 page마다 첫번 째 변경이 있을 때 변경이 발생한 page 전체를 XLOG로 WAL buffer에 기록함.

testdb=# checkpoint;
CHECKPOINT

pg_waldump  0000000B0000000200000095 | tail -10
rmgr: Standby     len (rec/tot):     50/    50, tx:          0, lsn: 2/95CD5BD8, prev 2/95CD5BA0, desc: RUNNING_XACTS nextXid 974 latestCompletedXid 973 oldestRunningXid 974
rmgr: XLOG        len (rec/tot):    114/   114, tx:          0, lsn: 2/95CD5C10, prev 2/95CD5BD8, desc: CHECKPOINT_ONLINE redo 2/95CD5BD8; tli 11; prev tli 11; fpw true; xid 0:974; oid 32804; multi 1; offset 0; oldest xid 722 in DB 1; oldest multi 1 in DB 1; oldest/newest commit timestamp xid: 0/0; oldest running xid 974; online
rmgr: Standby     len (rec/tot):     50/    50, tx:          0, lsn: 2/95CD5C88, prev 2/95CD5C10, desc: RUNNING_XACTS nextXid 974 latestCompletedXid 973 oldestRunningXid 974


testdb=# update test set aa = 6 where aa = 5;
UPDATE 1

#### FPI_FOR_HINT 발생 확인 가능
pg_waldump  0000000B0000000200000095 | tail -10
rmgr: XLOG        len (rec/tot):     49/   289, tx:          0, lsn: 2/95CD5CC0, prev 2/95CD5C88, desc: FPI_FOR_HINT , blkref #0: rel 1663/24775/24842 blk 0 FPW
rmgr: Heap        len (rec/tot):     70/    70, tx:        974, lsn: 2/95CD5DE8, prev 2/95CD5CC0, desc: HOT_UPDATE old_xmax: 974, old_off: 6, old_infobits: [], flags: 0x10, new_xmax: 0, new_off: 7, blkref #0: rel 1663/24775/24842 blk 0
rmgr: Transaction len (rec/tot):     46/    46, tx:        974, lsn: 2/95CD5E30, prev 2/95CD5DE8, desc: COMMIT 2024-12-28 10:29:05.391257 KST
rmgr: Standby     len (rec/tot):     50/    50, tx:          0, lsn: 2/95CD5E60, prev 2/95CD5E30, desc: RUNNING_XACTS nextXid 975 latestCompletedXid 974 oldestRunningXid 975

 

full-page image를 이용한 복구 과정

(1) corrupted page를 shared buffer에 올린다.

(2)  앞서 checkpoint 이후 page 변경으로 인해 full-page image인 backup block이 XLOG record로 기록되었고

       backup block XLOG record의 data 부분을 모두 shared buffer pool에 올라간 page에 overwrite한다.

 

동작 과정에 대한 자세한 내용은 아래 링크를 참고해서 보시길.

https://www.interdb.jp/pg/pgsql09/01.html

 

9.1. Overview :: Hironobu SUZUKI @ InterDB

As mentioned above, WAL can prevent data loss due to process or operating system crashes. However, if a file system or media failure occurs, the data will be lost. To deal with such failures, PostgreSQL provides online backup and replication features. If o

www.interdb.jp

 

wal segment file 구조

16진수가 24개로 8자리씩 의미가 있음.

ex) 0000000B00000001000000FE

timelineID로 복구 시 활용

    0000000B

LSN은 PostgreSQL에서 WAL의 위치를 나타내며 64비트 주소 공간으로 표현되고 위의 예시에서 00000001000000FE에 해당

  • 상위 32비트: WAL 세그먼트 번호.
  • 하위 32비트: 세그먼트 내 오프셋(Byte 단위).

실제 사용되는 LSN 주소 공간은 48bit만 사용

 

wal segment file 순환

WAL segmentfile은 오름차순으로 계속 증가하며 아래 예시처럼 하위 8digit 중 2자리만 사용하여 00~FF까지 증가하게 되면 중간 8-digit의 값이 1씩 증가

0000000B00000001000000FE
0000000B00000001000000FF
0000000B0000000200000000
0000000B0000000200000001

#### LSN의 상위 32bit가 walfile의 middle - 8dgit에 해당
select pg_current_wal_lsn();
 pg_current_wal_lsn 
--------------------
 2/95CD5F80

SELECT pg_walfile_name(pg_current_wal_lsn());
     pg_walfile_name      
--------------------------
 0000000B0000000200000095

 

 

그러나 wal-segzie에 따라 1씩 증가하는 범위가 달라지게됨.

wal segment file = 16MB일 때

    256개의 파일이 생성된 뒤 중간 8-digit이 1씩 증가

     계산 공식 : 2의 48승 / 16MB * 1024* 1024 = 256   

      ex) 0000000100000000 ~ 00000001000000FF         

            0000000200000000 ~ 00000002000000FF

wal segment file = 1024MB일 때

    3개의 파일이 생성된 뒤 중간 8-digit이 1씩 증가

     계산 공식 : 2의 48승 / 1024MB * 1024* 1024 = 3

    ex) 0000000100000000 ~ 0000000100000003

          0000000200000000 ~ 0000000200000003

 

WAL segment file의 내부 구조

wal segment file의 첫번 째 page header에는 XLogLongPageHeaderData 구조체를 사용

그 외 page header에는 XLogPageHeaderData 구조체를 사용

XLOG record는 XLogRecord header와 XLog record data로 구성

 

XLogRecord 내부 구조

 

xl_info와 xl_rmid는 resource managers와 관련됨.

resource manager 관련 메타 정보는 version이 올라갈 수록 증가하고 코드 상 확인 가능

typedef struct XLogRecord
{
	uint32		xl_tot_len;		/* total len of entire record */
	TransactionId xl_xid;		/* xact id */
	XLogRecPtr	xl_prev;		/* ptr to previous record in log */
	uint8		xl_info;		/* flag bits, see below */
	RmgrId		xl_rmid;		/* resource manager for this record */
	/* 2 bytes of padding here, initialize to zero */
	pg_crc32c	xl_crc;			/* CRC for this record */

	/* XLogRecordBlockHeaders and XLogRecordDataHeader follow, no padding */

} XLogRecord;

 

내부 구조에 대한 자세한 설명은 아래 내용 참고

https://www.interdb.jp/pg/pgsql09/04.html

 

9.4. Internal Layout of XLOG Record :: Hironobu SUZUKI @ InterDB

The main data of an XLOG record that contains a full-page image is not used except in some special cases, such as logical decoding and speculative insertions. It is ignored when the record is replayed, making it redundant data. This may be improved in the

www.interdb.jp

 

WAL 관련 process

WAL Writer

    WAL buffer를 주기적으로 확인해서 XLOG를 WAL segment file에 기록

     wal_writer_delay 파라미터로 WAL 수행 빈도 조정 가능

 

WAL Summarizer

     PostgreSQL 17에서 추가된 프로세스

      incremental backup을 위해 도입.

 

자세한 사항은 아래 내용 확인

https://www.interdb.jp/pg/pgsql09/06.html

 

9.6. WAL related processes :: Hironobu SUZUKI @ InterDB

9.6.1. WAL Writer Process The WAL writer is a background process that periodically checks the WAL buffer and writes all unwritten XLOG records to the WAL segments. This process helps to avoid bursts of XLOG record writing. If the WAL writer is not enabled,

www.interdb.jp

 

 

checkpointer

checkpointer가 checkpoint를 트리거하는 상황

    1. 마지막 checkpoint 이후 checkpoint_timeout(default 300초) 시간이 지나면 호출

    2. pg_wal 디렉토리 내 WAL segment file의 전체 사이즈가 max_wal_size를 초과할 때

    3. PostgreSQL을 smart mode or fast mode로 중지할 때

stop 방식에는 3가지가 존재하고 default는 fast
https://www.postgresql.org/docs/current/app-pg-ctl.html

smart : 새 connection을 허용하지 않고 기존 세션들이 종료될 때까지 대기
            서버가  hot standby라면,  모든 client가 끊어지고 recovery and streaming replication이 종료됨.
fast : 모든 active transaction은 rollback하고 clients를 강제로 종료
immediate : shutdown 시 정리 작업 없이 모든 서버 프로세스가 바로 중단됨
                    다시 DB를 시작하면 crash-recovery가 진행됨.

    4. 수동으로 checkpoint를 수행할 때

 

checkpointer가 하는 일

database recovery를 위해 redo point를 생성

shared buffer pool내 dirty page를 점진적으로 스토리지로 flush하도록 유도 (background writer가 실제로 dirty page를 내려씀)

 

 

checkpoint 내부 동작 프로세스는 아래 링크 참고

step1. checkpoint가 호출되면 redo point를 바로 생성하고 추후 recovery start point로 활용

step2. shared memory에서 모든 data(ex clog에 있는 내용)를 스토리지로 flush

step3. shared buffer pool내 dirty page를 점진적으로 스토리지로 flush하도록 유도 (background writer가 실제로 dirty page를 내려씀)

step4. WAL buffer에 checkpoint의 XLOG record를 기록

step5. pg_control file 갱신

https://www.interdb.jp/pg/pgsql09/07.html

 

9.7. Checkpoint Processing in PostgreSQL :: Hironobu SUZUKI @ InterDB

PostgreSQL 11 or later only stores the WAL segments that contain the latest checkpoint or newer. Older segment files, which contains the prior checkpoint, are not stored to reduce the disk space used for saving WAL segment files under the pg_wal subdirecto

www.interdb.jp

 

pg_controldata 정보 보기

pg_controldata | grep check
Latest checkpoint location:           2/95CD5ED0
Latest checkpoint's REDO location:    2/95CD5E98
Latest checkpoint's REDO WAL file:    0000000B0000000200000095
Latest checkpoint's TimeLineID:       11
Latest checkpoint's PrevTimeLineID:   11
Latest checkpoint's full_page_writes: on
Latest checkpoint's NextXID:          0:975
Latest checkpoint's NextOID:          32804
Latest checkpoint's NextMultiXactId:  1
Latest checkpoint's NextMultiOffset:  0
Latest checkpoint's oldestXID:        722
Latest checkpoint's oldestXID's DB:   1
Latest checkpoint's oldestActiveXID:  975
Latest checkpoint's oldestMultiXid:   1
Latest checkpoint's oldestMulti's DB: 1
Latest checkpoint's oldestCommitTsXid:0
Latest checkpoint's newestCommitTsXid:0
Time of latest checkpoint:            Sat 28 Dec 2024 10:33:27 AM KST
Data page checksum version:           1

 

 

WAL을 이용한 복구

data page 해더에 저장되는 LSN 값과 WAL file에 저장된 LSN을 비교

page lsn < WAL file lsn 일 때만  WAL에 저장된 데이터를 page에 적용함.

단, full page image인 경우엔 lsn과 상관없이 WAL에 저장된 full page image를 shared buffer pool에 올라온 data page에 overwrite함.

 

자세한 내용은 아래를 참고

https://www.interdb.jp/pg/pgsql09/08.html

 

9.8. Database Recovery in PostgreSQL :: Hironobu SUZUKI @ InterDB

PostgreSQL reads the latest checkpoint record, the location of which is written in the pg_control file, from the appropriate WAL segment file. It then gets the REDO point from the record. If the latest checkpoint record is invalid, PostgreSQL reads the one

www.interdb.jp

 

WAL segment file관리

wal segment file이 가득차면 새로운 wal segmet file로 switch됨.

WAL segment file의 수는 configuration parameter와 서버 트래픽 등에 따라 줄어들거나 늘어남.

 

WAL segment switch 발생 시점

1. WAL segment file이 가득찼을 때

2. pg_swtich_wal을 수동으로 호출할 때

3. archive_mode=on인 상태에서 archive_timeout에 도달했을 때

 

checkpoint가 시작될 때마다 다음 checkpoint 발생 시 필요할 것으로 예상되는 WAL segment files을 예상해서 준비함.

추정치는 이전 checkpoint에서 사용된 WAL segment file 수를 기반으로 함.

이전 checkpoint가 수행될 때 기록된 REDO point를 갖고 있는 WAL segment file부터 필요한 파일로 계산

 

필요할 것으로 예상되는 파일 갯수는 min_wal_sizemax_wal_size 사이에 있어야만 함.

만약 WAL segment files의 전체 크기가 max_wal_size를 넘어서면 checkpoint가 트리거 됨.

checkpoint가 수행되면 새로운 redo point가 생기게 되고 redo point를 포함하고 있는 WAL segment file보다 앞선 file들은 재사용되거나 제거됨.

그러나 wal_keep_size나 replication slot을 사용하고 있으면 replication과 data sync를 위해 오래된 WAL file을 유지해야할 경우 redo point를 가진 WAL segment file보다 먼저 생성된 file이 지워지지 않을 수 있음.

 

checkpoint가 시작되면 PostgreSQL은 필요한 WAL segment file을 보관하거나 재활용하고 불필요한 파일은 제거함.

switch된 wal segment file은 파일을 지우지 않고 미래에 생성될 wal segment file 이름으로 변경되거나 재사용됨.

그러나 필요 없는 경우엔 제거됨.

 

자세한 예시는 링크 참고

https://www.interdb.jp/pg/pgsql09/09.html

 

 

WAL segment file 아카이빙

WAL segment file switch가 발생할 때 아카이브 영역에 WAL segment file을 copy해서 보관할 수 있음

archiver process에 의해 수행됨.

 

아카이브를 저장할 영역은 archive_command 파라미터로 설장

 

자세한 예시가 궁금할 경우 참고

https://www.interdb.jp/pg/pgsql09/10.html

 

9.10. Continuous Archiving and Archive Logs :: Hironobu SUZUKI @ InterDB

PostgreSQL does not automatically clean up created archive logs, so proper log management is essential when using this feature. Without intervention, the number of archive logs will continuously grow. The pg_archivecleanup utility is one of the useful tool

www.interdb.jp

 

'PostgreSQL' 카테고리의 다른 글

23. PostgreSQL - planner의 cost 계산  (1) 2024.12.29
22. PostgreSQL - query processing  (1) 2024.12.29
20. PostgreSQL - buffer manager  (1) 2024.12.25
19. PostgreSQL - index-only scans  (3) 2024.12.25
18. PostgreSQL - Heap-Only Tuples(HOT)  (1) 2024.12.25