[2026] Linux 시리즈 #09 — 디스크·블록 계층: 저널·복구·할당·I/O 스케줄러
이 글의 핵심
파일시스템 저널이 트랜잭션을 어떻게 기록·복구하는지, 블록 할당기가 공간을 어떻게 고르는지, blk-mq 위에서 스케줄러가 요청을 어떻게 재정렬하는지 연결해 설명합니다.
시리즈 안내
#09 | inode·익스텐트 구조: #03 파일·inode · 실무 트러블슈팅: 디스크 full vs inode · 개요: Linux 완전 가이드
1. 들어가며: 블록 계층에서 “파일시스템”이 하는 일
#03에서 다룬 온디스크 inode·익스텐트는 공간의 의미를 정의합니다. 그 다음 층에서는 (1) 변경을 어떻게 원자적으로 디스크에 반영할 것인가(저널), (2) 빈 블록을 어디서 가져올 것인가(할당기), (3) 커널이 디바이스 큐에 넣은 BIO를 어떤 순서로 내보낼 것인가(스케줄러) 가 이어집니다. 이 글은 프로덕션에서 커널 매개변수·마운트 옵션과 연결되는 내부 직관을 목표로 합니다.
2. 저널링과 복구: ext4(JBD2) 관점
2.1 왜 저널이 필요한가
전원 상실·강제 리셋 순간, 디스크에는 메타데이터만 반쯤 갱신된 블록이 남을 수 있습니다. 저널링은 짧은 순서 있는 로그 영역에 “이 트랜잭션에서 바꿀 블록과 내용”을 먼저 기록해, 재부팅 시 완료 여부를 판정할 수 있게 합니다.
2.2 JBD2 트랜잭션의 뼈대
ext4는 JBD2 위에서 동작합니다. 전형적인 흐름은 다음과 같습니다(세부는 커널 버전에 따라 다름).
- 트랜잭션 시작: 메타데이터 버퍼(비트맵·inode·그룹 디스크립터 등)를 핸들링합니다.
- 저널 공간에 로그: 물리 블록의 이전/이후 이미지 또는 재실행 가능한 로그 레코드 형태로 기록합니다.
- 커밋 단계: 저널에 커밋 레코드가 안전히 남은 뒤, 실제 파일시스템 영역에 적용(checkpoint) 합니다.
fsync/fdatasync 는 이 파이프라인과 맞물려 플러시 지연으로 관측됩니다. 메타데이터 폭주 워크로드는 저널 영역 자체가 순차 쓰기 병목이 되기도 합니다.
커밋 경로를 한 층 더 (JBD2)
실제 구현에서는 트랜잭션 핸들이 러닝 트랜잭션에 버퍼를 모으고, 커밋 시점에 저널 블록에 디스크립터·블록 본문을 순서 있게 쓴 뒤, 커밋 블록(commit block) 으로 “이 트랜잭션은 완료”를 표시합니다. 재부팅 시 커밋 블록이 없거나 잘린 트랜잭션은 미완료로 취급할 수 있습니다. 반대로 커밋은 있는데 체크포인트가 덜 됐다면 리플레이로 앞으로 진행합니다. 배리어(barrier) 는 저널과 메인 FS 영역 사이, 또는 디바이스 캐시 플러시 순서와 맞물려 크래시 일관성을 보장하는 데 관여합니다(스토리지·커널·mount 옵션 조합에 따라 의미가 달라 배포 문서를 따르는 것이 안전합니다).
2.3 저널 모드: journal / ordered / writeback
journal: 데이터까지 저널에 풀 로깅에 가깝게 기록합니다. 쓰기 증폭이 크지만, 데이터까지 포함한 일관성 논의가 단순해지는 면이 있습니다.ordered(흔한 기본): 데이터가 먼저 디스크에 가까운 상태가 된 뒤 메타데이터 커밋이 진행되는 쪽으로 이해할 수 있습니다. Stale data 노출 위험을 줄이려는 타협입니다.writeback: 메타데이터 중심으로 저널링합니다. 성능은 좋을 수 있으나, 크래시 시 데이터 블록 내용과 메타데이터가 가리키는 내용이 어긋날 수 있어 애플리케이션 불변식이 명확해야 합니다.
운영에서는 기본값을 바꾸기 전에 벤치마크와 복구 시나리오(백업·DB 리두 로그)를 함께 검토합니다.
2.4 리플레이(replay)와 e2fsck
재마운트 직후, 커널은 저널을 스캔해 커밋되었지만 체크포인트가 미완료인 트랜잭션을 앞으로 재생합니다. 이것이 흔히 말하는 저널 리플레이입니다. 시간이 짧으면 “저널만 살짝 도는” 수준이고, 구조적 손상이 의심되면 오프라인 e2fsck 가 필요합니다. fsck가 오래 걸리는 경우는 리플레이만이 아니라 전체 비트맵·디렉터리 일관성 검사까지 포함된 경우가 많습니다.
2.5 orphan inode 목록
ext 계열은 트랜잭션 중 크래시 시 반쯤 삭제된 파일을 정리하기 위해 orphan inode 연결 리스트를 씁니다. 부팅 시 고아 inode를 회수하는 경로가 있어, “삭제 도중 끊김” 류의 공간 누수·불일치를 줄입니다.
3. XFS 로그와 복구
3.1 로그 영역과 AG
XFS는 로그(log) 가 메타데이터 변경을 기록합니다. 로그는 전용 영역에 있을 수도 있고, 외부 로그 디바이스로 분리할 수도 있습니다(구성에 따라). AG(할당 그룹) 별로 자유 공간·inode가 B+트리로 관리되므로, 로그 I/O와 데이터 I/O가 서로 다른 AG에서 경쟁하는 패턴이 생깁니다.
3.2 리플레이와 xfs_repair
정상 마운트 시 로그 리플레이로 일관된 상태로 맞추는 것이 먼저입니다. 구조 손상이 의심될 때만 xfs_repair 를 고려하며, 이는 운영 절차·백업 없이 실행하면 안 됩니다. “로그 공간 부족” 과 “데이터 블록 부족” 은 증상이 비슷해 보일 수 있으므로, dmesg의 XFS 메시지와 xfs_info 를 함께 봅니다.
4. 블록 할당 알고리즘
4.1 ext4: 목표와 buddy 계열
ext4는 블록 그룹 안에서 비트맵으로 빈 블록을 찾습니다. 다중 블록 할당(multi-block allocator)은 연속 구간을 선호해 익스텐트와 상성이 좋습니다. 내부적으로는 buddy 아이디어로 인접 후보를 탐색해 단편화를 완화하려 합니다.
운영에서 기억할 점은 다음입니다.
- 연속 할당이 잘 되면 익스텐트 개수·메타데이터 쓰기가 줄어듭니다.
- 랜덤 소량 쓰기가 많으면 비트맵 경쟁·그룹 간 점프가 늘어납니다.
- 디스크 가득 채움 직전에는 마지막 자투리 탐색 비용이 커질 수 있습니다.
4.2 XFS: B+트리와 지연 할당
XFS는 AG별 자유 공간 B+트리로 익스텐트 단위 후보를 찾습니다. 지연 할당은 실제 물리 블록을 늦게 고정해 연속성을 높이려는 전략입니다. 대신 메모리 압박·플러시 시점에 큰 배치 I/O가 몰릴 수 있어, 지연 분산이 관측되기도 합니다.
4.3 프리올로케이션과 단편화
스펙 프리올로케이션(XFS)이나 ext4의 연속 후보 선점은 순차 대용량 쓰기에 유리하지만, 동시 랜덤 워크로드와 섞이면 예약만 잡혀 공간이 “비어 있는데 못 쓰는” 식의 체감이 생길 수 있습니다. 이는 할당기·워크로드 상호작용 이슈입니다.
5. I/O 스케줄러: blk-mq 이후
5.1 레거시 단일 큐에서 멀티 큐로
옛날 CFQ 시절에는 단일 큐에서 시간 슬라이스·프로세스 공정성을 맞췄습니다. 현대 NVMe·멀티 큐 SSD 환경에서는 blk-mq 가 하드웨어 큐에 가깝게 BIO를 분배합니다. 스케줄러는 디바이스 특성에 따라 none(noop에 가까움), mq-deadline, BFQ 등으로 선택됩니다.
5.2 none
최소 재정렬. 디바이스 자체가 깊은 큐·병렬성으로 최적화되어 있다면, 커널에서 추가 지연·합치기가 이득이 아닐 수 있습니다. 저지연·고 IOPS 서버에서 흔히 후보가 됩니다.
5.3 mq-deadline
읽기·쓰기 큐를 나누고, FIFO 순서와 마감 시한(deadline) 을 함께 봅니다. 기아(starvation) 를 막기 위해 오래 기다린 요청을 우선하는 휴리스틱이 들어 있습니다. 혼합 워크로드에서 지연 꼬리를 줄이는 데 도움이 되는 경우가 많습니다.
5.4 BFQ(Budget Fair Queuing)
프로세스· cgroup 단위로 밴드폭을 공정하게 배분하려는 스케줄러입니다. 대화형 지연과 대용량 순차 전송이 섞인 데스크톱·일부 서버에서 유리할 수 있지만, 가상화·DB·저지연 API처럼 스택 전체 지연이 민감한 경우에는 오버헤드가 아쉬울 수 있습니다. 실측 없이 “공정성”만 보고 선택하면 역효과가 날 수 있습니다.
5.5 관측
cat /sys/block/sdX/queue/scheduler
# NVMe는 nvme0n1 등 블록 이름 확인
/proc/diskstats, iostat -x, blktrace/bpftrace 로 큐 깊이·서비스 시간·지연 분포를 보는 것이 스케줄러 변경의 전후를 검증하는 정석입니다.
6. 프로덕션 파일시스템 패턴
6.1 파티션·마운트 분리
/,/var,/var/log, DB 데이터, 백업 스테이징을 동일 스핀들/동일 볼륨에 두면 로그 폭주가 DB fsync 지연으로 전파됩니다.- 저널 전용 외부 디바이스(가능한 구성에서)는 저널 I/O를 분리합니다.
6.2 마운트 옵션(개념)
noatime/relatime: 메타데이터 쓰기를 줄여 콜드 리드가 많은 워크로드에 유리한 경우가 많습니다. 감사 요구가 있으면 정책과 충돌하지 않게 선택합니다.barrier논의: 과거 쓰기 캐시와 플러시 순서 문제와 연결되어 왔습니다. 현대 스택에서는 디바이스·파일시스템·커널 조합에 따라 의미가 달라 문서·배포 가이드를 따르는 것이 안전합니다.
6.3 품질·용량 관리
fstrim/discard: SSD TRIM 패턴은 환경에 따라 지연·스파이크를 바꿉니다. 주기적 fstrim 타이머 vs 연속 discard 트레이드오프를 검토합니다.xfs_growfs: XFS는 온라인 확장이 가능하지만 축소는 안 됨이 일반적입니다. LVM과 함께 설계할 때 향후 확장만 전제로 합니다.
6.4 모니터링
- 블록:
node_filesystem_avail_bytes등. - inode:
node_filesystem_files_free등. - I/O: 디바이스별
util,await, 큐 깊이. 스케줄러만 바꿔서 응용 지연이 좋아지는지 엔드투엔드로 봅니다.
7. 마무리
이 글은 저널 트랜잭션·리플레이, ext4·XFS 블록 할당기의 목표, blk-mq 위의 스케줄러 행동을 한 호흡에 묶었습니다. inode·익스텐트 표현 자체는 #03을, 용량·inode 고갈 순서는 트러블슈팅 가이드를 함께 두면 운영 시나리오가 완결됩니다.
내부 동작과 핵심 메커니즘
이 글의 주제는 「[2026] Linux 시리즈 #09 — 디스크·블록 계층: 저널·복구·할당·I/O 스케줄러」입니다. 여기서는 앞선 설명을 구현·런타임 관점에서 한 번 더 압축합니다. 시스템·런타임 경계(스케줄링, I/O, 메모리, 동시성)를 기준으로 생각하면, “입력이 어디서 검증되고, 핵심 연산이 어디서 일어나며, 부작용(I/O·네트워크·디스크)이 어디서 터지는가”가 한눈에 드러납니다.
처리 파이프라인(개념도)
flowchart TD A[입력·요청·이벤트] --> B[파싱·검증·디코딩] B --> C[핵심 연산·상태 전이] C --> D[부작용: I/O·네트워크·동시성] D --> E[결과·관측·저장]
알고리즘·프로토콜 관점에서의 체크포인트
- 불변 조건(Invariant): 각 단계가 만족해야 하는 조건(예: 버퍼 경계, 프로토콜 상태, 트랜잭션 격리)을 문장으로 적어 두면 디버깅 비용이 줄어듭니다.
- 결정성: 동일 입력에 동일 출력이 보장되는 순수한 층과, 시간·네트워크에 의해 달라질 수 있는 층을 분리해야 테스트와 장애 분석이 쉬워집니다.
- 경계 비용: 직렬화/역직렬화, 문자 인코딩, syscall 횟수, 락 경합처럼 “한 번의 호출이 아니라 누적되는 비용”을 의심 목록에 넣습니다.
프로덕션 운영 패턴
실서비스에서는 기능 구현과 함께 관측·배포·보안·비용이 동시에 요구됩니다. 아래는 팀에서 자주 쓰는 최소 체크리스트입니다.
| 영역 | 운영 관점에서의 질문 |
|---|---|
| 관측성 | 요청 단위 상관 ID, 에러율/지연 분위수, 주요 의존성 타임아웃이 보이는가 |
| 안전성 | 입력 검증·권한·비밀 관리가 코드 경로마다 일관적인가 |
| 신뢰성 | 재시도는 멱등한 연산에만 적용되는가, 서킷 브레이커·백오프가 있는가 |
| 성능 | 캐시 계층·배치 크기·풀링·백프레셔가 데이터 규모에 맞는가 |
| 배포 | 롤백 룬북, 카나리, 마이그레이션 호환성이 문서화되어 있는가 |
운영 환경에서는 “개발자 PC에서는 재현되지 않던 문제”가 시간·부하·데이터 크기 때문에 드러납니다. 따라서 스테이징의 데이터 양·네트워크 지연을 가능한 한 현실에 가깝게 맞추는 것이 중요합니다.
문제 해결(Troubleshooting)
| 증상 | 가능 원인 | 조치 |
|---|---|---|
| 간헐적 실패 | 레이스 컨디션, 타임아웃, 외부 의존성 불안정 | 최소 재현 스크립트 작성, 분산 트레이스·로그 상관관계 확인 |
| 성능 저하 | N+1 쿼리, 동기 I/O, 잠금 경합, 과도한 직렬화 | 프로파일러·APM으로 핫스팟 확인 후 한 가지씩 제거 |
| 메모리 증가 | 캐시 무제한, 클로저/이벤트 구독 누수, 대용량 객체의 불필요한 복사 | 상한·TTL·스냅샷 비교(힙 덤프/트레이스) |
| 빌드·배포만 실패 | 환경 변수·권한·플랫폼 차이 | CI 로그와 로컬 diff, 컨테이너/런타임 버전 핀(pin) |
권장 디버깅 순서: (1) 최소 재현 만들기 (2) 최근 변경 범위 좁히기 (3) 의존성·환경 변수 차이 확인 (4) 관측 데이터로 가설 검증 (5) 수정 후 회귀·부하 테스트.