🤖

본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.

⚠️

본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.

이미지 로딩 중...

파일 시스템 완벽 가이드 - 슬라이드 1/7
A

AI Generated

2025. 12. 28. · 3 Views

파일 시스템 완벽 가이드

운영체제의 핵심인 파일 시스템을 쉽게 이해할 수 있도록 정리한 가이드입니다. 파일의 개념부터 저널링까지, 실무에서 꼭 알아야 할 내용을 담았습니다.


목차

  1. 파일 개념과 속성
  2. 디렉토리 구조
  3. 파일 할당 방법
  4. FAT와 i-node
  5. 파일 시스템 마운트
  6. 저널링 파일 시스템

1. 파일 개념과 속성

어느 날 김개발 씨가 리눅스 서버에서 작업을 하다가 문득 궁금해졌습니다. "파일이라는 게 정확히 뭘까?

그냥 데이터 덩어리인가?" 옆자리 박시니어 씨가 웃으며 말했습니다. "파일은 단순한 데이터가 아니에요.

운영체제가 관리하는 정보의 기본 단위죠."

파일은 관련된 정보의 집합으로, 운영체제가 저장 장치에 기록하는 논리적인 단위입니다. 마치 서류 캐비닛에 보관된 문서처럼, 각 파일은 고유한 이름과 속성을 가지고 있습니다.

파일의 속성을 이해하면 권한 관리와 보안 설정을 제대로 할 수 있습니다.

다음 코드를 살펴봅시다.

// Java에서 파일 속성 확인하기
import java.nio.file.*;
import java.nio.file.attribute.*;

public class FileAttributeExample {
    public static void main(String[] args) throws Exception {
        Path path = Paths.get("/home/user/document.txt");

        // 기본 속성 읽기
        BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);

        System.out.println("파일 크기: " + attrs.size() + " bytes");
        System.out.println("생성 시간: " + attrs.creationTime());
        System.out.println("수정 시간: " + attrs.lastModifiedTime());
        System.out.println("디렉토리 여부: " + attrs.isDirectory());
    }
}

김개발 씨는 입사 2개월 차 신입 개발자입니다. 오늘 서버에 로그 파일을 저장하는 기능을 개발하다가 이상한 에러를 만났습니다.

"Permission denied"라는 메시지가 뜨는데, 분명 파일은 존재하는 것 같았습니다. 선배 개발자 박시니어 씨가 다가와 터미널을 살펴봅니다.

"아, 파일 속성을 확인해 봤어요? 권한 설정 문제일 거예요." 그렇다면 파일이란 정확히 무엇일까요?

쉽게 비유하자면, 파일은 마치 도서관의 책 한 권과 같습니다. 책에는 제목, 저자, 출판일, 페이지 수 같은 정보가 있듯이, 파일에도 이름, 소유자, 생성일, 크기 같은 메타데이터가 있습니다.

이런 정보들을 우리는 파일의 속성이라고 부릅니다. 파일의 주요 속성을 살펴보겠습니다.

먼저 이름은 사용자가 파일을 식별하기 위한 문자열입니다. 유형은 파일의 종류를 나타내며, 텍스트 파일인지 실행 파일인지 구분해줍니다.

위치는 파일이 저장된 장치와 경로를 가리킵니다. 크기는 현재 파일의 바이트 수를 나타냅니다.

보호 정보는 누가 읽고, 쓰고, 실행할 수 있는지를 결정하는 권한입니다. 바로 이 부분 때문에 김개발 씨가 에러를 만난 것이죠.

시간 정보도 중요한 속성입니다. 생성 시간, 최종 수정 시간, 최종 접근 시간이 기록됩니다.

이 정보는 백업이나 동기화 프로그램에서 매우 유용하게 사용됩니다. 위의 코드를 살펴보겠습니다.

Java의 Files.readAttributes 메서드를 사용하면 파일의 기본 속성을 쉽게 읽을 수 있습니다. BasicFileAttributes 인터페이스는 크기, 생성 시간, 수정 시간, 디렉토리 여부 등을 제공합니다.

