# 크래시 일관성: FSCK와 저널링

파일 시스템은 전력 손실이나 시스템 크래시가 발생하는 상황에서도 디스크 내용은 보호되어야한다. 이를 `크래시 일관성(crach-consistency)`  이라고한다.

- 과거의 해결책 - fsck(파일 시스템 검사)
- 저널링(journaling), write-ahead-loggin(WAL)
   - 쓰기 오버헤드가 추가되지만 크래시 상황에서 빠르게 복구 가능

### 예제

디스크 상에 여러 개의 자료 구조를 갱신하는 파일 연산

- 워크로드
   - 기존 파일에 블럭은 하나 추가하는 연산
   - 파일을 열고 lseek()로 파일의 끝으로 오프셋을 이동한 후 4KB를 쓰고 파일을 닫는다.

![Image](https://res.craft.do/user/full/3442554f-f1f2-6ecd-471f-e2827e4b5905/doc/D035A198-8EDD-4E76-8A7E-D738A5F192FC/02502CA6-DA25-477F-88D4-A423BAE40B76_2/k33eOityIerdbmuMrmx39ixIgNSsEWO9pSRweyh3xLEz/Image)

#### 그림 설명

- 아이노드 비트맵: 8비트의 아이노드 8개(0~7)
- 데이터 비트맵: 데이터 블럭 당 8비트 8개(0~7)
- 아이노드 비트맵을 보면 3번 비트가 설정되어있다.
- 아이노드 테이블에서 2번 아이노드가 할당되어있다.
- 데이터 비트맵의 4번 비트가 사용 중
- 아이노드는 첫번째 버전이기 때문에 I[v1]으로 표기 되어있다.

#### 아이노드 내부

![Image.png](https://res.craft.do/user/full/3442554f-f1f2-6ecd-471f-e2827e4b5905/doc/D035A198-8EDD-4E76-8A7E-D738A5F192FC/86073DA8-07F9-439B-887D-AB4AC9BFB459_2/R6delyZcKE3oHSuxQ8HJGZUanADPYVm0b0KaN8QInpwz/Image.png)

파일의 크기는 1(한 블럭)이고 첫번째 포인터는 4번 블럭을 가리킨다. 파일의 끝에 데이터를 추가하기 위해서 새로운 데이터 블럭을 추가해야한다. 이를 위해 `3개의 디스크 자료구조를 갱신` 해야한다.

- 아이노드
- 새로운 데이터 블럭
- 데이터 비트맵

#### 업데이트된 아이노드

![Image.png](https://res.craft.do/user/full/3442554f-f1f2-6ecd-471f-e2827e4b5905/doc/D035A198-8EDD-4E76-8A7E-D738A5F192FC/F2D8B5FC-D3B1-4F7A-AD69-3D427EEB7E7A_2/sWyeEShrPSIQ09vDPypghyA9fmcBZXSySxFS1l4mTWEz/Image.png)

![Image.png](https://res.craft.do/user/full/3442554f-f1f2-6ecd-471f-e2827e4b5905/doc/D035A198-8EDD-4E76-8A7E-D738A5F192FC/4C4D3106-FAE1-4ABC-8361-00F566795350_2/OJRXLsUb4g1QRuqiNz4iWz0acLf3ArHOy8jmF5ZkWFMz/Image.png)

데이터 비트맵은 0000 1100으로 갱신되어 5번이 1로 변경되었다. 그리고 새로운 데이터 블럭(Db)가 할당되었다.

기존 파일에 블럭 하나를 추가하기 위해서 파일 시스템은 디스크에 3번의 쓰기를 수행해야한다. 아이노드를 업데이트 해야하고 비트맵맵을 변경해야하며 데이터 블럭을 써야한다. write()를 사용하였을 때 데이터는 바로 디스크에 반영되지 않고 메모리 상에 존재하다가 파일 시스템이 실제로 ::디스크를 실행할 때 기록::된다.

### 크래시 시나리오

한번의 쓰기만 성공한 경우 3가지의 경우가 발생할 수 있다.

1. 데이터 블록만 디스크에 기록됨

   디스크에 데이터는 있지만 데이터를 가리키는 아이노드와 할당 여부를 나타내는 비트맵도 없다. 이러한 경우에는 그냥 없는 데이터 취급하면 되기 때문에 일관성 측면에서는 문제가 없다.

1. 업데이트된 아이노드만 디스크에 기록됨

   아이노드가 디스크의 주소를 가리키고 있지만 새로운 데이터 블럭은 기록되지 않았다. ::댕글링 포인터의 상황::이 발생했다. 잘못된 데이터를 읽게 되면 **일관성 손상** 문제를 발생시킬 수 있다. 파일 시스템 전체의 일관성이 손상되게 된다.

1. 업데이트된 비트맵만 디스크에 기록됨

   비트맵을 할당되었다고 표시하지만 아이노드가 가리키고 있는 블럭이 없다. 사용하지 않는 곳이 할당되었다고 표시되기 때문에 메모리 낭비가 발생한다.

두번의 쓰기만 성공한 경우 3가지 시나리오

1. 데이터를 제외한 아이노드와 비트맵은 기록

   파일 시스템 메타데이터의 일관성은 보장됨. 데이터를 사용하는데 는 문제되지 않지만 가리키는 곳에 쓰레기값이 들어있다.

1. 비트맵을 제외한 아이노드과 데이터는 기록

   아이노드는 제대로 가리키고 있지만 이전 버전의 비트맵과 아이노드 사이에 일관성이 없다.

1. 아이노드를 제외한 비트맵과 데이터는 기록

   아이노드와 비트맵 사이에 내용이 일치하지 않다. 블록이 기록되고 비트맵은 사용중이라고 하지만 아이노드가 파일을 가리키고 있지 않기 때문에 해당 블록이 어느 파일에 속한 것인지 알 수 없다.

#### 크래시 일관성 문제

디스크는 한번에 하나의 쓰기 작업을 처리하기 때문에 중간에 크래시가 발생하더라도 일관성을 유지해야한다.

## 해법1: 파일 시스템 검사기

초기의 파일 시스템은 크래시 일관성을 해결하기 위해서 리부팅 시에 일관성 문제를 해결하는 방식을 사용했다. 이를 fsck라고 한다. fsck는 일관성 불일치를 발견하고 수정하는 도구이다. 하지만 fsck를 사용하면 아이노드가 의미없는 블럭을 가리키지는 일관성에는 문제없는 경우는 해결할 수 없다. fsck는 파일 시스템이 마운트를 하기 직전에 실행되고 종료되면 디스크상의 파일 시스템이 일관성을 가지게된다.

#### fsck 작업

- **슈퍼 블럭**: fsck는 슈퍼블럭 내용에 오류가 없는지 검사. 파일 시스템에 블럭 개수가 파일 시슽메의 크기보다 큰지 검사. 의신되는 슈퍼블럭을 발견하면 사본으로 대체할지 결정.
- **프리 블럭**: 아이노드와 간접 블럭, 이중 간접 블럭을 검사. 파일 시스템에 현재 어떤 블럭들이 할당되었는지 정보 생성. 정확한 할당 비트맵 재구성. 기존의 비트맵과 일치하지 않으면 아이노드 정보를 기반으로 불일치 해결. 필요시 아이노드 비트맵 재구성.
- **아이노드 상태**: 아이노드가 손상되었는지 검사. 해결 불가능한 문제 발견시 초기화.
- **아이노드 링크**: 할당된 아이노드의 링크 개수를 확인. 루트 디렉터리를 시작으로 모든 디렉터리 트리를 탐색하면서 모든 파일과 디렉터리에 대한 링크 갯수를 수집. 수집된 링크 갯수와 아이노드에서 확인한 수와 다르면 개수 필드 수정. 아이노드는 있지만 아무도 참조하지 않으면 lost+found 디렉터리로 이동.
- **중복**: 중복된 포인터가 있는지 검사. 불량 아이노드 발견시 초기화 하거나 참조되는 블럭을 복제하여 각 아이노드가 하나씩 가리키도록 분리.
- **배드 블럭**: 유요하지 않는 공간을 참조하는 포인터 검사. 아이노드 삭제.
- **디렉터리 검사**: 파일의 내용을 파악하는 것은 불가능하기 때문에 파일 시스템에 기록된 디렉터리 데이터를 사용하여 검사 수행. 디렉터리의 각 아이노드가 실제 할당되어있는지 검사.

## 해법2: 저널링(=WAL)

디스크 내용을 갱신할 때 수행하고자하는 작업을 요약해서 기록해둔다. 그래서 "write-ahead(미리 쓰기)" + "log(로그)"라고 한다. 크래시가 발생하면 로그를 확인해서 다시 갱신하면 된다. 크래시가 발생했을 때 디스크 전체를 확인하지 않아도 되는 장점이있다.

### 데이터 저널링

![Image.png](https://res.craft.do/user/full/3442554f-f1f2-6ecd-471f-e2827e4b5905/doc/D035A198-8EDD-4E76-8A7E-D738A5F192FC/D4E4BB1B-5656-4BC8-81D5-D5BB231AC37E_2/H1evyj9aOAKiWY5WlPoST7iX8Jx3iUQiB9aKh1eM4YYz/Image.png)

디스크에 기록하기 전에 저널에 먼저 기록해둔다.

- TxB: 트랜젝션 시작 블럭으로 연산에 대한 정보를 기록한다.
- I[v2],B[v2],Db: 기록할 데이터와 트랜젝션 식별자(TID) → 갱신해야할 물리적 내용을 기록하기 때문에 **물리 로깅** 으로도 불린다.
- TxE: 트랜젝션 종료 블럭 → 트랜젝션 종료 블럭이 저널에 기록되면 ::commit::되었다고 한다.
- ::체크포인팅::: 저널에 기록된 내용을 실제 디스크에 반영

저널에 Db를 기록하는 도중 잘못된 값이 입력되게 되면 프로그램은 해당 값이 잘못된 값인지 판단할 수 없다. 해당 문제를 방지하기 위해 파일 시스템은 트랜젝션을 두 단계로 나누어 기록한다.

1. TxE를 제외한 모든 블럭을 한번의 쓰기 요청으로 저널에 쓴다.
2. TxE 블럭을 쓰고 저널을 최종적이고 안전한 상태로 만든다.

두 단계로 나눠서 트랜젝션을 수행하기 위해서 디스크 쓰기 연산은 원자적으로 실행되어야한다. 디스크는 섹터단위(512Bytes)크기의 쓰게에 원자성을 보장한다. 그래서 두단계로 나눠서 원자성을 보장해준다.

결과적으로 쓰기 과정은 3단계로 분류된다.

1. 저널 쓰기: 트랜젝션의 내용을 저널에 쓴다.(TxE를 제외한 블럭을 쓴다.)
2. 저널 커밋: 트랜젝션 커밋 블럭을 저널에 쓴다.(TxE 블럭을 쓴다.)
3. 체크포인트: 디스크에 쓴다.

이제 저널에 대한 신뢰성을 확보했으니까 원래의 목적인 저널을 사용해서 영속성을 유지하는 법을 알아보자.

### 복구

크래시 상황에서 저널을 사용해서 복구 할 수 있다.

1. 트랙젝션이 저널에 기록되기 전에 크래시 발생 → 아무것도 안하면 됨
2. 저널에 기록 후 체크포인트 완료 전에 클래시 발생 → 재생(replayed, =redo logging)
   1. 시스템이 부팅할 때 로그(저널)을 탐색해서 디스크에 커밋된 트랜젝션이 있는지 확인
   2. 커밋된 트랜젝션의 블럭들을 디스크에 쓴다.

### 일괄적인 로그 기록

이전의 저널링 방식은 디스크에 많은 트래픽을 유발한다.

두 파일을 저장할 경우 동일한 아이노드에 여러번 기록해야하는 상황이 발생할 수 있다. → 파일 시스템은 여러개의 저널 로그를 모아서 한번에 디스크에 커밋하는 방법을 사용.

1. 로깅 해야할 모든 갱신 내용을 트랜젝션 버퍼에 보관. 트랜젝션 버퍼는 파티션 마다 하나씩 있는 전역 자료구조(::저널 버퍼::)이다.
2. 2개의 파일을 생성하는 경우 메모리에 존재하는 아이노드 비트맵, 파일들의 아이노드들, 디렉터리 데이터, 디렉터리 아이노드를 ::“dirty”::로 설정하고 해당 블럭들을 트랜젝션 버퍼 리스트에 추가한다.
3. 트랜젝션 버퍼의 내용은 파일 시스템이 저널을 커밋할 때 디스크에 기록.
4. 파일 시스템은 메모리에 있는 갱신 내용을 정기적으로 저널에 기록. fsync()나 sync() 함수 호출시에 트랜젝션 버퍼 내용 저널에 커밋.

→ 저널에 기록될 블럭들을 버퍼링함으로써 많은 양의 쓰기 트래픽을 줄일 수 있다.

#### 데이터 기록 순서

갱신 내용 메모리에 일정시간동안 버퍼에 유지 → 저널에 트랜젝션 커밋 → 저널에서 디스크로 체크포인트

### 로그 공간 관리

로그의 크기는 한정적이기 때문에 트랜젝션이 계속 추가되었을 때를 대비해야한다.

#### 예상 문제

1. 저널에 블럭이 많기 때문에 복구 시간 오래걸림
2. 저널이 가득차면 더이상 트랜젝션을 커밋할 수 없음.

#### 해결법 - 환형 로그(circular log)

저널이 오버플로우되는 것을 막기 위해서 환형 자료구조를 사용한다. 로그 영역을 끝까지 다 쓰게 되면 앞부터 다시 쓰게된다. 어떤 파일 시스템은 트랜젝션이 체크포인트되면 공간을 재사용할 수 있도록 로그 공간을 비운다. 최신 트랜젝션과 가장 오래된 트랜젝션을 **저널 슈퍼블럭** 에 기록해 놓아서 확인할 수 있다.

#### 저널링 프로토콜

저널 쓰기 → 저널 커밋 → 체크포인트 → 프리(저널 비우기)

> 환형 로그를 쓰면 오버플로우는 막아도 필요한 로그가 없어지는 경우가 생기진 않을까?

### 메타데이터 저널링

위의 방법을 사용하면 저널과 데이터를 두번 써야하기 때문에 디스크 쓰기가 오래걸린다. 이 문제를 ::Ordered Journaling(메타데이터 저널링)::를 사용하여 해결할 수 있다. 저널에 데이터 블럭을 기록하지 않는다.

![Image.png](https://res.craft.do/user/full/3442554f-f1f2-6ecd-471f-e2827e4b5905/doc/D035A198-8EDD-4E76-8A7E-D738A5F192FC/1B29B8DE-689E-4A29-A704-6C9C2889E5F9_2/s7RSBZPCfaomJtTYJ2hvyRlbxw4UM529oQJxWUPFCZMz/Image.png)

메타데이터(I[v2], B[v2])를 저널에 기록하기 전에 먼저 데이터(Db)를 디스크에 쓴다. 그 뒤에 저널을 기록하게되면 데이터만 기록된 경우는 메타데이터 없어 다시 쓰면되고 메타데이터가 기록되지 않은 경우에는 저널을 체크포인트하면 된다.

Ordered를 사용하지 않는 Unordered Journaling은 아이노드가 쓰레기 데이터 블럭을 가리킬 수 있다.

#### 메타데이터 저널링 프로토콜

데이터 쓰기 → 저널 메타데이터 쓰기 → 저널 커밋 → 체크포인트 메타데이터 → 프리

### ext3의 흑막 Stephen Tweedie

tweedie는 삭제 기능을 제일 신경써야한다고 한다. 1000번 주소에 디렉터리 메타데이터를 가지고 있는 foo 디렉터리가 있다고 하자. foo디렉터리에 파일을 추가하면 1000번 주소에 메타데이터를 수정해야한다. 그 다음 디렉터리를 삭제하고 해제가된 1000번 주소에 새로운 파일을 쓰는 상황을 가정해보자. 위 상황에서 저널의 상황은 그림이랑 같다.

![Image.png](https://res.craft.do/user/full/3442554f-f1f2-6ecd-471f-e2827e4b5905/doc/D035A198-8EDD-4E76-8A7E-D738A5F192FC/781AA5A4-E3E5-4C39-9D02-68A246B0B8DD_2/Xt9IpPWeAQLXOt7pljcmcn4tAnzPqZvc4K7rfIyDTBgz/Image.png)

위 상황에서 크래시가 발생하여 복구가 된다면 1000번 주소에는 foo 디렉터리의 메타데이터가 기록되어 foobar 파일을 읽으면 이상한 값이 읽기헤 된다.

이 상황을 방지하기 위해서 지워진 블럭이 체크포인트 될때까지 절대로 재사용하지 않는 방법이있다. ext3은 저널에 철회 레코드(revoke record)라는 새로운 항목을 추가하여 해당 기능을 구현하였다. 저널을 재실행시에 시스템은 먼저 철회 레코드를 확인하고 철회된 내용은 재실행하지 않는 식으로 해결한다.

