(KO) PostgreSQL 보조 프로세스 — bgwriter, walwriter, checkpointer, startup, syslogger
목차
- 학술적 배경
- DBMS 공통 설계 패턴
- PostgreSQL의 구현
- 소스 코드 가이드
- 소스 검증 (2026-06-05 기준)
- PostgreSQL 너머 — 비교 설계와 연구 프론티어
- 출처
학술적 배경
섹션 제목: “학술적 배경”클라이언트 세션 내에서 I/O를 모두 처리하는 데이터베이스 서버는 구조적으로 바람직하지 않은 지연 결합(latency coupling)을 만들어낸다. 더티 버퍼를 디스크에 쓰는 클라이언트는 자신의 응답 시간을 늦출 뿐 아니라, 잠금 경합으로 다른 세션에도 영향을 준다. 전통적인 해법은 예측 가능한 주기적 I/O 작업을 클라이언트 세션과 무관하게 동작하는 전담 백그라운드 프로세스에 위임하는 것이다.
설계 공간을 결정하는 두 가지 하위 문제가 있다.
-
쓰기 비용을 여러 클라이언트에 분산하기. 버퍼 풀에서 페이지를 쫓아내는 클라이언트가 매번 직접 디스크에 써야 한다면, 쓰기 지연이 일반 워크로드의 임계 경로가 된다. 백그라운드 라이터(background writer)가 미리 버퍼를 청소해 두면, 클라이언트가 페이지를 내보낼 때 대부분 깨끗한 슬롯을 바로 얻는다. WAL도 마찬가지다. 여러 커밋 레코드를 하나의
fsync호출로 묶는 그룹 커밋(group commit)은 커밋당 I/O 비용을 줄인다. -
주기적 일관성 지점(체크포인트). ARIES 방식의 복구(Mohan et al., 1992)는 리두(redo)를 시작할 수 있는 일관된 온디스크 상태를 식별할 수 있어야 한다. 체크포인트는 모든 더티 버퍼를 데이터 파일에 기록하고 그 WAL 위치를 남긴다. 복구 시에는 가장 최근 체크포인트 이후의 WAL만 재생하면 된다. 체크포인트 빈도가 높을수록 복구 시간이 줄어들고, 더티 데이터를 적극적으로 플러시할수록 체크포인트 비용이 낮아진다. 이 두 힘은 정상 I/O 대역폭과 길항하기 때문에, 체크포인트 스케줄링과 속도 조절은 단순하지 않은 공학 문제다.
Architecture of a Database System(Hellerstein et al., 2007, §6)은 백그라운드
라이터 패턴이 버퍼 풀 관리자에서 사실상 보편적이라고 설명하며, PostgreSQL이
9.2에서 백그라운드 라이터와 체크포인터를 완전히 분리한 것이 I/O 스케줄링을
세밀하게 조율할 수 있게 해 준다고 지적한다. 체크포인터는 체크포인트 간격에
걸쳐 작업을 분산하고(checkpoint_completion_target), bgwriter는 상시 적재
압력을 처리한다.
WAL 복구는 별개의 문제다. 클라이언트 백엔드를 받기 전에 WAL 레코드를 재생해 데이터 파일을 일관된 상태로 만드는 프로세스가 필요하다. 이것이 startup 프로세스 패턴이다. 하나의 프로세스가 전체 복구 파이프라인을 주도하고, 완료되면 포스트마스터에 신호를 보내 연결 수락을 허가한다. 스탠바이 모드에서는 이 프로세스가 종료되지 않고 지속적인 리두 루프로 동작한다.
로깅 인프라는 DBMS 고유의 문제가 아니지만, 전담 프로세스에 격리하는 구조적 선택은 중요하다. 파이프 하나로 모든 쓰기를 직렬화하면 파일에 대한 동시 쓰기로 인한 잠금 경합이 없어지고, 로그 순환(log rotation) 로직이 한 곳에 집중된다.
DBMS 공통 설계 패턴
섹션 제목: “DBMS 공통 설계 패턴”백그라운드 라이터 / 페이지 클리너
섹션 제목: “백그라운드 라이터 / 페이지 클리너”거의 모든 프로덕션 RDBMS는 버퍼 풀의 더티 페이지를 선제적으로 플러시하는 백그라운드 프로세스(또는 스레드)를 운영한다.
- Oracle: DBWR(Database Writer) 프로세스. 병렬 처리를 위해 여러 인스턴스를 허용하며, 버퍼 여유 임계값이나 타임아웃이 되면 깨어난다.
- MySQL/InnoDB:
io_write스레드와 5.6에서 도입된 페이지 클리너 스레드. 단일 스레드 플러시 병목을 해소하기 위해 분리되었다. - SQL Server: LazyWriter와 Checkpoint Writer. LazyWriter는 상시 적재 압력을, Checkpoint Writer는 주기적 일관성 플러시를 담당한다.
- CUBRID: Flush Manager 스레드. PostgreSQL의 “bgwriter + checkpointer” 분리 구조와 유사하다.
공통 패턴은 이렇다. 설정된 간격만큼 잠들었다가, 버퍼 풀의 클락 핸드(clock-hand) 또는 LRU 리스트 일부를 스캔하고, LSN이 충분히 오래된 더티 페이지를 쓴 뒤, 다시 잠든다. 피드백 루프가 발견한 작업량에 따라 스캔 창이나 슬립 시간을 조정하며, 시스템이 유휴 상태면 하이버네이트(hibernate) 모드로 진입해 지수적으로 간격을 늘린다.
WAL 그룹 커밋
섹션 제목: “WAL 그룹 커밋”fsync 비용을 상각하기 위해 커밋 레코드를 묶는 방식도 사실상 보편적이다.
각 RDBMS마다 이름이 다르지만 — Oracle의 “redo copy” 래칭, InnoDB의 “group
commit” 최적화, PostgreSQL의 walwriter와 synchronous_commit = off 경로 —
불변 조건은 같다. 트랜잭션이 WAL을 직접 fsync하지 않아도, 다른 프로세스가
정해진 지연 안에 처리해 준다는 보장이 있으면 된다.
체크포인트 속도 조절
섹션 제목: “체크포인트 속도 조절”단순한 체크포인트는 더티 페이지를 가능한 한 빠르게 써 I/O 스파이크를 만든다.
이 스파이크를 체크포인트 간격에 분산하는 것이 핵심 설계 통찰이다. ARIES는
체크포인트 스케줄링을 구현 고려 사항으로 언급하며, 실제 속도 조절 전략은
엔진마다 다르다. PostgreSQL의 checkpoint_completion_target(기본값 0.9)은
체크포인터에게 checkpoint_timeout의 90% 안에 완료하라고 지시해, 정상
I/O를 차단하지 않는 여유를 남긴다.
전담 조기 수명 주기 단계로서의 복구
섹션 제목: “전담 조기 수명 주기 단계로서의 복구”WAL 기반 RDBMS는 모두 연결을 열기 전에 기동 시 WAL을 재생해야 한다. 이 작업을
메인 서버 스레드에서 할지, 전담 서브프로세스에서 할지, 첫 번째 클라이언트가
트리거할지는 설계 선택이다. PostgreSQL의 답은 전담 startup 프로세스다. 포스트마스터
자신은 WAL을 읽지 않고, startup 프로세스로부터 PMSIGNAL을 받아 PMState를
전환한다.
개념 ↔ 구현 대응표
섹션 제목: “개념 ↔ 구현 대응표”| 개념 | PostgreSQL 이름 |
|---|---|
| 백그라운드 페이지 클리너 | B_BG_WRITER / BackgroundWriterMain |
| WAL 그룹 커밋 / fsync 분산자 | B_WAL_WRITER / WalWriterMain |
| 체크포인트 스케줄러 + 실행자 | B_CHECKPOINTER / CheckpointerMain |
| WAL 복구 / 리두 드라이버 | B_STARTUP / StartupProcessMain → StartupXLOG |
| stderr 로그 수집기 + 순환기 | B_LOGGER / SysLoggerMain |
| 공통 보조 초기화 경로 | AuxiliaryProcessMainCommon |
| 클라이언트용 체크포인트 요청 인터페이스 | CheckpointerShmem (공유 구조체) |
PostgreSQL의 구현
섹션 제목: “PostgreSQL의 구현”AuxiliaryProcessMainCommon: 공통 초기화
섹션 제목: “AuxiliaryProcessMainCommon: 공통 초기화”다섯 프로세스 모두 MyBackendType을 설정한 직후 AuxiliaryProcessMainCommon
(auxprocess.c)을 호출한다. 이 함수는 InitPostgres 전체 경로를 거치지 않는
프로세스를 위한 최소 초기화 경로를 제공한다.
// AuxiliaryProcessMainCommon — src/backend/postmaster/auxprocess.cvoidAuxiliaryProcessMainCommon(void){ Assert(IsUnderPostmaster);
/* Release postmaster's working memory context */ if (PostmasterContext) { MemoryContextDelete(PostmasterContext); PostmasterContext = NULL; }
init_ps_display(NULL); /* process-title display */
IgnoreSystemIndexes = true; /* no catalog reads yet */
InitAuxiliaryProcess(); /* create PGPROC in shared memory */ BaseInit(); /* low-level I/O and buffer init */ ProcSignalInit(NULL, 0); /* register in procsignal array */
CreateAuxProcessResourceOwner(); /* resource tracking sans transactions */
pgstat_beinit(); /* backend status infrastructure */ pgstat_bestart_initial(); pgstat_bestart_final();
before_shmem_exit(ShutdownAuxiliaryProcess, 0); /* LWLock cleanup */
SetProcessingMode(NormalProcessing);}핵심 호출은 InitAuxiliaryProcess다. 이 함수가 프로세스를 위한 PGPROC 슬롯을
공유 메모리에 할당한다. PGPROC 없이는 LWLock을 획득할 수 없고 procarray에서도
보이지 않는다. InitPostgres와 달리 보조 초기화는 데이터베이스를 열지 않고,
역할(role)을 설정하지 않으며, 헤비웨이트 잠금도 활성화하지 않는다. 보조 프로세스는
사용자 트랜잭션과 무관한 무상태(stateless) 프로세스다.
syslogger는 유일한 예외다. InitAuxiliaryProcess도 AuxiliaryProcessMainCommon도
호출하지 않는다. syslogger는 공유 메모리에 전혀 접속하지 않기 때문이다. 공유
세그먼트가 알 수 없는 상태일 때도 로거는 살아있어야 한다는 설계 의도에서 비롯된
격리다.
bgwriter: 선제적 버퍼 클리너
섹션 제목: “bgwriter: 선제적 버퍼 클리너”BackgroundWriterMain(bgwriter.c)은 BgBufferSync 호출 → bgwriter_delay
밀리초 슬립 → 반복의 연속 루프를 돌린다.
// BackgroundWriterMain — src/backend/postmaster/bgwriter.cvoidBackgroundWriterMain(const void *startup_data, size_t startup_data_len){ MyBackendType = B_BG_WRITER; AuxiliaryProcessMainCommon();
pqsignal(SIGHUP, SignalHandlerForConfigReload); pqsignal(SIGTERM, SignalHandlerForShutdownRequest); /* ... condensed signal setup ... */
for (;;) { ResetLatch(MyLatch); ProcessMainLoopInterrupts();
can_hibernate = BgBufferSync(&wb_context);
pgstat_report_bgwriter(); pgstat_report_wal(true);
if (FirstCallSinceLastCheckpoint()) smgrdestroyall(); /* free dropped-relation smgr objects */
/* Periodically log xl_running_xacts for replication standby */ if (XLogStandbyInfoActive() && !RecoveryInProgress()) { /* ... condensed: LogStandbySnapshot every 15 s ... */ }
rc = WaitLatch(MyLatch, WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH, BgWriterDelay, WAIT_EVENT_BGWRITER_MAIN);
/* Hibernate if nothing happening for two consecutive cycles */ if (rc == WL_TIMEOUT && can_hibernate && prev_hibernate) { StrategyNotifyBgWriter(MyProcNumber); (void) WaitLatch(MyLatch, WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH, BgWriterDelay * HIBERNATE_FACTOR, WAIT_EVENT_BGWRITER_HIBERNATE); StrategyNotifyBgWriter(-1); } prev_hibernate = can_hibernate; }}BgBufferSync(bufmgr.c)가 클락 핸드 알고리즘으로 실제 더티 페이지 스캔을
수행한다. 작업이 없으면 true(can_hibernate)를 반환한다. 두 사이클 연속으로
“하이버네이트 가능” 판정이 나오면, bgwriter는 StrategyNotifyBgWriter로 자신의
proc 번호를 등록해 다음 버퍼 할당 시 깨어나도록 하고, BgWriterDelay * 50 ms
동안 잠든다. 이 하이버네이트 메커니즘이 유휴 상태의 CPU 사용량과 디스크 웨이크업을
크게 줄인다.
실제 청소 작업은 BgBufferSync 내부의 클락-스위프 LRU 스캔이다. bgwriter는
매 사이클마다 풀 전체를 스캔하지 않는다. 다음 할당 사이클에서 필요한 버퍼 수를
추정한 뒤(smoothed_alloc 이동 평균과 bgwriter_lru_multiplier를 곱한 값),
next_to_clean에서 앞으로 진행하며 전략 클락 스위프를 따라잡거나, 추정치를
충족하거나, bgwriter_lru_maxpages 상한에 걸릴 때까지 정리한다.
// BgBufferSync — src/backend/storage/buffer/bufmgr.cboolBgBufferSync(WritebackContext *wb_context){ /* Find where the freelist clock sweep is + allocations since last call */ strategy_buf_id = StrategySyncStart(&strategy_passes, &recent_alloc); PendingBgWriterStats.buf_alloc += recent_alloc;
if (bgwriter_lru_maxpages <= 0) /* LRU scan disabled */ { saved_info_valid = false; return true; /* OK to hibernate */ }
/* ... condensed: compute strategy_delta, bufs_to_lap, smoothed_density, smoothed_alloc, then upcoming_alloc_est = smoothed_alloc * multiplier ... */
num_to_scan = bufs_to_lap; num_written = 0; reusable_buffers = reusable_buffers_est;
/* Execute the LRU scan: clean forward until lapped / estimate met / capped */ while (num_to_scan > 0 && reusable_buffers < upcoming_alloc_est) { int sync_state = SyncOneBuffer(next_to_clean, true, wb_context);
if (++next_to_clean >= NBuffers) { next_to_clean = 0; next_passes++; } num_to_scan--;
if (sync_state & BUF_WRITTEN) { reusable_buffers++; if (++num_written >= bgwriter_lru_maxpages) { PendingBgWriterStats.maxwritten_clean++; break; /* hit per-cycle write cap */ } } else if (sync_state & BUF_REUSABLE) reusable_buffers++; }
PendingBgWriterStats.buf_written_clean += num_written;
/* Hibernate only if we lapped the sweep AND no allocations occurred */ return (bufs_to_lap == 0 && recent_alloc == 0);}can_hibernate에 연결되는 반환값은 bufs_to_lap == 0 && recent_alloc == 0이다.
bgwriter는 전략 클락 스위프를 따라잡은 상태에서 이전 호출 이후 백엔드 버퍼 할당도
없었을 때만 하이버네이트 가능 신호를 보낸다. SyncOneBuffer(같은 파일)는 핀되지
않고 최근에 사용되지 않은 더티 버퍼 후보 하나를 쓰고, BUF_WRITTEN | BUF_REUSABLE
비트마스크를 반환한다. 두 피드백 추정자 — 빠른 공격·느린 감소 방식의 할당률
smoothed_alloc과 재사용 가능 버퍼 하나당 스캔 버퍼 수를 추적하는
smoothed_density — 가 이미 지나친 깨끗한 버퍼를 재스캔하지 않으면서
변화하는 워크로드를 추적하게 한다.
그림 1b — BgBufferSync LRU 클락-스위프 청소 루프
flowchart TD
START["BgBufferSync"]
SYNCSTART["StrategySyncStart\nstrategy_buf_id, recent_alloc"]
DISABLED["bgwriter_lru_maxpages <= 0?"]
HIB["return true\n(하이버네이트)"]
EST["upcoming_alloc_est 추정\nsmoothed_alloc x multiplier\nbufs_to_lap 계산"]
COND["num_to_scan > 0 AND\nreusable_buffers < est?"]
SYNC["SyncOneBuffer(next_to_clean)"]
ADV["next_to_clean 전진\n(0으로 감싸기, next_passes++)"]
WRITTEN["BUF_WRITTEN?\nnum_written++"]
CAP["num_written >= maxpages?\nbreak"]
RET["return bufs_to_lap == 0\nAND recent_alloc == 0"]
START --> SYNCSTART --> DISABLED
DISABLED -->|yes| HIB
DISABLED -->|no| EST --> COND
COND -->|yes| SYNC --> ADV --> WRITTEN
WRITTEN -->|yes| CAP
CAP -->|상한 미만| COND
CAP -->|상한 도달| RET
WRITTEN -->|no| COND
COND -->|no| RET
bgwriter 루프에는 두 가지 부가 임무가 있다. bgwriter가 정기적으로 실행되는
유일한 프로세스이기 때문에 이 임무들이 여기에 위치한다. 첫째, 체크포인트 이후
첫 루프에서 삭제된 릴레이션의 smgr 객체를 해제한다(bgwriter는 백엔드와 달리
AtEOXact_SMgr을 호출하지 않기 때문이다). 둘째, 스탠바이가 일관된 상태에
도달하는 속도를 높이기 위해 15초마다 xl_running_xacts 스냅샷을 로깅한다.
그림 1 — bgwriter 메인 루프
flowchart TD
START["BackgroundWriterMain"]
INIT["AuxiliaryProcessMainCommon\n시그널 설정"]
LOOP["for(;;)"]
RESET["ResetLatch"]
INTR["ProcessMainLoopInterrupts\n(종료 / 설정 재로드)"]
SYNC["BgBufferSync\ncan_hibernate 반환"]
STATS["pgstat_report_bgwriter\npgstat_report_wal"]
SMGR["FirstCallSinceLastCheckpoint?\nsmgrdestroyall"]
SNAP["XLogStandbyInfoActive?\n15초마다 LogStandbySnapshot"]
WAIT["WaitLatch(BgWriterDelay)"]
HIB["하이버네이트?\nStrategyNotifyBgWriter\nWaitLatch(BgWriterDelay * 50)"]
START --> INIT --> LOOP
LOOP --> RESET --> INTR --> SYNC --> STATS --> SMGR --> SNAP --> WAIT
WAIT -->|타임아웃 + can_hibernate 2회 연속| HIB --> LOOP
WAIT -->|래치 또는 타임아웃| LOOP
walwriter: WAL fsync 분산자
섹션 제목: “walwriter: WAL fsync 분산자”WalWriterMain(walwriter.c)은 매 사이클마다 XLogBackgroundFlush를 호출해
미플러시 WAL 버퍼를 처리한다.
// WalWriterMain — src/backend/postmaster/walwriter.cvoidWalWriterMain(const void *startup_data, size_t startup_data_len){ MyBackendType = B_WAL_WRITER; AuxiliaryProcessMainCommon();
/* Advertise proc number so backends can wake us */ ProcGlobal->walwriterProc = MyProcNumber;
for (;;) { if (hibernating != (left_till_hibernate <= 1)) { hibernating = (left_till_hibernate <= 1); SetWalWriterSleeping(hibernating); /* global flag for async commits */ }
ResetLatch(MyLatch); ProcessMainLoopInterrupts();
if (XLogBackgroundFlush()) left_till_hibernate = LOOPS_UNTIL_HIBERNATE; /* reset to 50 */ else if (left_till_hibernate > 0) left_till_hibernate--;
pgstat_report_wal(false);
cur_timeout = (left_till_hibernate > 0) ? WalWriterDelay : WalWriterDelay * HIBERNATE_FACTOR; /* 25× */
(void) WaitLatch(MyLatch, WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH, cur_timeout, WAIT_EVENT_WAL_WRITER_MAIN); }}ProcGlobal->walwriterProc은 비동기 커밋 백엔드가 플러시가 필요할 때 신호 대신
walwriter의 래치를 직접 설정할 수 있게 하는 메커니즘이다. SetWalWriterSleeping(true)는
walwriter가 긴 슬립에 진입한다는 사실을 비동기 커밋 코드에 알려, synchronous_commit = off
의 타임아웃 계산(wal_writer_delay * 3 이내 플러시 보장)이 하이버네이트 중에도 정확하게
유지되도록 한다.
XLogBackgroundFlush(xlog.c)는 아직 WAL 세그먼트에 기록되지 않은 버퍼를 쓰고,
현재 플러시 포인트까지 fsync한다. 무언가를 플러시했으면 true를 반환한다. 50번
(LOOPS_UNTIL_HIBERNATE) 연속으로 아무것도 없으면 left_till_hibernate가 0이 되고
슬립 시간이 WalWriterDelay * 25로 늘어난다.
checkpointer: 체크포인트 소유자 겸 fsync 디스패처
섹션 제목: “checkpointer: 체크포인트 소유자 겸 fsync 디스패처”CheckpointerMain(checkpointer.c)은 모든 체크포인트와 재시작점(restartpoint)
실행을 소유한다. 백엔드와의 통신은 CheckpointerShmem을 거쳐 진행되며, 이
공유 구조체는 fsync 요청 큐도 겸한다.
// CheckpointerShmemStruct — src/backend/postmaster/checkpointer.ctypedef struct{ pid_t checkpointer_pid; /* 0 if not started */
slock_t ckpt_lck; /* protects ckpt_* counters */
int ckpt_started; /* incremented at checkpoint start */ int ckpt_done; /* set == ckpt_started on completion */ int ckpt_failed; /* incremented on failure */
int ckpt_flags; /* OR of CHECKPOINT_* request bits */
ConditionVariable start_cv; /* signaled when ckpt_started advances */ ConditionVariable done_cv; /* signaled when ckpt_done advances */
int num_requests; /* pending fsync requests */ int max_requests; CheckpointerRequest requests[FLEXIBLE_ARRAY_MEMBER];} CheckpointerShmemStruct;세 카운터 프로토콜(ckpt_started, ckpt_done, ckpt_failed)은 체크포인트
요청을 보낸 백엔드가 자신의 체크포인트 완료를 커스텀 시그널 없이 기다릴 수
있도록 한다. 백엔드는 신호 전송 전에 ckpt_started를 기록하고, ckpt_started가
증가할 때까지 기다린 다음(새 체크포인트 시작 확인), ckpt_done이 그 값에
도달할 때까지 기다린다. 그 사이에 ckpt_failed가 증가했다면 체크포인트가
실패한 것이다.
메인 루프:
// CheckpointerMain loop (condensed) — src/backend/postmaster/checkpointer.cvoidCheckpointerMain(const void *startup_data, size_t startup_data_len){ MyBackendType = B_CHECKPOINTER; AuxiliaryProcessMainCommon();
CheckpointerShmem->checkpointer_pid = MyProcPid; ProcGlobal->checkpointerProc = MyProcNumber;
/* SIGINT = shutdown checkpoint request; SIGUSR2 = exit after that */ pqsignal(SIGINT, ReqShutdownXLOG); pqsignal(SIGTERM, SIG_IGN); /* ignore normal SIGTERM */ pqsignal(SIGUSR2, SignalHandlerForShutdownRequest);
for (;;) { ResetLatch(MyLatch); AbsorbSyncRequests(); /* drain fsync request queue */ ProcessCheckpointerInterrupts(); if (ShutdownXLOGPending || ShutdownRequestPending) break;
/* Decide: time-driven or request-driven checkpoint? */ if (CheckpointerShmem->ckpt_flags) { do_checkpoint = true; chkpt_or_rstpt_requested = true; } elapsed_secs = (pg_time_t)time(NULL) - last_checkpoint_time; if (elapsed_secs >= CheckPointTimeout) { do_checkpoint = true; flags |= CHECKPOINT_CAUSE_TIME; }
if (do_checkpoint) { /* Broadcast start counter, do checkpoint, broadcast done */ SpinLockAcquire(&CheckpointerShmem->ckpt_lck); CheckpointerShmem->ckpt_flags = 0; CheckpointerShmem->ckpt_started++; SpinLockRelease(&CheckpointerShmem->ckpt_lck); ConditionVariableBroadcast(&CheckpointerShmem->start_cv);
if (!RecoveryInProgress()) ckpt_performed = CreateCheckPoint(flags); else ckpt_performed = CreateRestartPoint(flags);
SpinLockAcquire(&CheckpointerShmem->ckpt_lck); CheckpointerShmem->ckpt_done = CheckpointerShmem->ckpt_started; SpinLockRelease(&CheckpointerShmem->ckpt_lck); ConditionVariableBroadcast(&CheckpointerShmem->done_cv); }
CheckArchiveTimeout(); pgstat_report_checkpointer(); pgstat_report_wal(true);
/* Sleep until next checkpoint time or signal */ (void) WaitLatch(MyLatch, WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH, cur_timeout * 1000L, WAIT_EVENT_CHECKPOINTER_MAIN); } /* Shutdown path: write shutdown checkpoint, then exit */ if (ShutdownXLOGPending) ShutdownXLOG(0, 0);}checkpointer는 시그널 할당이 독특하다. SIGTERM을 무시한다. 포스트마스터가
정상 종료 시 클라이언트 백엔드에 SIGTERM을 보내는데, checkpointer는 그 단계에서
살아있어야 한다. 종료 시퀀스는 SIGINT(종료 체크포인트 기록)와 이후 SIGUSR2(종료)로
구성되며, 이 순서는 포스트마스터의 PostmasterStateMachine이 강제한다.
그림 2 — CheckpointerShmem 세 카운터 프로토콜
flowchart LR
BE["백엔드가\nRequestCheckpoint 호출"]
REC["ckpt_started 기록\nckpt_flags 비트 설정\n공유 메모리에 쓰기"]
SIG["checkpointer에\nSIGUSR1 전송"]
CKP_S["Checkpointer:\nAbsorbSyncRequests\nckpt_started 증가\nstart_cv 브로드캐스트"]
EXEC["CreateCheckPoint\n또는 CreateRestartPoint"]
CKP_D["Checkpointer:\nckpt_done = ckpt_started\ndone_cv 브로드캐스트"]
WAIT_S["백엔드가\nstart_cv 대기"]
WAIT_D["백엔드가\ndone_cv 대기"]
CHK["백엔드가\nckpt_failed 변화 확인"]
BE --> REC --> SIG --> CKP_S
BE --> WAIT_S
CKP_S --> EXEC --> CKP_D
CKP_S --> WAIT_S
WAIT_S -->|ckpt_started 증가| WAIT_D
CKP_D --> WAIT_D
WAIT_D -->|ckpt_done 따라잡음| CHK
startup 프로세스: WAL 복구 드라이버
섹션 제목: “startup 프로세스: WAL 복구 드라이버”StartupProcessMain(startup.c)은 다섯 메인 함수 중 가장 짧다. StartupXLOG를
호출하고 종료하는 것이 전부다.
// StartupProcessMain — src/backend/postmaster/startup.cvoidStartupProcessMain(const void *startup_data, size_t startup_data_len){ MyBackendType = B_STARTUP; AuxiliaryProcessMainCommon();
on_shmem_exit(StartupProcExit, 0); /* cleanup recovery env on exit */
pqsignal(SIGHUP, StartupProcSigHupHandler); pqsignal(SIGTERM, StartupProcShutdownHandler); /* request abort */ pqsignal(SIGUSR2, StartupProcTriggerHandler); /* promote to primary */
sigprocmask(SIG_SETMASK, &UnBlockSig, NULL);
StartupXLOG(); /* replays WAL; in standby mode, loops continuously */
proc_exit(0); /* exit code 0 → postmaster transitions PMState */}StartupXLOG(xlogrecovery.c)는 컨트롤 파일을 읽고 최신 체크포인트 레코드를
찾아 WAL을 앞으로 재생한다. 재생할 WAL이 없는 1차 기동에서는 빠르게 완료된다.
크래시 복구에서는 마지막 체크포인트 이후의 모든 WAL을 재생한다. 스탠바이 모드에서는
SIGUSR2로 승격(promotion)이 트리거될 때까지 walreceiver로부터 들어오는 WAL을
지속적으로 적용하는 루프에 진입한다.
startup 프로세스의 시그널 의미는 다른 보조 프로세스와 다르다. SIGTERM은
shutdown_requested를 세트해 proc_exit(1) (비정상 종료)을 유발한다. SIGUSR2는
promote_signaled를 세트하며, StartupXLOG가 IsPromoteSignaled()를 폴링해
스탠바이-프라이머리 전환을 트리거한다. in_restore_command 플래그는
restore_command 실행 중(안전하게 멈출 수 있는 지점)에 SIGTERM을 지연 없이
처리할 수 있게 한다.
startup 프로세스는 복구 완료 후 종료하므로 지속적인 이벤트 루프가 없다. 스탠바이
모드에서는 StartupXLOG 내부의 WAL 재생 루프가 실질적인 메인 루프이며, 시그널
핸들러가 WakeupRecovery()를 호출해 루프를 구동한다.
syslogger: stderr 파이프 수집기
섹션 제목: “syslogger: stderr 파이프 수집기”SysLoggerMain(syslogger.c)은 나머지 네 보조 프로세스와 성격이 다르다. 공유
메모리가 존재하기 전에(또는 알 수 없는 상태일 때) 포크되고, 공유 메모리에
전혀 접속하지 않으므로 어떤 공유 인프라도 사용할 수 없다.
동작 원리는 다음과 같다. 포스트마스터가 syslogger를 포크하기 전에 자신의 stderr
(및 이후 포크되는 모든 자식의 stderr)를 파이프로 리디렉션한다. syslogger는
syslogPipe의 읽기 끝에서 읽고, 청크로 분할된 메시지를 재조립해 현재 로그 파일에
기록한다.
// SysLoggerMain startup (condensed) — src/backend/postmaster/syslogger.cvoidSysLoggerMain(const void *startup_data, size_t startup_data_len){ MyBackendType = B_LOGGER; init_ps_display(NULL); /* no AuxiliaryProcessMainCommon */
/* Ignore all termination signals; exit only when pipe EOF seen */ pqsignal(SIGTERM, SIG_IGN); pqsignal(SIGQUIT, SIG_IGN); pqsignal(SIGUSR1, sigUsr1Handler); /* request log rotation */
sigprocmask(SIG_SETMASK, &UnBlockSig, NULL);
/* Main loop: read pipe, rotate files, sleep */ for (;;) { /* read from syslogPipe[0], reassemble chunks into save_buffer list, write completed messages to syslogFile / csvlogFile / jsonlogFile */ process_pipe_input(logbuffer, &bytes_in_logbuffer);
/* rotate if time- or size-based threshold crossed */ logfile_rotate(time_based_rotation, size_rotation_for);
if (pipe_eof_seen) break; /* all writers (postmaster + children) are gone */
(void) WaitEventSetWait(wes, ...); } /* flush remaining input, close log files, exit */}세 개의 로그 파일 핸들이 동시에 존재한다. syslogFile(일반 텍스트),
csvlogFile(CSV 형식), jsonlogFile(JSON 형식)이며, log_destination 설정에
따라 독립적으로 기록된다. 로그 순환은 SIGUSR1(pg_rotate_logfile())이나
시간/크기 임계값(Log_RotationAge, Log_RotationSize)으로 트리거된다.
pipe_eof_seen 플래그는 syslogPipe의 모든 쓰기 끝이 닫힐 때 참이 된다.
포스트마스터가 종료할 때까지 syslogPipe[1]을 보유하므로, syslogger는 클린
종료 시 가장 마지막으로 종료되는 프로세스가 된다.
메시지는 고정 크기 PipeProtoChunk 구조체로 파이프에 실려 전송된다. 긴 로그
메시지를 생성하는 백엔드는 이를 여러 청크로 나눈다. syslogger는 소스 PID를
키로 하는 save_buffer 리스트를 유지해 멀티청크 메시지를 파일에 쓰기 전에
재조립한다.
그림 3 — syslogger 파이프 아키텍처
flowchart TD
PM["postmaster\nstderr → syslogPipe[1]"]
BE["백엔드 + 보조 프로세스\nstderr → syslogPipe[1]"]
PIPE["syslogPipe[1]\n(쓰기 끝)"]
SYS["SysLoggerMain\nsyslogPipe[0]에서 읽기"]
CHUNK["process_pipe_input\nPipeProtoChunk 재조립\nsave_buffer 리스트 활용"]
ROTATE["logfile_rotate\n시간 / 크기 / SIGUSR1"]
FILES["syslogFile\ncsvlogFile\njsonlogFile"]
PM --> PIPE
BE --> PIPE
PIPE -->|읽기| SYS
SYS --> CHUNK --> FILES
SYS --> ROTATE --> FILES
기동 순서와 수명
섹션 제목: “기동 순서와 수명”포스트마스터는 LaunchMissingBackgroundProcesses와 PMState 머신에 의해
보조 프로세스를 정해진 순서로 기동한다.
- Syslogger(
B_LOGGER) — 공유 메모리보다 먼저, 다른 어떤 프로세스보다 먼저 기동한다. 이후 모든 stderr 출력을 캡처한다. - Startup 프로세스(
B_STARTUP) —PM_STARTUP단계에서 기동된다. WAL 복구를 주도하며,proc_exit(0)으로 포스트마스터가pmState를PM_RECOVERY또는PM_RUN으로 전환하게 한다. - checkpointer(
B_CHECKPOINTER)와 bgwriter(B_BG_WRITER) —PM_STARTUP단계부터 기동되며,PM_RECOVERY,PM_HOT_STANDBY,PM_RUN전반에 걸쳐 유지된다. 클라이언트를 받기 전부터 필요하다. - walwriter(
B_WAL_WRITER) —PM_RUN에서만 기동된다. WAL 쓰기는 프라이머리 모드에서만 발생하기 때문이다. - 다섯 프로세스 모두 예기치 않게 종료되면
ServerLoop의 매 반복마다LaunchMissingBackgroundProcesses가 자동으로 재기동한다. 단, syslogger (별도 처리)와 복구 완료 후 정상 종료하는 startup 프로세스는 예외다.
bgwriter, walwriter, checkpointer의 크래시는 포스트마스터가 백엔드 크래시와
동일하게 취급한다. HandleChildCrash가 SIGQUIT을 나머지 자식 전체에 보내고
크래시 복구 사이클을 시작한다. 이 프로세스들은 공유 메모리를 건드리므로,
예기치 않은 종료는 공유 메모리 손상 가능성을 의미한다.
소스 코드 가이드
섹션 제목: “소스 코드 가이드”AuxiliaryProcessMainCommon
섹션 제목: “AuxiliaryProcessMainCommon”AuxiliaryProcessMainCommon(auxprocess.c:39) — 공통 초기화: 포스트마스터 컨텍스트 삭제,InitAuxiliaryProcess+BaseInit+ProcSignalInit+CreateAuxProcessResourceOwner+ pgstat 초기화 호출;ShutdownAuxiliaryProcess를 before-shmem-exit 콜백으로 등록.ShutdownAuxiliaryProcess(auxprocess.c:98) — 모든 LWLock 해제, 조건 변수 대기 취소, pgstat에 wait-end 보고.InitAuxiliaryProcess(proc.c) — 헤비웨이트 잠금 필드 없이 프로세스를 위한PGPROC를 할당.
bgwriter
섹션 제목: “bgwriter”BackgroundWriterMain(bgwriter.c:88) — 진입점;B_BG_WRITER설정,AuxiliaryProcessMainCommon호출;BgBufferSync루프 실행.BgBufferSync(bufmgr.c) — 버퍼 풀의 클락 핸드 스캔;next_to_clean에서 전략 스위프를 따라잡거나,upcoming_alloc_est를 충족하거나,bgwriter_lru_maxpages에 걸릴 때까지 정리;bufs_to_lap == 0 && recent_alloc == 0일 때만true(can_hibernate) 반환.SyncOneBuffer(bufmgr.c) — 핀되지 않고 최근에 사용되지 않은 더티 버퍼 하나를 쓰고,BUF_WRITTEN | BUF_REUSABLE비트마스크 반환.StrategySyncStart(freelist.c) — 현재 클락-스위프 위치와 마지막 호출 이후 할당 횟수를 보고.StrategyNotifyBgWriter(freelist.c) — 다음 버퍼 할당 시 웨이크업을 위한 bgwriter proc 번호 등록/해제.WritebackContextInit/WritebackContext—bgwriter_flush_after병합을 위한 더티 페이지 쓰기 추적.
walwriter
섹션 제목: “walwriter”WalWriterMain(walwriter.c:88) — 진입점;B_WAL_WRITER설정,ProcGlobal->walwriterProc광고,XLogBackgroundFlush루프 실행.XLogBackgroundFlush(xlog.c) — 미기록 WAL 버퍼 쓰기 및 현재 삽입 LSN까지 fsync.SetWalWriterSleeping(walwriter.c) — 비동기 커밋 코드의 타임아웃 계산에 사용되는 전역 플래그 설정.
checkpointer
섹션 제목: “checkpointer”CheckpointerMain(checkpointer.c:182) — 진입점;B_CHECKPOINTER설정;pgstat_before_server_shutdown을 shmem-exit 콜백으로 등록(종료 시 누적 통계를 플러시하는 유일한 프로세스).CheckpointerShmemInit(checkpointer.c) — 포스트마스터 기동 시 공유 메모리에서CheckpointerShmemStruct할당.CheckpointerShmemSize(checkpointer.c) —CalculateShmemSize에 크기 보고.AbsorbSyncRequests(checkpointer.c) — 백엔드가ForwardSyncRequest로 쌓은 fsync 요청 큐 소비.CreateCheckPoint(xlog.c) — 전체 체크포인트 레코드 기록;CheckpointWriteDelay속도 조절로 모든 더티 버퍼를 smgrwrite.CreateRestartPoint(xlog.c) — 스탠바이 모드의 재시작점 동등 함수.RequestCheckpoint(checkpointer.c) — 백엔드가ckpt_flags로 요청을 게시하고SIGUSR1을 전송; 세 카운터 프로토콜로 완료를 선택적으로 대기.
startup 프로세스
섹션 제목: “startup 프로세스”StartupProcessMain(startup.c:216) — 진입점;B_STARTUP설정;StartupXLOG호출; 성공 시 0으로 종료.StartupXLOG(xlogrecovery.c) — 컨트롤 파일 읽기, 마지막 체크포인트부터 WAL 재생; 핫 스탠바이 모드에서 스탠바이 재생 루프 진입.ProcessStartupProcInterrupts(startup.c:154) —got_SIGHUP,shutdown_requested, 포스트마스터 생존 확인, 배리어 시그널 폴링.StartupProcTriggerHandler(startup.c:93) —SIGUSR2핸들러;promote_signaled설정 및WakeupRecovery호출.IsPromoteSignaled/ResetPromoteSignaled—StartupXLOG가 승격 시그널을 폴링하고 확인하는 데 사용.
syslogger
섹션 제목: “syslogger”SysLoggerMain(syslogger.c:165) — 진입점;B_LOGGER설정;AuxiliaryProcessMainCommon미호출;syslogPipe[0]에서 읽기;process_pipe_input과logfile_rotate를 루프에서 호출.process_pipe_input(syslogger.c) — 소스 PID를 키로 하는save_buffer리스트로 파이프의PipeProtoChunk메시지 재조립.logfile_rotate(syslogger.c) — 새 로그 파일 열기; 이전 파일 닫기;last_sys_file_name/last_csv_file_name/last_json_file_name갱신.SysLogger_Start(syslogger.c) — 포스트마스터가syslogPipe생성,stderr리디렉션, syslogger 포크를 위해 호출.write_syslogger_file(syslogger.c) — 백엔드 측에서 메시지를 포맷하고 청크로 나눠syslogPipe[1]에 쓰는 함수.
위치 힌트 (2026-06-05 기준, 커밋 273fe94)
섹션 제목: “위치 힌트 (2026-06-05 기준, 커밋 273fe94)”| 심볼 | 파일 | 줄 |
|---|---|---|
AuxiliaryProcessMainCommon | src/backend/postmaster/auxprocess.c | 39 |
ShutdownAuxiliaryProcess | src/backend/postmaster/auxprocess.c | 98 |
BackgroundWriterMain | src/backend/postmaster/bgwriter.c | 88 |
BgBufferSync | src/backend/storage/buffer/bufmgr.c | 3629 |
SyncOneBuffer | src/backend/storage/buffer/bufmgr.c | 3927 |
BgWriterDelay (GUC) | src/backend/postmaster/bgwriter.c | 58 |
HIBERNATE_FACTOR (bgwriter) | src/backend/postmaster/bgwriter.c | 64 |
LOG_SNAPSHOT_INTERVAL_MS | src/backend/postmaster/bgwriter.c | 70 |
WalWriterMain | src/backend/postmaster/walwriter.c | 88 |
WalWriterDelay (GUC) | src/backend/postmaster/walwriter.c | 70 |
LOOPS_UNTIL_HIBERNATE (walwriter) | src/backend/postmaster/walwriter.c | 78 |
HIBERNATE_FACTOR (walwriter) | src/backend/postmaster/walwriter.c | 79 |
CheckpointerMain | src/backend/postmaster/checkpointer.c | 182 |
CheckpointerShmemStruct | src/backend/postmaster/checkpointer.c | 107 |
CheckPointTimeout (GUC) | src/backend/postmaster/checkpointer.c | 144 |
CheckPointCompletionTarget (GUC) | src/backend/postmaster/checkpointer.c | 146 |
StartupProcessMain | src/backend/postmaster/startup.c | 216 |
ProcessStartupProcInterrupts | src/backend/postmaster/startup.c | 154 |
StartupProcTriggerHandler | src/backend/postmaster/startup.c | 93 |
SysLoggerMain | src/backend/postmaster/syslogger.c | 165 |
Logging_collector (GUC) | src/backend/postmaster/syslogger.c | 70 |
syslogPipe | src/backend/postmaster/syslogger.c | 114 |
NBUFFER_LISTS | src/backend/postmaster/syslogger.c | 109 |
소스 검증 (2026-06-05 기준)
섹션 제목: “소스 검증 (2026-06-05 기준)”검증된 사실
섹션 제목: “검증된 사실”-
AuxiliaryProcessMainCommon은 bgwriter, walwriter, checkpointer, startup에서 호출되지만 syslogger에서는 호출되지 않는다.SysLoggerMain(syslogger.c:165)이MyBackendType = B_LOGGER를 설정하고AuxiliaryProcessMainCommon없이init_ps_display를 직접 호출한다는 사실로 검증됨. syslogger는 공유 메모리가 존재하기 전에 포크될 수 있다. -
bgwriter에는 두 가지 하이버네이트 메커니즘이 있다: 50× 슬립 배수와
StrategyNotifyBgWriter웨이크업 등록.bgwriter.c:329–342에서 검증됨. 조건은WaitLatch의WL_TIMEOUT반환,BgBufferSync의can_hibernate, 이전 사이클의prev_hibernate세 가지가 모두 참일 때다. -
walwriter는
ProcGlobal->walwriterProc으로 자신의 proc 번호를 광고한다.walwriter.c:216에서 검증됨. 비동기 커밋 백엔드가 신호 대신 walwriter의 래치를 직접 설정하는 데 사용하는 필드다. -
checkpointer는
SIGTERM을 무시하고SIGINT를 종료 체크포인트 요청에 사용한다.checkpointer.c:202–209에서 검증됨. 일반 Unix 종료 시init이 모든 프로세스에SIGTERM을 보내며, checkpointer는 포스트마스터가SIGINT로 명시적으로 지시할 때까지 그 단계를 살아남아야 한다. -
checkpointer는 종료 시 누적 통계를 플러시하는 책임을 진다.
checkpointer.c:232에서 검증됨.before_shmem_exit(pgstat_before_server_shutdown, 0)이CheckpointerMain에 등록됨. 주석에 “정상 종료 중 정확히 하나의 프로세스가 이를 호출해야 한다”고 명시됨. -
startup 프로세스는 성공 시 코드 0으로 종료하며, 포스트마스터가 이를 이용해
PMState를 전환한다.startup.c:263–264에서 검증됨. 비정상 종료(예:proc_exit(1))는 포스트마스터가PM_RUN으로 전환하는 대신 크래시 복구에 진입하게 한다. -
syslogger는 텍스트, CSV, JSON 세 가지 로그 형식을 동시에 지원한다.
syslogger.c:82–86에서 검증됨. 세 개의FILE *전역변수(syslogFile,csvlogFile,jsonlogFile)가 각각log_destination설정에 따라 독립적으로 기록됨. JSON 로그 지원은 PG15에 추가됨. -
pipe_eof_seen은 syslogger를 마지막으로 종료하는 프로세스로 만든다.syslogger.c:219–220과 메인 루프 종료 조건에서 검증됨. 파이프 EOF는 모든 쓰기 끝 파일 디스크립터가 닫힐 때만 발생한다. 포스트마스터가 종료할 때까지syslogPipe[1]을 유지하므로, syslogger는 다른 모든 프로세스보다 오래 살아남는다.
미해결 질문
섹션 제목: “미해결 질문”-
B_IO_WORKER와AuxiliaryProcessMainCommon. PG18은 비동기 I/O를 위한B_IO_WORKER프로세스(storage/aio/)를 추가했다. 이 프로세스가AuxiliaryProcessMainCommon을 호출하고 동일한 초기화 경로를 따르는지는 이 문서에서 검증하지 않았다. 조사 경로:storage/aio/aio_worker.c. -
fsync 요청 큐 오버플로.
CheckpointerShmem은 최대MAX_CHECKPOINT_REQUESTS(10,000,000)개의 fsync 요청을 담는다. 큐가 가득 찰 때 백엔드가 블록하는지 직접 fsync로 폴백하는지는 추적하지 않았다. 조사 경로:md.c의ForwardSyncRequest와checkpointer.c의CompactCheckpointerRequestQueue. -
Windows(
EXEC_BACKEND)에서의 syslogger. Windows에서 syslogger는fork()로 파일 디스크립터를 상속하는 대신startup_data매개변수로SysloggerStartupData로 열린 파일 디스크립터를 받는다. syslogger의 전체EXEC_BACKEND재진입 경로(SubPostmasterMain경유)는 여기서 추적하지 않았다.
PostgreSQL 너머 — 비교 설계와 연구 프론티어
섹션 제목: “PostgreSQL 너머 — 비교 설계와 연구 프론티어”-
InnoDB 페이지 클리너 진화. MySQL 5.6 이전에는 InnoDB의 페이지 플러시 스레드가 하나뿐이어서 멀티코어 환경에서 병목이 되었다. MySQL 5.6에서
innodb_page_cleanersGUC로 여러 페이지 클리너 스레드를 도입했다. PostgreSQL의 bgwriter는 단일 스레드를 유지하지만, 스캔 창을 작게 유지하고 체크포인트 분산에 의존한다. PG18의 비동기 I/O(B_IO_WORKER)는 플러셔 수보다 I/O 서브시스템 수준에서 병렬성을 추가한다. -
ARIES 체크포인트 속도 조절. 원래 ARIES 논문은 체크포인트 중에도 정상 처리가 계속될 수 있는 퍼지 체크포인트(fuzzy checkpoint)를 기술한다. PostgreSQL의
checkpoint_completion_target이 바로 이 페이싱 아이디어를 구현한다.CreateCheckPoint에서 호출되는CheckpointWriteDelay는 쓰기 사이에 슬립해 체크포인트를 목표 비율에 걸쳐 분산한다.max_wal_size를 늘리면 체크포인트 빈도가 줄고,checkpoint_completion_target을 높이면 체크포인트당 I/O 스파이크가 줄어든다. -
그룹 커밋 메커니즘으로서의 walwriter. Architecture of a Database System (§6.3)은 그룹 커밋을 핵심 최적화로 설명한다. WAL 레코드 fsync를 기다리는 트랜잭션들을 묶어 하나의 fsync가 여러 커밋을 커버하게 한다. PostgreSQL의 walwriter는
synchronous_commit = off일 때 이를 구현한다. 매wal_writer_delayms마다 WAL을 플러시하며, 비동기 커밋 트랜잭션은 최대wal_writer_delay * 3이내에 레코드 내구성이 보장된다. 이는 명시된 트레이드오프다. 크래시 시 최대wal_writer_delay * 3안에 커밋된 트랜잭션이 유실될 수 있다. -
Oracle 복구 아키텍처 비교. Oracle의 SMON(System MONitor) 프로세스는 온라인 리두 로그를 읽어 인스턴스 복구를 처리한다. PostgreSQL의 startup 프로세스처럼 클라이언트 접근 허가 전에 실행된다. 차이점은 SMON이 여유 익스텐트 병합, 임시 세그먼트 정리 등 지속적인 정리 작업도 담당한다는 점이다. PostgreSQL은 이를 startup 프로세스(순수 복구)와 autovacuum(지속적 정리)으로 분리했다.
-
로그 아키텍처: 파이프 대 공유 메모리 큐. PostgreSQL은 Unix 파이프로 로그 출력을 수집해 모든 쓰기를 syslogger 하나로 직렬화한다. 대안은 잠금 없는 공유 메모리 링 버퍼로, 일부 시스템이 고처리량 로깅에 활용한다. 파이프 방식은 공유 메모리 초기화 전에도 동작해 포스트마스터 초기 기동 메시지를 캡처할 수 있다는 장점이 있다. 공유 메모리 방식은 청킹과 재조립 오버헤드를 없애지만, 첫 번째 로그 메시지 이전에 공유 메모리가 준비되어 있어야 한다.
소비된 원본 파일
섹션 제목: “소비된 원본 파일”- 없음 (REL_18_STABLE / 커밋 273fe94 소스 트리에서 직접 합성).
소스 코드 경로 (REL_18_STABLE / 커밋 273fe94)
섹션 제목: “소스 코드 경로 (REL_18_STABLE / 커밋 273fe94)”src/backend/postmaster/auxprocess.c—AuxiliaryProcessMainCommon,ShutdownAuxiliaryProcesssrc/backend/postmaster/bgwriter.c—BackgroundWriterMain, 하이버네이트 로직,LogStandbySnapshot임무src/backend/storage/buffer/bufmgr.c—BgBufferSyncLRU 클락-스위프 청소 루프,SyncOneBuffersrc/backend/postmaster/walwriter.c—WalWriterMain,SetWalWriterSleeping,XLogBackgroundFlush호출src/backend/postmaster/checkpointer.c—CheckpointerMain,CheckpointerShmemStruct,CheckpointerShmemInit,AbsorbSyncRequests,RequestCheckpointsrc/backend/postmaster/startup.c—StartupProcessMain,ProcessStartupProcInterrupts, 시그널 핸들러, 승격 로직src/backend/postmaster/syslogger.c—SysLoggerMain,SysLogger_Start,process_pipe_input,logfile_rotate,write_syslogger_filesrc/include/miscadmin.h—BackendType열거형(B_BG_WRITER, B_WAL_WRITER, B_CHECKPOINTER, B_STARTUP, B_LOGGER)
교재 및 논문 참고문헌
섹션 제목: “교재 및 논문 참고문헌”- Mohan, C., et al. “ARIES: A Transaction Recovery Method Supporting
Fine-Granularity Locking and Partial Rollbacks Using Write-Ahead Logging.”
ACM TODS, 1992. (체크포인트 개념, 리두 복구.)
knowledge/research/dbms-papers/aries.md에 정리됨. - Hellerstein, Stonebraker, Hamilton. Architecture of a Database System,
Foundations and Trends in Databases, 2007. §6 (버퍼 관리, 백그라운드
라이터, 그룹 커밋.)
knowledge/research/dbms-papers/fntdb07-architecture.md에 정리됨. - Stonebraker, M., and Rowe, L. A. “The Design of POSTGRES.” SIGMOD 1986. (원래 프로세스 모델과 포스트마스터 개념.)