실제 현업에서는 파일 속성을 어떻게 활용할까요? 예를 들어 로그 관리 시스템을 개발한다고 가정해봅시다.

수정 시간을 확인해서 오래된 로그를 자동으로 삭제하거나, 파일 크기를 모니터링해서 디스크 용량 부족을 미리 경고할 수 있습니다. 주의할 점도 있습니다.

파일 속성 중 권한 정보는 운영체제마다 다르게 구현됩니다. 리눅스에서는 rwx 형태의 유닉스 권한을 사용하고, 윈도우에서는 ACL을 사용합니다.

크로스 플랫폼 개발 시 이 점을 고려해야 합니다. 다시 김개발 씨 이야기로 돌아가 봅시다.

박시니어 씨와 함께 파일 권한을 확인한 결과, 쓰기 권한이 없었던 것이 문제였습니다. "파일을 다룰 때는 항상 속성부터 확인하는 습관을 들이세요." 박시니어 씨의 조언이었습니다.

실전 팁

💡 - 파일 작업 전 권한 속성을 먼저 확인하는 습관을 들이세요

  • 시간 속성을 활용하면 효율적인 캐싱과 동기화 로직을 구현할 수 있습니다

2. 디렉토리 구조

김개발 씨가 프로젝트 폴더를 정리하다가 한숨을 쉽니다. "파일이 너무 많아서 찾기가 힘들어요." 박시니어 씨가 말합니다.

"디렉토리 구조를 잘 설계하면 수천 개의 파일도 효율적으로 관리할 수 있어요. 운영체제가 어떻게 디렉토리를 구현하는지 알면 더 잘 이해될 거예요."

디렉토리는 파일들을 논리적으로 그룹화하여 관리하는 특수한 파일입니다. 마치 도서관의 분류 체계처럼, 디렉토리는 계층적 구조를 통해 수많은 파일을 체계적으로 정리합니다.

현대 운영체제는 대부분 트리 구조의 디렉토리 시스템을 사용합니다.

다음 코드를 살펴봅시다.

// Java에서 디렉토리 탐색하기
import java.io.File;
import java.nio.file.*;

public class DirectoryTraversal {
    public static void listDirectory(String path, int depth) {
        File dir = new File(path);
        File[] files = dir.listFiles();

        if (files != null) {
            for (File file : files) {
                // 들여쓰기로 계층 구조 표현
                String indent = "  ".repeat(depth);
                String type = file.isDirectory() ? "[DIR]" : "[FILE]";
                System.out.println(indent + type + " " + file.getName());

                // 디렉토리면 재귀 탐색
                if (file.isDirectory()) {
                    listDirectory(file.getPath(), depth + 1);
                }
            }
        }
    }
}

신입 개발자 김개발 씨에게 새로운 과제가 주어졌습니다. 회사의 대용량 파일 서버를 정리하는 일이었습니다.

수만 개의 파일이 뒤섞여 있는 상황을 보며 막막해하던 그에게, 박시니어 씨가 디렉토리 구조의 역사부터 설명해주었습니다. 컴퓨터 초창기에는 모든 파일이 하나의 공간에 모여 있었습니다.

이것을 단일 레벨 디렉토리라고 부릅니다. 마치 모든 책을 한 방에 쌓아놓은 것과 같았죠.

파일이 조금만 많아져도 관리가 불가능했습니다. 그래서 등장한 것이 2단계 디렉토리입니다.

각 사용자마다 별도의 디렉토리를 주는 방식이었습니다. 도서관에 개인 사물함이 생긴 셈이죠.

하지만 이것만으로는 부족했습니다. 사용자 한 명이 관리해야 할 파일도 점점 많아졌으니까요.

현대 운영체제는 트리 구조 디렉토리를 사용합니다. 디렉토리 안에 다시 디렉토리를 만들 수 있는 구조입니다.

