플러싱과 Dirty Page: 가상 메모리의 디스크 반영 방식

가상 메모리에서 Dirty Page를 디스크에 반영하는 플러싱의 흐름, 운영체제별 구현, 파일 시스템 연계와 튜닝 지점을 정리한다.

2026-08-14 · 최초 발행 2025-12-28

메모리에 올라온 페이지가 수정되면 디스크의 원본은 더 이상 현재 상태를 보장하지 못한다. 플러싱은 이 차이를 해소하기 위해 Dirty Page를 영구 저장소에 기록하는 과정이다. 페이지 교체 시 수정 내용을 보존하고, 시스템 크래시에서 데이터 손실을 줄이며, 메모리와 디스크 사이의 일관성을 유지하는 데 쓰인다. Page Write-out, Disk Sync라는 이름으로도 불린다.

플러싱은 페이지 교체 과정, 주기적 동기화, 명시적인 sync 호출에서 발생할 수 있다.

Dirty Bit가 쓰기 필요 여부를 판단한다

Dirty Bit는 페이지 테이블 엔트리(PTE)에 있는 1비트다. 값이 0이면 수정되지 않은 Clean Page이고, 1이면 수정된 Dirty Page다. 하드웨어의 MMU가 쓰기 동작에서 이 비트를 자동으로 설정하며, OS는 이를 읽는다. 플러싱이 끝난 뒤에는 OS가 값을 0으로 되돌리고, 페이지를 적재할 때도 초기화한다.

Dirty Bit = 0Write OperationFlushingDirty Bit = 0Page LoadClean PageDirty Page (Dirty Bit = 1)Disk Write

페이지 폴트가 페이지 교체와 플러싱으로 이어지는 경우

요구한 페이지가 메모리에 없으면 Page Fault가 발생하고, OS는 LRU·FIFO·Clock 등의 알고리즘으로 Victim Page를 고른다. 그 다음 Dirty Bit를 검사한다.

Clean Page는 디스크와 같은 상태이므로 즉시 교체할 수 있다. 반대로 Dirty Page는 디스크의 내용과 다르기 때문에 플러싱을 먼저 수행해야 하며, 이만큼 지연이 더해진다. 쓰기가 완료되면 새 페이지를 적재하고 페이지 테이블을 갱신한 뒤 프로세스를 다시 실행한다.

0 (Clean)1 (Dirty)Page FaultVictim Page 선택Dirty Bit 확인즉시 교체Flushing 수행Disk WriteDirty Bit = 0 페이지 로드프로세스 재개

쓰기 시점에 따라 달라지는 플러싱 방식

Demand Flushing은 페이지를 교체할 때만 Dirty Page를 기록한다. 불필요한 쓰기와 CPU 사용을 줄일 수 있지만, 교체 순간의 지연이 길어지고 버스트 I/O가 발생할 수 있다. 메모리 여유가 충분한 시스템에 맞는 방식이다.

Background Flushing은 Flusher 데몬 또는 스레드가 백그라운드에서 Dirty Page를 주기적으로 검사하고 비동기 쓰기를 수행한다. 페이지 교체 시간을 줄이고 I/O 부하를 분산하며 데이터 손실 위험을 낮출 수 있다. 대신 CPU와 I/O 오버헤드가 생기고 불필요한 쓰기가 발생할 여지도 있다. 대부분의 현대 OS가 이 방식을 사용한다.

Periodic Sync는 일정 시간 간격(예: 30초)으로 모든 Dirty Page를 디스크에 기록한다. 시스템 크래시 시 데이터 손실을 줄이는 것이 목적이며, sync() 시스템 콜로 구현한다.

DiskMemory ManagerOperating SystemProcessDiskMemory ManagerOperating SystemProcessBackground Flusher 동작loop[주기적 스캔]Page Fault 발생alt[Clean Page][Dirty Page]Dirty Page 검색비동기 쓰기완료Dirty Bit = 0Page Fault페이지 교체 요청Victim 선택즉시 교체Flushing (동기 쓰기)완료새 페이지 로드데이터재개

운영체제가 Dirty Page를 처리하는 방식

Linux에서는 pdflush/flush 커널 스레드가 Dirty Page를 주기적으로 스캔하고, 임계값을 넘으면 플러싱한다. dirty_ratio는 전체 메모리에서 Dirty Page 비율의 상한을, dirty_background_ratio는 백그라운드 플러싱 시작 비율을 지정한다. dirty_expire_centisecs는 Dirty Page 만료 시간이고, dirty_writeback_centisecs는 플러싱 간격이다. 명시적인 동기화에는 sync(), fsync(), msync()를 사용한다.

