PostgreSQL의 WAL File I/O에 이어, 이번 글에서는 메모리 상의 변경 내용이 Data File에 반영되는 과정을 다루도록 하겠습니다. Data File I/O가 발생하는 주요 흐름과, 그 과정에서 나타나는 Wait Event를 중심으로 설명하겠습니다.
Data File
Data File은 테이블과 인덱스 등 사용자 데이터가 저장되는 파일입니다.
Shared Buffer에 존재하는 Data Page는 Write 작업을 통해 Data File로 Flush되어 영구 저장되며, 프로세스에 의해 요청되는 Data Page는 Read 작업을 통해 Data File에서 Shared Buffer로 적재됩니다.
즉, Data File을 대상으로 수행되는 모든 I/O 작업은 Shared Memory 영역의 Shared Buffer를 매개로 발생합니다.
Data File과 프로세스
Data File I/O 작업은 Shared Buffer를 매개로 하여 BGWriter, Checkpointer, Backend Process에 의해 수행됩니다.

BG Writer Process
BG Writer는 Shared Buffer에 저장된 Dirty Page를 주기적으로 Data File로 Writer하는 프로세스입니다.
Dirty Page를 사전에 정리함으로써 Shared Buffer의 여유 공간을 확보하고, Data File에 대한 I/O를 분산하는 역할을 합니다.
Checkpointer Process
Checkpointer는 Checkpoint 발생 시점에 Shared Buffer에 존재하는 Dirty Page를 일괄적으로 Data File에 Flush합니다.
Checkpoint 시점까지의 데이터 변경 내용을 Data File에 반영하는 것을 목표로 하기 때문에, Checkpointer에 의해 발생하는 Data File I/O는 대량으로 집중적으로 발생할 수 있습니다.
Checkpoint와 Checkpointer에 대해서는 “PWI - I/O > Checkpoint”에서 자세히 다루겠습니다.
Backend Process
Backend Process는 SQL 실행 시 필요한 Data Page가 Shared Buffer에 존재하지 않을 경우, Data File에서 직접 Read하여 Shared Buffer에 적재합니다. 또한, Shared Buffer 공간이 부족한 경우, Dirty Page를 직접 Data File에 Write하는 상황도 발생할 수 있습니다.
이러한 Backend Process에 의한 Data File I/O는 쿼리 실행 과정 중에 이뤄지므로, 응답 시간 증가 등 성능 측면에서 가장 불리한 형태의 I/O입니다.
Wait Event
Data File을 대상으로 하는 Read/Write 작업에 의해 I/O가 증가하면 IO:DataFileRead와 IO:DataFileWrite 두 Wait Event가 발생할 수 있습니다.
이 Wait Event는 Data File I/O 작업 완료를 대기하는 프로세스에서 관찰됩니다.
IO:DataFileRead
프로세스에 의해 요청된 Data Page가 Shared Buffer에 존재하지 않아 Data File에서 해당 Page를 읽어오는 작업을 대기할 때 발생하는 Wait Event입니다.
IO:DataFileRead는 대부분 Backend Process에서 발생하며, 대기 시간이 길어질 경우 쿼리 응답시간에 직접적인 영향을 미칩니다.
IO:DataFileWrite
프로세스가 Shared Buffer에 존재하는 Dirty Page를 Data File로 Write할 때, 작업의 완료를 대기하는 과정에서 발생하는 Wait Event입니다.
IO:DataFileWrite는 BG Writer, Checkpointer, Backend Process 모두에서 발생할 수 있으며, 특히 Backend Process에서 발생하는 경우 Data File에 대한 I/O가 완료될 때까지 쿼리 실행이 지연되므로 더욱 주의를 요합니다.
주요 발생 원인
- Shared Buffer 크기
Shared Buffer 크기가 충분하지 않을 경우, 필요한 Data Page가 Shared Buffer 내에 존재하지 않은 상황이 빈번하게 발생됩니다. 이로 인해 Data Page를 Data File로부터 읽어오는 과정에서 IO:DataFileRead 발생이 증가할 수 있습니다.
또한 Shared Buffer 공간 확보를 위해 Dirty Page를 즉시 Data File로 Write해야 하는 상황이 자주 발생한다면, Backend Process에 의한 IO:DataFileWrite 역시 증가하게 됩니다.
- 대량의 데이터를 대상으로 하는 SQL
대용량 테이블에 대한 Sequential Scan, 비효율 인덱스의 사용 등, 필요 이상의 데이터를 조회하는 경우 연관된 Data Page를 모두 Read해야 합니다. 이러한 과정에서 Data File을 대상으로 한 Read I/O가 급증하며 IO:DataFileRead 발생 빈도가 증가할 수 있습니다.
데이터를 변경하는 작업(
INSERT/UPDATE/DELETE)인 경우에는 많은 Data Page가 Dirty Page가 되며, 이후 BG Writer, Checkpointer 또는 Backend Process에 의해 Data File로 Write되어야 하는 Dirty Page 수가 증가합니다. 그 결과 IO:DataFileWrite 발생 또한 함께 증가할 수 있습니다.- BG Writer와 Checkpointer 동작 설정
Shared Buffer의 Dirty Page를 Data File에 Write하는 Background Process인 BG Writer와 Checkpointer는 관련 파라미터 설정에 따라 Data File을 대상으로 한 I/O 발생 시점과 부하 정도가 달라집니다.
BG Writer가 미리 충분한 Dirty Page를 처리하도록 설정된 경우, Backend Porcess에 의한 I/O를 줄일 수 있으며, Checkpoint 시점에 집중되는 I/O 또한 완화할 수 있습니다. 반면, BG Writer 처리량이 부족하여 Backend Process나 Checkpointer에 의해 처리되는 양이 많아진다면, I/O가 특정 시점에 집중되어 IO:DataFileWrite 발생이 단시간에 급증할 수 있습니다.
- Analyze와 Vacuum 작업 수행
- Vacuum - 테이블 및 인덱스 Page를 순차적으로 Scan하면서 Dead Tuple을 제거하고, 필요 시 Page 내용을 갱신하여 Data File에 Write합니다. 이 과정에서 Read 작업으로 인한 대량의 I/O가 발생하며, 상황에 따라 Write I/O가 동반됩니다.
- Analyze - 테이블의 일부 Page를 Sampling하여 Read하여 통계 정보를 수집하므로, Data File에 대한 Read I/O를 지속적으로 발생시킵니다.
테이블이나 인덱스에 존재하는 Dead Tuple을 정리하는 Vacuum과 통계 정보를 수집하는 Analyze와 같은 관리 작업 수행 시에도 Data File을 대상으로 한 I/O 작업이 발생합니다.
따라서 Analyze와 Vacuum 작업을 수행하는 과정에서도 IO:DataFileRead, IO:DataFileWrite Wait Event가 발생할 수 있습니다. 이 경우 한 번에 처리하는 데이터 양을 제한하거나 작업 수행 시점을 분산함으로써 Data File I/O 부하를 줄일 수 있습니다.
해결 방안
Data File을 대상으로 한 I/O로 인해 IO:DataFileRead, IO:DataFileWrite Wait Event가 빈번하게 발생하는 경우, 다음과 같은 방안을 고려할 수 있습니다.
- Shared Buffer 크기(shared_buffer)를 확장하면 필요한 Data Page가 Shared Buffer에 존재할 가능성이 높아지므로 Buffer Miss로 인한 Data File I/O가 감소합니다. 동시에 Shared Buffer 공간 확보를 위한 Backend Process의 직접적인 Data File I/O 발생도 줄일 수 있습니다.
- 불필요한 대량의 데이터 Scan이 발생하지 않도록 쿼리 실행 계획을 분석, 적절한 인덱스 설계 및 활용으로 Data File I/O를 줄일 수 있도록 쿼리 레벨의 최적화를 수행합니다. 그리고 한번에 많은 데이터를 변경하는 작업은 배치로 나누어 처리하여 작업량을 제한함으로써 I/O 부하가 분산될 수 있도록 합니다.
- BG Writer가 주기적으로 동작하는 간격이 너무 크면, Dirty Pge 정리가 그만큼 지연되어 Backend Process에 의해 Data File I/O가 수행될 가능성이 높아집니다. 반면, 적절한 간격으로 BG Writer가 동작하도록 설정했다면, Dirty Page가 Background에서 사전에 잘 정리되므로 Backend Process에 의해 발생하는 Data File I/O를 줄일 수 있습니다.
bgwriter_delay(Default, 200)
BG Writer Process의 동작 주기를 지정하는 파라미터로, 설정한 주기에 따라 BG Writer는 Shared Buffer의 Dirty Page에 대하여 Write 작업을 수행합니다.
Checkpoint 관련 설정이 적절하지 않은 경우, Dirty Page가 한 시점에서 대량으로 Flush되면서 IO:DataFileWrite가 단시간에 급증할 수 있으므로, Checkpoint 관련 파라미터 조정을 통해 I/O가 특정 시점에 집중되지 않도록 합니다.
- Analyze와 Vacuum과 같은 작업은 의도적으로 테이블 및 인덱스 Page를 Scan하여 관리하는 작업이므로, 많은 양의 데이터를 처리하는 만큼 Data File를 대상으로 하는 I/O 발생 자체를 피할 수 없습니다. 한번에 작업하는 양을 제한하거나, 작업 수행 주기 및 작업 수행 시점(일량이 적은 시간대) 등을 조정하여 Data File에 대한 I/O를 줄이면 사용자 쿼리 성능에 미치는 영향을 최소화할 수 있습니다.
Parameter | 설명 |
default_statistics_target (Default, 100) | - Analyze 작업 시 대상으로 하는 샘플 데이터의 양을 지정
- 0~10,000까지 설정 가능
- 값이 커질수록 샘플 크기가 커지면서 정교하게 수집 (작업 리소스 사용량은 증가) |
autovacuum_vacuum_threshold
(Default, 50) | - 하나의 테이블에 대해서 Autovacuum이 트리거되는 최소한의 Dead Tuple 개수
- 전역적으로 혹은 개별 테이블에 대해서 설정 가능 |
autovacuum_vacuum_scale_factor
(Default, 0.2) | - 하나의 테이블에 대한 Autovacuum 트리거 여부를 결정하기 위해 확인하는 Dead Tuple의 비율
- 하나의 테이블에 대해 아래의 계산식에 따라 결정된 값보다 더 많은 Dead Tuple이 생성되면 Autovacuum이 트리거됨
(테이블 크기(Tuple) * autovacuum_vacuum_scale_factor) + autovacuum_vacuum_threshold
- 전역적으로 혹은 개별 테이블에 대해서 설정 가능 |
autovacuum_vacuum_cost_limit
(Default, -1) | - Autovacuum 작업에 사용될 비용 제한 값
- Default일 경우, Database에서 전역적으로 적용되는 vacuum_cost_limit(Default, 200) 값을 따름
- 전역적으로 혹은 개별 테이블에 대해서 설정 가능 |
BG Writer 동작 주기에 따른 Data File I/O 발생 비교
테스트를 통해 BG Writer 동작 주기(bgwriter_delay)의 변화에 따라 Data File I/O 발생량이 어떻게 달라지는지 확인해보겠습니다.
위와 같이 2000만건을 Insert하는 쿼리를 bgwriter_delay 값을 10ms, 200ms, 4000ms으로 변경해가며 테스트를 진행하였습니다.
bgwriter_delay=10ms
- buffers_chekpoint는 Checkpointer가 Data File로 Write한 Buffer 수로 타 프로세스 대비 I/O 비중이 크지 않으며,(
2901), BG Writer는 비교적 많은 양의 Dirty Page를 처리한 것을 볼 수 있습니다.(buffers_clean:204264)
- buffers_backend는 Backend Process가 직접 Data File로 Write한 Dirty Page를 의미하며 전체의 51%(
221571)로 매우 높게 나타납니다. 이는 Shared Buffer 공간 확보를 위해 Data File I/O를 빈번하게 수행했음을 의미합니다.
- maxwritten_clean 값이
0으로, 이는 BGWriter가 설정된 Write 한도(bgwriter_lru_maxpages) 내에서 원활히 Dirty Page를 처리하였음을 의미합니다. BG Writer가 사전에 충분하게 Dirty Page를 처리하고 있음에도 Backend Process가 많은 양을 처리한 원인은 대량의 Dirty Page가 생성되는 Insert Workload로 인한 것으로 해석할 수 있습니다.
📢 pg_stat_bgwriter.maxwritten_clean
BG Writer가 LRU 스캔을 통해 Dirty Page를 Write하려 했지만, bgwriter_lru_maxpages 한도에 도달해 조기 종료된 횟수를 나타냅니다.
maxwritten_clean이 자주 증가한다면, BG Writer가 Dirty Page를 미리 정리하지 못하고 있거나 BG Writer 처리량이 Dirty Page 생성 속도를 따라가지 못하는 것으로 분석할 수 있습니다.
bgwriter_lru_maxpages(Default, 100)는 BG Writer가 처리할 수 있는 Dirty Page의 최대 양을 설정하는 파라미터입니다.
0으로 설정하면 BG Writer는 Dirty Page를 처리하지 않으며, Dirty Page는 Checkpointer나 Backend Process에 의해 처리됩니다.bgwriter_delay=200ms, bgwriter_delay=4000ms
- bgwriter_delay를 200ms, 4000ms로 조정하여 동일한 쿼리를 실행한 결과, BG Writer 동작 주기가 길수록 BG Writer가 처리하는 Dirty Page의 비중이 감소하는 것을 확인할 수 있습니다.(buffers_clean: 204264→
168583→19954)
- 반면 Backend Process가 직접 Write한 Dirty Page 수는 bgwriter_delay 값이 커질수록 지속적으로 더 증가하였습니다.(buffers_backend: 221571→
261449→414326) 특히 bgwriter_delay=4000ms인 경우, 전체 Dirty Page의 약 95%가 Backend Process에 의해 처리되었습니다.
Insert Workload와 같이 Dirty Page 생성 속도가 빠른 환경에서는 Shared Buffer 부족이 빈번하게 발생하며, 이로 인해 Backend Process에 의한 Data File I/O가 급증하게 됩니다.
- maxwritten_clean 수치는 증가한 것을 확인할 수 있습니다. 이는 BG Writer가 Dirty Page를 처리하는 과정에서 설정된 최대 처리 한도(bgwriter_lru_maxpages)에 도달하여 작업이 중단된 상황이 발생했음을 의미합니다.
Wait Event 발생 비교
이번에는 bgwriter_delay 값을 10ms, 200ms, 4000ms으로 설정했을 때 Data File I/O와 관련된 Wait Event 발생 이력을 비교 분석해보겠습니다.
- IO:DataFileExtend가 모든 CASE에서 확인되는데, 이 Wait Event는 Data File이 확장될 때 발생하는 I/O 대기를 의미합니다. Insert 과정에서 테이블 파일 확장이 발생하며, 동일한 쿼리를 수행했기 때문에 처리되는 데이터 양도 동일하여 각 CASE에서 유사한 count가 기록된 것을 볼 수 있습니다.
- Insert 작업 특성상 Read 작업은 불필요 하지만, IO:DataFileRead가 일부 확인됩니다. 이는 Insert 수행 중 새로운 Page를 확보하는 과정에서 FSM 확인이나 relation metadata에 접근하는 등, Buffer Manager 과정에서 발생한 부수적인 I/O로 볼 수 있습니다.
- CASE2에서는 CASE1과 달리 IO:DataFileRead와 IO:DataFileWrite를 확인할 수 있습니다. bgwriter_delay 10→
200으로 조정하면서 BG Writer 동작 주기가 길어져 Backend가 직접 Data File로 Write하는 상황이 증가했기 때문입니다.
- CASE3의 경우, IO:DataFileWrite가 가장 많이 발생, 증가했습니다. 위에서 확인한대로 BG Writer가 처리한 Buffer 수가
19954로, Dirty Page 대부분이 Shared Buffer에 있고, Shared Buffer 공간이 필요한 시점마다 Backend에 의해 Data File I/O 집중적으로 발생했기 때문입니다.
마무리
- Data File은 테이블과 인덱스 등 사용자 데이터가 저장되는 파일이다.
Data File을 대상으로 수행되는 모든 I/O 작업은 Shared Memory 영역의 Shared Buffer를 매개로 발생한다.
- Data File 관련 I/O 작업은 BGWriter, Checkpointer, Backend Process에 의해 수행된다.
이 중 BG Writer는 Dirty Page를 사전에 정리하여 Shared Buffer의 여유 공간을 확보하고, Data File에 대한 I/O를 분산하는 역할을 한다.
- Data File을 대상으로 하는 Read/Write 작업으로 인해 I/O가 증가하면 IO:DataFileRead와 IO:DataFileWrite가 발생할 가능성이 높아진다.
- bgwriter_delay 파라미터는 BG Writer의 동작 주기를 결정한다. 값이 작을 수록 BG Writer에 의해 주기적으로 처리되는 Dirty Page가 증가하며, 값이 클수록 BG Writer에 의한 처리가 늦어지면서 Backend Process가 직접 Dirty Page를 처리하는 비중이 증가한다.
- WAL File과 마찬가지로, Data File에 대한 Backend Process의 직접 쓰기 비율이 높을 수록 사용자 응답성을 저해할 수 있다.
- Backend Process에 의한 IO:DataFileWrite 발생 빈도가 높은 경우, BG Writer가 Dirty Page를 충분히 미리 처리할 수 있도록 bgwriter_delay 값을 우선적으로 점검한다. pg_stat_bgwriter의 maxwritten_clean 값이 지속적으로 증가한다면, bgwriter_lru_maxpages 파라미터를 확인하여 조정한다.
함께 보면 좋은 아티클