마치 대분류, 중분류, 소분류로 책을 정리하는 도서관의 분류 체계와 같습니다. 이 구조 덕분에 우리는 "/home/user/projects/2024/webapp" 같은 경로를 사용할 수 있게 되었습니다.

더 나아가 일부 시스템은 비순환 그래프 구조를 지원합니다. 하나의 파일이나 디렉토리를 여러 곳에서 참조할 수 있게 해주는 것이죠.

리눅스의 심볼릭 링크가 대표적인 예입니다. 위의 코드를 살펴보겠습니다.

Java에서 디렉토리를 탐색할 때는 재귀 호출을 사용하는 것이 자연스럽습니다. 디렉토리 구조 자체가 트리이기 때문입니다.

isDirectory 메서드로 디렉토리인지 확인하고, 디렉토리라면 그 안으로 들어가 다시 탐색합니다. 디렉토리는 내부적으로 어떻게 구현될까요?

디렉토리도 결국 하나의 파일입니다. 다만 일반 파일과 달리, 그 내용이 파일 목록이라는 점이 다릅니다.

각 항목에는 파일 이름과 해당 파일의 메타데이터 위치가 저장되어 있습니다. 실무에서 디렉토리 구조를 설계할 때는 몇 가지 원칙이 있습니다.

첫째, 너무 깊은 중첩은 피하세요. 경로가 길어지면 관리가 어려워집니다.

둘째, 의미 있는 이름을 사용하세요. 셋째, 관련 파일은 같은 디렉토리에 모으세요.

주의할 점도 있습니다. 심볼릭 링크를 잘못 사용하면 무한 루프가 발생할 수 있습니다.

A가 B를 가리키고 B가 다시 A를 가리키는 상황이죠. 파일 탐색 프로그램을 만들 때는 이런 순환 참조를 감지하는 로직이 필요합니다.

김개발 씨는 이제 체계적인 디렉토리 구조를 설계해서 파일 서버를 깔끔하게 정리할 수 있게 되었습니다. "디렉토리를 잘 설계하면 나중에 찾기도 쉽고, 백업도 편해져요." 박시니어 씨의 말에 김개발 씨가 고개를 끄덕였습니다.

실전 팁

💡 - 디렉토리 깊이는 5단계 이내로 유지하는 것이 좋습니다

  • 심볼릭 링크 사용 시 순환 참조에 주의하세요

3. 파일 할당 방법

김개발 씨가 SSD를 새로 구매하며 궁금해졌습니다. "파일이 디스크에 어떻게 저장되는 걸까요?" 박시니어 씨가 설명합니다.

"디스크는 블록 단위로 나뉘어 있어요. 파일을 이 블록들에 어떻게 배치하느냐에 따라 성능이 크게 달라지죠."

파일 할당은 파일 데이터를 디스크의 블록들에 배치하는 방법을 말합니다. 마치 창고에 물건을 배치하는 것처럼, 어떤 전략을 쓰느냐에 따라 공간 효율성과 접근 속도가 달라집니다.

대표적으로 연속, 연결, 인덱스 할당 방식이 있습니다.

다음 코드를 살펴봅시다.

// 세 가지 할당 방식을 개념적으로 표현한 코드
public class FileAllocation {

    // 연속 할당: 시작 블록과 길이만 저장
    class ContiguousAllocation {
        int startBlock;      // 시작 블록 번호
        int blockCount;      // 연속된 블록 수
        // 장점: 순차/직접 접근 모두 빠름
        // 단점: 외부 단편화 발생
    }

    // 연결 할당: 각 블록이 다음 블록을 가리킴
    class LinkedAllocation {
        int firstBlock;      // 첫 번째 블록
        // 각 블록 끝에 다음 블록 포인터 저장
        // 장점: 외부 단편화 없음
        // 단점: 직접 접근 느림, 포인터 공간 필요
    }