Windows에서는 Lazy Writer가 백그라운드에서 주기적으로 플러싱하며 기본 간격은 1초다. Available Memory가 임계값 아래로 내려가면 Modified Page Writer가 활성화되어 Dirty Page를 디스크에 쓴다. FlushFileBuffers(), FlushViewOfFile()로 명시적인 플러싱도 할 수 있다.

macOS의 update 데몬은 30초마다 모든 Dirty Buffer를 플러싱한다. UBC(Unified Buffer Cache)는 파일 시스템과 VM을 통합 관리하며 동적 플러싱 전략을 사용한다.

macOS Flushingupdate 데몬30초 간격 동기화UBC동적 플러싱Windows FlushingLazy Writer주기적 플러싱 (1초)Modified Page Writer메모리 압박 플러싱Linux Flushing초과미만pdflush/flushDirty Page 스캔임계값 체크플러싱대기

I/O 비용을 다루는 방법

Write Coalescing은 인접한 Dirty Page를 하나의 I/O로 묶는 방식이다. 디스크 헤드 이동을 줄이고 I/O 처리량을 높일 수 있으며, I/O 스케줄러 수준에서 구현한다.

Delayed Write는 즉시 쓰지 않고 버퍼에 보관하는 방식이다. 같은 페이지가 여러 번 수정돼도 한 번만 기록할 수 있어 쓰기 횟수가 줄어든다. 다만 플러싱 전에 크래시가 발생하면 데이터가 손실될 수 있다.

Write-through는 모든 쓰기를 즉시 디스크에 반영하므로 안전하지만 느리다. Write-back은 메모리에 먼저 기록한 뒤 나중에 플러싱하므로 빠르지만 데이터 손실 위험이 있다. 일반적으로는 Write-back과 주기적 플러싱을 함께 선택한다.

메타데이터와 저널 데이터는 우선순위를 높게 두고, 일반 파일 데이터와 캐시 데이터는 상대적으로 낮게 처리한다.

높음낮음Dirty PagesWrite Coalescing인접 페이지 병합I/O 요청 생성I/O 스케줄러우선순위메타데이터일반 데이터Disk Write

파일 시스템 정책과 맞물리는 지점

저널링은 파일 시스템 일관성을 위해 변경 사항을 저널에 먼저 기록하고, 저널 플러싱 후 실제 데이터 영역을 갱신한다. 크래시가 발생했을 때 복구를 빠르게 하는 방식이다.

COW(Copy-On-Write)는 수정 내용이 생기면 새 위치에 기록한다. 원본은 보존하고 새 데이터만 쓰며, Btrfs와 ZFS가 예다.

메타데이터 플러싱은 파일 시스템 구조 변경을 즉시 반영하므로 우선순위가 높다. 데이터 플러싱은 더 유연한 정책을 적용해 성능을 조정할 수 있다.

쓰기 증폭과 I/O 폭주를 피하려면

SSD에서는 작은 쓰기가 큰 블록 쓰기로 증폭되는 Write Amplification이 발생할 수 있고, 이는 SSD 수명을 단축한다. 쓰기 병합과 대용량 쓰기로 완화할 수 있다.

대량의 Dirty Page를 한 번에 플러싱하면 I/O Storm이 발생해 시스템 응답성이 떨어질 수 있다. 플러싱 속도 제한(throttling)이 완화 수단이다.

플러싱 전에 시스템이 크래시하면 데이터 손실이 생길 수 있다. 주기적 플러싱 간격을 줄이고, UPS(무정전 전원 장치)와 저널링 파일 시스템을 함께 사용하는 방법이 있다.

Linux에서 상태를 확인하고 조정하기

# Dirty Page 통계 확인
cat /proc/vmstat | grep dirty

# 현재 설정 확인
sysctl -a | grep dirty

# 설정 변경 (예: dirty_ratio를 20%로)
sysctl -w vm.dirty_ratio=20

관찰할 지표는 전체 메모리 대비 Dirty Page 비율, 단위 시간당 플러싱 횟수, 플러싱으로 인한 I/O 대기 시간, 플러싱 I/O 대역폭이다.

플러싱가상 메모리Dirty Page페이지 교체디스크 동기화