    // 인덱스 할당: 별도의 인덱스 블록 사용
    class IndexedAllocation {
        int indexBlock;      // 인덱스 블록 번호
        int[] dataBlocks;    // 실제 데이터 블록 목록
        // 장점: 직접 접근 가능, 외부 단편화 없음
        // 단점: 인덱스 블록 공간 필요
    }
}

김개발 씨는 파일 시스템의 성능 문제를 디버깅하다가 흥미로운 사실을 발견했습니다. 같은 크기의 파일인데 어떤 것은 빠르게 읽히고, 어떤 것은 느리게 읽혔습니다.

박시니어 씨가 그 이유를 설명해주었습니다. 디스크는 블록 단위로 나뉘어 있습니다.

블록은 보통 4KB 정도 크기의 저장 단위입니다. 10MB 파일을 저장하려면 약 2,560개의 블록이 필요한 셈이죠.

문제는 이 블록들을 어떻게 배치하느냐입니다. 첫 번째 방식은 연속 할당입니다.

이름 그대로 파일을 연속된 블록에 저장하는 방식입니다. 마치 도서관에서 시리즈 책을 나란히 꽂아두는 것과 같습니다.

시작 위치와 길이만 알면 되니 관리가 간단하고, 순차 읽기도 매우 빠릅니다. 하지만 연속 할당에는 치명적인 단점이 있습니다.

외부 단편화 문제입니다. 파일을 삭제하면 빈 공간이 생기는데, 이 빈 공간들이 여기저기 흩어져 있으면 큰 파일을 저장할 수 없게 됩니다.

마치 지하철에서 빈 자리가 있어도 떨어져 있으면 일행이 함께 앉을 수 없는 것과 같습니다. 두 번째 방식은 연결 할당입니다.

각 블록이 다음 블록의 위치를 가리키는 포인터를 가지고 있습니다. 마치 보물찾기에서 첫 번째 단서가 두 번째 단서 위치를 알려주는 것처럼요.

블록들이 흩어져 있어도 괜찮으니 외부 단편화 문제가 해결됩니다. 하지만 연결 할당도 문제가 있습니다.

파일 중간 부분을 읽으려면 처음부터 링크를 따라가야 합니다. 100번째 블록을 읽으려면 99개의 블록을 먼저 거쳐야 하는 셈이죠.

직접 접근이 매우 느립니다. 세 번째 방식은 인덱스 할당입니다.

별도의 인덱스 블록에 모든 데이터 블록의 위치를 저장합니다. 책의 목차와 같습니다.

목차만 보면 원하는 챕터가 몇 페이지에 있는지 바로 알 수 있듯이, 인덱스 블록만 보면 원하는 데이터가 어느 블록에 있는지 바로 알 수 있습니다. 인덱스 할당은 연속 할당의 빠른 직접 접근과 연결 할당의 유연성을 모두 갖추고 있습니다.

대신 인덱스 블록을 저장할 추가 공간이 필요합니다. 현대 파일 시스템 대부분이 인덱스 할당을 기반으로 합니다.

위의 코드는 세 가지 할당 방식을 개념적으로 보여줍니다. ContiguousAllocation은 시작 블록과 블록 수만 저장합니다.

LinkedAllocation은 첫 블록만 알면 체인을 따라갈 수 있습니다. IndexedAllocation은 인덱스 블록이 모든 데이터 블록 위치를 담고 있습니다.

김개발 씨가 발견한 성능 차이의 원인이 밝혀졌습니다. 느린 파일은 단편화가 심하게 발생해 있었던 것입니다.

블록들이 디스크 여기저기에 흩어져 있으니 읽는 데 시간이 오래 걸렸던 것이죠.

실전 팁

💡 - 디스크 조각 모음은 파일 블록들을 연속되게 재배치하는 작업입니다

  • SSD는 물리적 이동이 없어 단편화 영향이 적지만, 완전히 무시할 수는 없습니다

4. FAT와 i-node

김개발 씨가 USB 메모리를 포맷하다가 "FAT32"와 "NTFS" 옵션을 보고 궁금해졌습니다. "FAT가 뭐예요?" 박시니어 씨가 미소를 지으며 대답합니다.

"FAT는 파일 할당 테이블의 약자예요. 유닉스 세계에는 i-node라는 비슷하면서도 다른 방식이 있죠."

FATi-node는 파일의 메타데이터와 블록 위치를 관리하는 두 가지 대표적인 방식입니다. FAT는 연결 할당을 개선한 방식으로 윈도우에서 주로 사용하고, i-node는 인덱스 할당을 기반으로 유닉스와 리눅스에서 사용합니다.

다음 코드를 살펴봅시다.

// i-node 구조를 개념적으로 표현
public class INode {
    // 파일 메타데이터
    int fileType;           // 파일 유형 (일반, 디렉토리, 링크)
    int permissions;        // 읽기/쓰기/실행 권한
    int ownerId;            // 소유자 ID
    int groupId;            // 그룹 ID
    long fileSize;          // 파일 크기
    long createdTime;       // 생성 시간
    long modifiedTime;      // 수정 시간
    int linkCount;          // 하드 링크 수

    // 블록 포인터 (인덱스 할당)
    int[] directBlocks = new int[12];      // 직접 블록 (약 48KB)
    int singleIndirect;                     // 1단계 간접 블록
    int doubleIndirect;                     // 2단계 간접 블록
    int tripleIndirect;                     // 3단계 간접 블록

    // 작은 파일: directBlocks만 사용 (빠른 접근)
    // 큰 파일: 간접 블록으로 확장 (유연성)
}

USB 메모리 포맷 화면을 보던 김개발 씨에게 박시니어 씨가 FAT의 역사부터 들려주었습니다. 1970년대 말, 마이크로소프트는 플로피 디스크를 위한 간단한 파일 시스템이 필요했습니다.

그렇게 탄생한 것이 FAT입니다. FAT는 File Allocation Table의 약자입니다.

연결 할당의 단점을 기억하시나요? 블록마다 다음 블록 포인터가 있어서 직접 접근이 느렸습니다.

FAT는 이 포인터들을 모두 꺼내서 별도의 테이블에 모아놓았습니다. 마치 기차 객실마다 다음 객실 번호가 적혀 있는 대신, 역무원이 전체 객실 연결 목록을 가지고 있는 것과 같습니다.

이 테이블을 메모리에 올려두면 직접 접근도 빨라집니다. FAT에는 FAT12, FAT16, FAT32가 있습니다.

숫자는 블록 주소를 표현하는 비트 수입니다. FAT32는 2의 32승, 약 40억 개의 블록을 표현할 수 있습니다.

하지만 FAT는 구조가 단순한 만큼 한계도 있습니다. 단일 파일 크기가 4GB로 제한되고, 권한 관리 기능이 없습니다.

한편 유닉스 세계에서는 다른 접근법을 택했습니다. 바로 i-node입니다.

i-node는 index node의 줄임말로, 각 파일마다 하나씩 할당되는 자료 구조입니다. i-node의 특징은 모든 메타데이터를 한 곳에 모아둔다는 것입니다.

파일 크기, 권한, 소유자, 시간 정보, 그리고 데이터 블록 위치까지 모두 i-node에 담겨 있습니다. 파일 이름은 i-node에 없고, 디렉토리에 저장됩니다.

이름과 i-node 번호를 연결해주는 것이 디렉토리의 역할입니다. 위의 코드를 살펴보겠습니다.

i-node는 12개의 직접 블록 포인터를 가집니다. 블록 하나가 4KB라면 48KB까지는 직접 블록만으로 저장할 수 있습니다.

대부분의 파일이 이 크기 이하이므로 효율적입니다. 더 큰 파일은 어떻게 할까요?

간접 블록을 사용합니다. 1단계 간접 블록은 블록 포인터들의 배열을 가리킵니다.

2단계는 그 배열이 다시 배열을 가리킵니다. 3단계 간접 블록까지 사용하면 테라바이트급 파일도 저장할 수 있습니다.

이런 구조 덕분에 작은 파일은 빠르게, 큰 파일은 유연하게 처리할 수 있습니다. 리눅스의 ext4, 맥OS의 APFS 등 현대 파일 시스템들은 모두 i-node 개념을 기반으로 합니다.

김개발 씨가 물었습니다. "그럼 USB에는 뭘 써야 해요?" 박시니어 씨가 대답합니다.

"호환성이 중요하면 FAT32, 큰 파일을 다루면 NTFS나 exFAT를 쓰세요. 리눅스 전용이라면 ext4가 좋고요."

실전 팁

💡 - 리눅스에서 ls -i 명령으로 파일의 i-node 번호를 확인할 수 있습니다

  • 하드 링크는 같은 i-node를 가리키는 여러 이름을 만드는 것입니다

5. 파일 시스템 마운트

김개발 씨가 새 하드디스크를 서버에 연결했습니다. 그런데 파일 탐색기에서 보이지 않습니다.

"디스크를 연결했는데 왜 안 보이죠?" 박시니어 씨가 설명합니다. "마운트를 해야 해요.

파일 시스템을 디렉토리 트리에 연결하는 작업이죠."

마운트는 저장 장치의 파일 시스템을 운영체제의 디렉토리 구조에 연결하는 작업입니다. 마치 새 책장을 도서관 특정 구역에 배치하는 것처럼, 마운트를 통해 새 저장 장치가 기존 디렉토리 트리의 일부가 됩니다.

다음 코드를 살펴봅시다.

// 리눅스 마운트 명령 예시와 Java에서 마운트 정보 확인
// 터미널: mount /dev/sdb1 /mnt/newdisk

import java.nio.file.*;

public class MountExample {
    public static void main(String[] args) throws Exception {
        // Java에서 파일 시스템 정보 확인
        for (FileStore store : FileSystems.getDefault().getFileStores()) {
            System.out.println("이름: " + store.name());
            System.out.println("유형: " + store.type());
            System.out.println("총 공간: " + store.getTotalSpace() / (1024*1024*1024) + " GB");
            System.out.println("사용 가능: " + store.getUsableSpace() / (1024*1024*1024) + " GB");
            System.out.println("---");
        }

        // 특정 경로의 파일 시스템 확인
        Path path = Paths.get("/mnt/newdisk");
        FileStore fs = Files.getFileStore(path);
        System.out.println(path + "의 파일 시스템: " + fs.type());
    }
}

김개발 씨는 리눅스 서버 관리가 처음이었습니다. 윈도우에서는 하드디스크를 연결하면 D:, E: 드라이브로 자동으로 나타났는데, 리눅스는 달랐습니다.

박시니어 씨가 유닉스 파일 시스템의 철학을 설명해주었습니다. 유닉스 계열 운영체제에서는 모든 것이 파일입니다.

그리고 모든 파일은 하나의 디렉토리 트리 안에 존재합니다. 윈도우처럼 C:, D: 같은 별도의 루트가 없습니다.

루트는 오직 / 하나뿐입니다. 그렇다면 새 하드디스크는 어떻게 사용할까요?

바로 마운트입니다. 마운트는 새 파일 시스템을 기존 디렉토리 트리의 특정 지점에 붙이는 작업입니다.

마치 레고 블록을 기존 구조물에 끼워 넣는 것과 같습니다. 예를 들어 새 하드디스크를 /mnt/newdisk에 마운트하면, 그 디렉토리로 이동했을 때 새 하드디스크의 내용이 보입니다.

마운트 전에 /mnt/newdisk에 있던 파일들은 마운트를 해제할 때까지 가려집니다. 이 개념이 중요한 이유가 있습니다.

마운트 포인트를 통해 다양한 파일 시스템을 통일된 방식으로 접근할 수 있습니다. ext4, NTFS, NFS 네트워크 드라이브, USB 메모리 등 서로 다른 파일 시스템이라도 사용자 입장에서는 그냥 디렉토리처럼 보입니다.

리눅스에서 마운트는 mount 명령을 사용합니다. "mount /dev/sdb1 /mnt/newdisk"는 sdb1 장치를 /mnt/newdisk 디렉토리에 마운트하라는 뜻입니다.

마운트를 해제할 때는 umount 명령을 사용합니다. 위의 코드를 살펴보겠습니다.

Java의 FileStore 클래스를 사용하면 시스템에 마운트된 파일 시스템 정보를 확인할 수 있습니다. 각 파일 시스템의 이름, 유형, 용량 등을 조회할 수 있습니다.

실무에서 마운트는 매우 중요합니다. 서버에 새 스토리지를 추가하거나, NFS로 네트워크 드라이브를 연결하거나, 도커 컨테이너에 볼륨을 연결할 때 모두 마운트 개념을 사용합니다.

부팅 시 자동으로 마운트되게 하려면 /etc/fstab 파일에 등록해야 합니다. 이 파일에는 어떤 장치를 어디에 어떤 옵션으로 마운트할지가 적혀 있습니다.

잘못 설정하면 부팅이 안 될 수도 있으니 주의가 필요합니다. 마운트 해제 시에도 주의해야 합니다.

해당 파일 시스템을 사용 중인 프로세스가 있으면 마운트 해제가 실패합니다. "device is busy" 에러가 나면 어떤 프로세스가 사용 중인지 확인해야 합니다.

김개발 씨가 박시니어 씨의 도움으로 새 하드디스크를 성공적으로 마운트했습니다. "이제 /data 디렉토리로 가면 새 하드디스크가 보여요.

마운트 덕분에 기존 경로 구조를 바꾸지 않고도 저장 공간을 확장할 수 있네요."

실전 팁

💡 - 마운트 해제 전에 lsof 명령으로 사용 중인 프로세스를 확인하세요

  • /etc/fstab 수정 전에는 반드시 백업을 해두세요

6. 저널링 파일 시스템

김개발 씨가 어느 날 서버 전원이 갑자기 나갔다가 복구된 후, 파일이 손상되지 않은 것을 보고 놀랐습니다. "전원이 나갔는데 데이터가 멀쩡해요?" 박시니어 씨가 설명합니다.

"저널링 파일 시스템 덕분이에요. 마치 은행의 거래 기록처럼 모든 변경 사항을 미리 기록해두거든요."

저널링은 파일 시스템의 변경 사항을 실제 적용하기 전에 별도의 영역에 먼저 기록해두는 기술입니다. 마치 회계 장부에 먼저 기록하고 나서 실제 돈을 옮기는 것처럼, 저널링을 통해 시스템 장애 시에도 일관성을 유지할 수 있습니다.

다음 코드를 살펴봅시다.

// 저널링 개념을 의사 코드로 표현
public class JournalingFileSystem {
    private Journal journal;
    private FileSystem fs;

    public void writeFile(String path, byte[] data) throws Exception {
        // 1단계: 저널에 작업 내용 기록 (로그)
        Transaction tx = journal.beginTransaction();
        tx.logOperation("WRITE", path, data.length);
        tx.logAffectedBlocks(calculateBlocks(path, data));
        journal.commit(tx);  // 저널에 먼저 기록

        // 2단계: 실제 파일 시스템에 데이터 쓰기
        fs.writeBlocks(path, data);

        // 3단계: 저널에서 트랜잭션 완료 표시
        journal.markComplete(tx);
    }

    // 시스템 복구 시
    public void recover() {
        for (Transaction tx : journal.getIncompleteTransactions()) {
            if (tx.isFullyLogged()) {
                fs.replay(tx);  // 완전히 기록된 건 재실행
            } else {
                journal.discard(tx);  // 불완전한 건 폐기
            }
        }
    }
}

서버 관리를 시작한 김개발 씨에게 박시니어 씨가 경험담을 들려주었습니다. "예전에 저널링이 없던 시절에는 정전 한 번에 파일 시스템 전체를 검사해야 했어요.

서버가 몇 시간씩 멈춰 있었죠." 저널링이 없는 파일 시스템은 왜 문제가 될까요? 파일 하나를 저장할 때 여러 곳을 수정해야 합니다.

데이터 블록도 써야 하고, i-node도 수정해야 하고, 디렉토리 항목도 추가해야 합니다. 이 작업 중간에 전원이 나가면 어떻게 될까요?

데이터 블록은 썼는데 i-node가 업데이트되지 않았다면, 그 데이터는 고아 블록이 됩니다. 반대로 i-node는 있는데 데이터 블록이 없으면 파일이 손상됩니다.

이런 불일치 상태를 찾아 고치려면 전체 파일 시스템을 스캔해야 합니다. 디스크가 클수록 시간이 오래 걸립니다.

저널링은 이 문제를 해결합니다. 핵심 아이디어는 간단합니다.

실제 작업을 하기 전에, 무엇을 할지 먼저 기록해두는 것입니다. 이 기록을 저널 또는 로그라고 부릅니다.

마치 은행 거래를 생각해보세요. 계좌 이체를 할 때 은행은 먼저 거래 장부에 "A 계좌에서 100만원 출금, B 계좌에 100만원 입금"이라고 기록합니다.

그다음에 실제로 잔액을 변경합니다. 중간에 문제가 생기면 장부를 보고 거래를 완료하거나 취소할 수 있습니다.

저널링 파일 시스템도 마찬가지입니다. 파일을 쓰기 전에 저널에 "이 블록들에 이 데이터를 쓸 예정"이라고 기록합니다.

그다음 실제로 씁니다. 다 쓰면 저널에 "완료"라고 표시합니다.

위의 코드를 살펴보겠습니다. writeFile 메서드는 세 단계로 진행됩니다.

먼저 저널에 작업 내용을 기록하고, 실제 파일 시스템에 쓰고, 마지막으로 저널에 완료 표시를 합니다. recover 메서드는 미완료된 트랜잭션을 찾아 재실행하거나 폐기합니다.

저널링에도 종류가 있습니다. 메타데이터 저널링은 i-node, 디렉토리 같은 메타데이터만 저널에 기록합니다.

빠르지만 데이터 손실 가능성이 있습니다. 풀 저널링은 데이터까지 모두 저널에 기록합니다.

안전하지만 모든 데이터를 두 번 쓰니 느립니다. 대부분의 현대 파일 시스템은 저널링을 지원합니다.

리눅스의 ext4, 윈도우의 NTFS, 맥의 APFS 모두 저널링을 사용합니다. ext4의 기본 설정은 메타데이터 저널링이며, 필요하면 옵션으로 풀 저널링을 활성화할 수 있습니다.

저널링 덕분에 복구 시간이 획기적으로 줄었습니다. 전체 디스크를 스캔할 필요 없이 저널만 확인하면 됩니다.

수 시간이 걸리던 복구가 수 초로 줄어든 것입니다. 김개발 씨가 고개를 끄덕였습니다.

"그래서 서버가 갑자기 꺼졌는데도 금방 복구됐군요." 박시니어 씨가 덧붙였습니다. "저널링이 있어도 중요한 데이터는 정기적으로 백업하는 것이 좋아요.

디스크 자체가 고장나면 저널도 소용없으니까요."

실전 팁

💡 - ext4 파일 시스템의 저널 모드는 mount 옵션의 data=journal, data=ordered, data=writeback으로 설정합니다

  • 저널링은 복구 속도를 높여주지만, 백업을 대체할 수는 없습니다

이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!

#OperatingSystem#FileSystem#FAT#inode#Journaling#Operating System

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.