파일시스템 5편 - 파일시스템 디자인-2

이 글은 지난번 파일시스템 디자인-1 에 이은 글입니다.

파일시스템 계층화

파일시스템은 계층화가 잘되어있다. 일반적으로 파일시스템은 여러 개의 layer로 나뉘어져 구성된다.

파일시스템 계층화

위의 그림에서 각 layer를 간단하게 살펴보자.

Logical file system

여기서는 파일시스템의 메타데이터를 관리한다.

File-organization module

이 layer에서는 파일의 logical block 주소를 physical block 주소로 변환해준다. 예를들어 하드디스크를 매체로 사용하게되면 sector 주소로 접근해야 하는데 이를 위한 변환을 진행한다.

Basic file system

여기서는 위에서 변환된 physical block 주소를 가지고 읽고 쓰도록 command를 날린다. 여기서 DMA를 사용한다.

I/O control

이는 device driver다. device driver가 하드웨어에 맞게 명령을 전달한다.


Virtual File System

파일시스템은 어쩔 수 없이 사용하는 매체에 의존성이 있다. 그래서 파일시스템은 종류가 여러가지가 있을 수 있다. 예를들어 요즘은 잘 안쓰지만 CD-ROM이 있을 수 있고 USB, SSD, Disk 그리고 파일이 현재 호스트가 아닌 다른 네트워크의 호스트에 있을 수도 있다. Linux kernel 에서는 여러가지 파일시스템을 지원한다.
VFS는 다양한 logical file system 들을 추상화한다. 따라서 VFS를 통해 실제로 시스템에는 여러개의 다른 파일시스템이 사용되더라도 마치 1개의 파일시스템만 사용하는 것처럼 프로프래밍 할 수 있다. 이는 OOP의 개념과 같은데 특정 파일에 대해 read/write syscall 이 호출되면 해당 파일이 속한 파일시스템 구현체의 read/write 가 호출되는 방식이다.

VFS


File System data structure

파일시스템에서 사용하는 자료구조를 알아보면서 파일시스템에 대한 이해를 높여보자.
On-disk 그리고 In-memory 에서 사용하는 자료구조들을 볼 것이다.

On-disk data structure

On-disk 자료구조들은 이미 이전 포스트인 파일시스템 디자인-1 에서 vsfs(very simple file system)에서 살펴봤던 내용들이 많다.

  • Boot block
    첫번째 block으로 운영체제가 booting 하기위해 필요한 정보를 담아놓는 block이다. 하지만 이를 안만드는 경우도 많다.
  • Super block
    파일시스템 관련 정보들이 어디에 저장되어있는지에 대한 metadata를 저장한다. 예를들어 inode table은 어디서 시작인지, data block은 어디 block부터 시작하는지, root directory에 대한 inode 번호 등을 저장한다. Super block은 보통은 각 disk partition의 첫번째 block 에 할당하고 이 super block 이 손상되면 파일시스템의 복구가 힘들기때문에 보통 중복해서 super block을 저장한다. 그리고 이 super block은 in-memory data structure로 메모리에 올려 캐싱한다.
  • File control block
    FCB(file control block)은 결국 inode와 같다. inode 자체도 disk에 저장이 필요하다.

다음의 disk structure layout를 살펴보자.

On-disk data structure

여기서 각 inode table을 여러개로 나누어 저장한 이유는 data block과 inode가 가까우면 성능을 높일 수 있기때문에 조금씩 inode 들을 나누어 설계했다. 다만 이런 내용들은 전적으로 파일시스템 구현에 달라진다.
결국 여기서의 그림을 보면 파일 접근을 위해서 super block, inode, data block 에 대한 접근으로 파일 하나의 접근에 대해 3번의 disk access가 필요하다.


In-memory data structure

보통 파일시스템에서의 in-memory data structure 들은 거의 caching을 통한 성능향상 그리고 파일시스템 관리가 목표이다.

  • Dentry
    이는 directory cache 이다. directory 접근을 위해 on-disk 의 super block 그리고 root directory 의 inode 부터 접근해서 dentry 라는 directory cache를 만든다. dentry로 파일접근시 해당 파일의 inode를 알기위해 여러번 disk 에 접근할 필요가 없이 memory 에서 처리할 수 있다.
  • open file table
    이는 예전에 살펴본 내용으로 system-wide open file table 그리고 per process open file table 이 있는데 각각 open 한 file을 관리한다. per-process open file table 에서 각 index 번호가 file descriptor 가 된다.
  • Buffer cache
    최근에 사용한 data block 을 memory 에 캐싱한다.

Buffer Cache

Buffer cache는 조금 더 자세히 살펴보겠다.
Buffer cache는 파일의 메타데이터가 아닌 data block 자체를 메모리에 올려둔다. 보통 같은 data block을 다시 접근해야 하는 경우가 많기때문에 이를 캐싱해두면 다시 disk를 조회하지 않아도된다.
다만 buffer cache는 완전한 software로 구현된다. 이 말은 virtual memory의 주소변환을 위해 TLB 같은 하드웨어를 도입하는게 아닌 순수한 software 레벨에서 구현한 캐시라는 의미이다.
Buffer cache는 보통 물리메모리의 1 ~ 10% 정도로 할당하고 이는 kernel parameter로 수정이 가능하다. Buffer cache 도 공간이 한정되므로 교체알고리즘을 사용한다. 보통 disk access 에는 locality가 나타나기 때문에 LRU 방식을 사용한다.
다만 DBMS 나 multimedia application은 LRU 로 이득을 볼 수 없는 경우가 대부분이기 때문에 이들은 buffer cache를 거치지 않고 바로 disk 에 접근할 수 있는 flag를 사용해서 buffer cache를 거치지 않도록 프로그래밍 한다.

Read syscall 과 Write syscall 각각의 동작방식을 buffer cache 의 관점에서 살펴보자.

Read syscall

Buffer cache read

먼저 read(fd, buf, size) syscall 을 호출한다. read syscall 의 두번째 인자인 buf 는 사용자의 buffer를 의미한다.
그다음 VFS에서는 file descriptor를 보고 file이 있는 device를 알아낸 후 logical block number 를 physical block number 로 변환한다. buffer cache 쪽에 해당 block이 있는지 확인을 하고 block 이 없다면 파일시스템에서 가져와야한다. 만약 buffer cache에 이미 cache된 block이 있으면 VFS에 반환한다.
cache된 block이 없다면 파일시스템에 block을 요청하고 이 read 를 요청한 프로세스는 sleep 한다. 즉, context switching 이 일어난다. I/O가 필요하기 때문이다. 나중에 disk에서 block을 읽어오면 interrupt 가 발생하고 그 block 을 다시 caching 해주고 block 을 반환한다.
VFS에 반환할때는 application의 buf에 block들을 copy해주고 읽어온 byte 수를 반환한다.

만약 read 를 하더라도 buffer cache 에 대상 block 이 존재하여 cache hit 이 된다면, 해당 프로세스는 sleep 하지 않는다.
그리고 buffer cache 쪽을 보게되면 hash table 구조로 구현되어 있고 value로는 linked list 로 각 cache 되어있는 data block 들이 있는 것을 확인할 수 있다. hash 의 key로는 보통은 block number 를 사용한다고 한다.


Write syscall

Buffer cache write

먼저 write(fd, buf, size) syscall 을 호출한다.
VFS에서 device 및 block number로 변환하여 buffer cache에 해당 block이 이미 있는지 확인한다. 만약 write 하려는 block 이 buffer cache에 없다면 먼저 파일시스템에서 읽어오도록 요청한다. 이 과정에서도 write를 요청한 프로세스는 sleep 한다.
파일시스템에서 읽어왔으면 다시 write를 시도하면 해당 block이 buffer cache 에 존재하므로 이 경우에는 해당 buffer cache의 data block에 직접 write를 수행하고(in-memory) 해당 block이 disk에 있는 block과 일치하지 않는다는 표시를 하기위해 dirty bit를 체크한다.
만약 처음부터 buffer cache에 write 시도시 해당 block 이 buffer cache에 있었다면 바로 그 block에 write 하고 반환한다.

이런 방식이면 buffer cache와 disk 의 sync가 깨지게 되는데 이는 다른 worker 스레드가 주기적으로 dirty bit가 체크된 data block 들을 disk에 sync 시켜준다.

kworker

이 kworker 라는 kernel thread가 buffer cache와 disk block 간의 동기화를 수행한다.
예를들어 data block은 30초, metadata는 5초마다 동기화를 시킬수도 있다. 다만 이런 구현은 실제 OS 구현마다 다르며 파일시스템 구현마다도 다르다.
다만 이런 방식이면 순간적으로 머신이 꺼지거나 할때 동기화가 되지 않은 부분은 유실될 수 있다. 즉 write를 하더라도 그 내용이 disk에 반영된다는 보장이 없다. 이를 위해 저널링 파일시스템 같은 대안을 사용한다.
Application 에서 fsync system call을 사용하면 강제적으로 buffer cache의 내용을 disk에 반영한다.
DBMS도 구현마다 다르지만 매번의 update 마다 fsync 를 수행하지는 않는다. DBMS에서는 이를 write ahead log 과 transaction을 이용해 해결한다.


Memory-Mapped File(mmap)

mmap은 파일을 프로세스의 address space 한 부분으로 mapping 한다. 파일을 프로세스의 memory space 에 그냥 가져다가 붙힌다고 생각하면 쉽다.
이렇게 되면 단순히 memory access instruction 을 통해 파일에 대한 read, write을 할 수 있다. kernel은 이런 memory access instruction 을 적절하게 read, write로 변환하여 수행해준다.
mmap 함수는 다음과 같다.

void* mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset)

start는 단지 이 주소를 사용했으면 좋겠다는 의미로 0을 보통 넣는다. 그리고 offset 부터의 length 만큼의 바이트를 start 주소로 매핑하기를 원한다는 의미이다.
fd는 파일에 대한 file descriptor 로 이미 존재하는 파일에 대한 file descriptor 를 넘겨 이를 읽거나 수정할 수도 있고, 새로운 파일을 O_CREAT flag 와 함께 open 하여 이 file descriptor 를 넘겨 새로운 파일을 쓸 수도 있다.

유저는 파일에 대한 I/O 를 단지 memset, memcpy 같은 memory access 로 단순화 할 수 있다.
그리고 여러개의 프로세스에서 동일한 파일을 open 하여 사용할 경우 kernel 은 동일한 파일에 대한 내용을 memory에 1개만 들고있으면 된다.

mmap 파일의 동작을 그림으로 보자.

mmap 과정

먼저 mmap 을 사용하지 않는 경우를 생각해보면, disk 에서 DMA로 data block을 가져오면 이를 buffer cache에 올리고 그다음 사용자 buffer 로 copy를 해야한다.
buffer cache 로는 DMA 가 copy 해주지만, buffer cache 에서 user buffer 로는 CPU가 직접 copy 해야한다. 따라서 file IO 가 빨라지려면 CPU도 중요하다.

mmap은 조금 다르다. mmap은 buffer cache를 사용하지 않는다. mmap은 buffer cache에 data block을 쓰지 않고 바로 kernel space로 DMA가 쓴다. 그리고 이를 프로세스의 address space 에 page table을 통해 매핑한다.
따라서 user buffer 로의 copy가 존재하지 않는다.
위의 그림에서는 process A 와 process B 가 각각 mmap 되어있는 physical fragment 를 공유하고 있다. mmap 에서는 flag에 MAP_SHARED flag 를 통해 다른 프로세스와 mmap 된 파일을 공유할 수 있다. 하지만 다른 동기화 같은 장치는 제공하지 않는다.
process A 에서 먼저 특정 파일을 mmap 하였을때 그 다음 process B 에서 같은 파일에 대해 mmap을 하게되면 서로 반환받는 virtual address 주소는 다르지만, 결국 page table 에 매핑된 physical address 주소는 같아 같은 파일을 바라보게 된다.

파일시스템 4편 - 파일시스템 디자인-1

이 글은 학부 System Programming 수업을 듣고 파일시스템 관련내용을 정리한 글입니다.

이번 편에서는 크게 다음 2가지를 볼 것이다.

  1. File System의 디자인
  2. Directory의 디자인

unix 시스템에서 파일과 디렉토리는 비슷하면서도 다르게 구현이된다.
파일의 디자인부터 살펴보자.

자세히 보기

파일시스템 3편 - 파일시스템이란?

이 글은 학부 System Programming 수업을 듣고 파일시스템 관련내용을 정리한 글입니다.

파일(File)이란?

파일은 무엇일까?
파일은 linear array of bytes 이다. 이게 무슨말일까?
이전 운영체제 메모리편에서 보았듯이 address space는 sparse 하다. 즉 프로세스가 사용하는 주소공간은 중간부분을 사용하지 않고 비어있을 수 있다. 그러므로 효율적으로 사용하고자 multi-level page table을 사용하곤 했었다.
이와 다르게 파일은 linear array of bytes로 중간이 비어있지 않다. 그리고 파일은 byte addressable하다. Byte 단위로 접근이 가능하다는 것이다. 그리고 여기서의 linear array는 밑에서 보겠지만 logical한 block이 linear하다는 것이다.

자세히 보기

파일시스템 2편 - Flash 그리고 SSD

이 글은 학부 System Programming 수업을 듣고 다른 자료들과 함께 공부한 내용을 정리한 글입니다.
이전 포스트의 하드디스크에 이어 이번 포스트에서는 Flash 그리고 SSD에 대해 알아볼 것입니다.

이번 편은 이전의 운영체제 3편 - 컴퓨터 구조와 I/O 글의 I/O 부분을 이해하고보면 도움이 됩니다.


Flash Memory

하드디스크와 다르게 Flash는 메모리나 CPU처럼 트랜지스터 들로 구성이 되어있다. 즉 disk의 arm이 움직이거나 platter가 회전하는 물리적인 움직임이 없다.
Reliability 측면에서도 disk에 비해 훌륭한 편이다. Disk는 물리적인 head crash가 날수도 있고, dead block(더이상 사용할 수 없는 block) 자체가 생길 수 있다. 물리적으로 읽고 쓰기 때문이다. 그리고 disk는 생각보다 자주깨진다.
이와는 다르게 Flash는 순수한 silicon으로 구성되어 있으며 전자적으로 동작하기에 더 신뢰성이 높다. Flash는 또한 매우 빠른 access time을 제공하고 power가 적게드는 반도체 특성으로 disk에 비해 매우매우 저전력이다. Flash Memory는 여러타입이 있는데 보통은 NAND-Flash를 의미한다. 다만 Flash가 가진 특이한 특성들 때문에 이를 해결하기 위해 몇가지 기법들을 적용해야한다. 이들은 밑에서 자세히 알아볼 것이다.

Flash chip은 하나의 transistor의 1개 이상의 bit를 저장할 수 있다. SLC(Single-Level Cell) flash는 오직 1개의 bit만 transistor에 저장할 수 있고, MLC(Multi-Level Cell) flash는 2개의 bit를 저장할 수 있다. 그러므로 00, 01, 10, 11을 저장할 수 있다. TLC(Triple-Level Cell) flash는 3개의 bit를 저장가능하다. 전반적으로 SLC가 더 성능이 좋고 가격이 비싸다.

자세히 보기

파일시스템 1편 - 하드디스크

이 글은 학부 System Programming 수업을 듣고 다른 자료들과 함께 공부한 내용을 정리한 글입니다.

처음으로 파일시스템 자체를 보기전에 물리적인 device의 작동원리에 대해 이해를 하고 파일시스템을 보게되면 더 이해가 쉽습니다. 이번 편에서는 하드디스크에 대해 다룰 것입니다.

이번 편은 이전의 운영체제 3편 - 컴퓨터 구조와 I/O 글의 I/O 부분을 이해하고보면 도움이 됩니다.

자세히 보기

파일시스템 2편 - RAID

이 글은 학부 System Programming 수업을 듣고 다른 자료들과 함께 공부한 내용을 정리한 글입니다.
이전 포스트의 하드디스크에 이어 이번 포스트에서는 RAID에 대해 알아볼 것입니다.

이번 편은 이전의 운영체제 3편 - 컴퓨터 구조와 I/O 글의 I/O 부분을 이해하고보면 도움이 됩니다.


What is RAID?

RAIDRedundant Array of Inexpensive Disks의 약자이다. 즉 여러개의 disk를 사용해서 더 빠르고, 더 크고, 더 신뢰성 있는 disk 시스템을 구축하는 방식이다.
외부적으로 보는 관점에서는 RAID는 그냥 disk와 동일하다. 외부에서는 그저 큰 disk처럼 바라볼 뿐이다. disk와 동일하게 block의 묶음을 읽고 쓸수있다. 하지만 RAID 내부적으로는 시스템을 관리하는 프로세서도 존재하고 메모리도 존재하며 무엇보다 여러개의 disk로 구성되어 있다.

RAID는 왜 쓰는 것일까?
RAID를 사용하면 먼저 성능상으로 이득을 볼 수 있다. 여러개의 disk에 병렬로 I/O를 할 수 있다. 그리고 용량(capacity)측면에서도 이득을 볼 수 있다. 한개의 disk 크기를 넘어서는 크기를 저장할 수 있기 때문이다.
그리고 신뢰성(reliability)도 더 높은데 RAID를 구성하는 방식에 따라 다를 수 있지만 데이터를 여러 disk에 중복해서 저장함으로서 single disk failure에 대해 극복할 수 있고 외부에서는 single disk failure가 일어나도 아무 일도 일어나지 않은 것처럼 수행할 수 있다.


RAID Internals

파일시스템 밑에서 RAID는 그저 매우 크고 신뢰성있고 빠른 disk 처럼 보일뿐이다. single disk처럼 linear array of blocks로 보이고 각 block은 파일시스템에 의해 읽고 쓰여질 수 있다.
파일시스템이 RAID에 logical block에 대해 I/O 요청을 하면 RAID는 내부적으로 여러 disk를 가지고 있는데 어떤 disk에 접근해서 physical I/O를 할 지 결정하고, 수행한 후 그 결과를 반환한다. 이 physical I/O를 어떻게 수행할 것인가는 밑에서 볼 RAID level에 따라 다르다.
간단하게 RAID가 각 disk의 copy본을 들고있는 mirrored RAID 라고 해보자. 그러면 write을 수행할 때 각 block들을 disk에 2번씩 써주어야 한다.


RAID Evaluation

RAID를 구성하는 방법은 여러가지가 있다. 구성하는 방법에 따라 특징 그리고 장단점이 존재하는데 이런 특징을 수치로 측정할 수 있으로면 비교하기 쉽다. 그래서 우리는 3가지 측면에서 RAID의 특징을 측정할 것이다.

capacity

먼저 capacity이다. B개의 block을 저장할 수 있는 N개의 disk가 있다. 클라이언트는 RAID에 얼마나 많은 용량을 저장할 수 있을까?
중복없이 저장한다면 N * B가 되겠다. 각 block마다 copy본을 둔다면 N * B / 2가 되겠다.

reliability

두번째는 reliability 즉 신뢰성이다. RAID가 몇개의 disk failure 까지 허용할 수 있을까에 대해 이야기한다.

performance

마지막은 performance이다. Performance는 측정하기 조금 힘들 수 있는데 RAID에 요청되는 작업의 종류에 의존하는 경우가 많기 때문이다.
RAID performance를 측정할때는 크게 2가지 측면을 고려할 것인데 첫번째는 single-request latency이다. RAID에서 single I/O 요청에 대해 어떤 latency를 가지는지를 측명하면 얼마나 해당 RAID가 병렬성을 가지고 있는지 그대로 이해할 수 있다.
두번째는 steady-state throughput 이다. 어떤 규칙적인 요청이 왔을때 그때의 처리량을 의미한다. 예를들어 여러개의 요청이 동시에 왔을때 그때의 RAID 전체의 bandwidth를 측정할 수 있겠다.
이들을 조금 더 자세히 측정하기 위해 각 요청에 대한 내용들이 두가지 종류가 있다고 가정한다. sequentialrandom이다.
sequential workload는 요청들이 큰 단위의 연속된 block을 읽는 요청들로 이루어져 있다고 가정한다. 이런 sequential workload는 큰 파일을 읽어 특정 키워드를 찾고싶을때처럼 자주 오는 요청들이다.
random workload는 각 요청은 매우 작은 크기의 block을 읽는 요청들이고 각 요청들은 서로 다른 disk location을 대상으로 한다. 예를들어 처음 4KB에 대해 logical address 10에 접근하고 그다음은 logical address 55,000 그 다음은 logical address 4,500 에 접근하는 방식이다. 이런 random workload는 database에서 빈번하게 일어난다.

위의 sequential, random workload는 disk의 특성으로 인해 각각 다른 performance 특징을 가진다.
sequential access에서는 disk는 가장 효율적인 방식으로 동작한다. seek time과 rotational delay에 매우 적은 시간을 사용하므로 대부분의 시간을 data transfer에 활용할 수 있다.
다만 random access의 경우는 대부분의 시간을 seek time과 rotational delay에 사용하므로 상대적으로 data transfer에는 작은 시간을 할애할 수밖에 없다.
그래서 이런 차이점을 더 명확히 확인하기 위해 sequential workload 에서는 S MB/s 로 data transfer가 가능하다고 하고, random workload 에서는 R MB/s 속도로 data transfer가 가능하다고 하자. 보통은 S가 R보다 훨씬 크다. 이 측정치를 밑에서 RAID level 별로 계산해보며 성능을 비교해볼 것이다.

밑에서는 몇가지 RAID 종류들을 보게될 것이다. RAID Level 0(striping), RAID Level 1(leveling), RAID Levels 4, 5(parity-based redundancy)이다.


RAID Level 0: Striping

RAID 0은 striping으로 더 잘 알려져 있다. 이 방식은 capacity와 performance 측면에서 높은 결과를 낸다.
striping은 말그대로 줄무늬 방식이라고 이해해도 좋다. striping 방식에서는 각 block들을 여러 disk에 걸쳐 줄무늬처럼 배열한다. 다음 그림을 보자.

RAID 0: Striping

여기서는 4개의 disk를 사용했다. striping의 기본 아이디어는 block의 array를 라운드로빈 방식으로 disk에 하나씩 할당한다. 이 방식은 large sequential read 요청에 대해 병렬로 처리할 수 있도록 설계한 방식이다.
위의 예에서는 1개의 block(4KB) 기준으로 라운드로빈으로 disk에 할당하였는데 다음과 같이 할당할수도 있다.

RAID 0: Bigger chunk size

여기서는 다음 disk로 넘어가기 전에 2개의 block(8KB)를 할당하고 다음 disk로 넘어갔다. 이 단위를 chunk size라고 한다. 여기서는 8KB의 chunk size를 사용하였다.

Chunk Size

chunk size는 performance에 영향을 많이 끼친다. 작은 chunk size를 사용한다면 많은 파일들이 여러 disk에 striped 되어 저장될 것이다. 그러므로 파일 read write 시에 병렬성을 증가시킬 수 있을것이다.
다만 block에 접근하기 위한 positioning time(seek time + rotational delay)가 증가한다. 왜냐하면 여러 disk에 병렬로 읽거나 쓰게될때 결국 완료시간은 가장 오랜시간이 걸린 positioning time에 의해 결정되기 때문이다.

chunk size를 크게잡으면 어떨까?
작은 크기의 파일에 대해선은 read, write에 병렬성은 떨어질 것이다. 그러나 positioning time이 줄어들 것이다. 만약 작은 크기의 파일의 크기가 chunk size보다 작아 single disk에 저장된다면 그 single disk 에서의 positioning time이 결정한다.

그러므로 최적의 chunk size를 찾기 위해서는 어떤 요청들을 위주로 처리할지에 대한 지식이 먼저 있으면 결정하기 좋다.
대부분은 큰 chunk size(64KB)를 사용하고는 한다.

RAID 0 Evaluation

RAID 0 에서 capacity, reliability, performance를 측정해보자.
먼저 capacity는 간단하다. B개의 block들로 이루어진 N개의 disk가 있다면 N * B의 block을 저장할 수 있다.
reliability도 간단한데 striping 방식에서는 reliability가 좋지 않다. 하나의 disk가 fail이 나더라도 바로 data loss로 이어진다.

performance는 위에서 본 sequential workload의 S와 random workload의 R을 구해보며 비교해보자.
sequential transfer size는 평균적으로 10MB, random transfer size는 평균적으로 10KB라고 가정하자. 이들을 전송하는데 속도가 얼마인지 계산해보자.
Disk의 spec은 다음과 같다.

  • Average seek time: 7ms
  • Average rotational delay: 3ms
  • Transfer rate of disk: 50MB/s

S(Amound of Data) / (Time to access) 이므로 10MB / (7ms + 3ms + 200ms) = 47.62 MB/s 와 같다. Time to access는 seek time + rotational delay와 10MB를 전송하는데 200ms가 걸리므로 이를 합치면 계산할 수 있다.
R10KB / 10.195ms = 0.981 MB/s이다. 10KB를 전송하는데 0.195ms가 걸린다.
SR은 disk 1개에서 고려한 속도이다.

이처럼 striping의 performace를 보게되면 single-block request에 대해서는 single disk 성능과 동일하다.
하지만 sequential, random workload 관점에서 볼때 sequential workload는 하나의 disk가 S의 속도를 낼때 N개의 disk가 있다면 전체 처리량은 S * N이 되겠다.
randon workload 에서의 전체 처리량은 R * N이 된다.


RAID Level 1: Mirroring

RAID Level 1은 mirroring으로 잘 알려져있다. Mirrored System에서는 각 block에 대해 copy본을 같이 저장함으로서 disk failure를 극복할 수 있다. 전형적인 mirroring 방식은 다음과 같다.

RAID 1: Mirroring

위의 방식에서는 RAID는 물리적으로 두개의 물리적 copy를 저장한다. disk 0은 disk 1과 동일한 내용을 들고있고 disk 2는 disk 3과 동일한 내용을 들고있다. 데이터들은 이 mirror pair들에 걸쳐 striped 된다.
disk들에 copy본들을 어디에 위치시킬지는 여러가지 방법이 있는데 위에서 본 방식은 가장 일반적인 방식으로 이런 방식을 RAID-10이라고 부르기도 한다. stripe of mirror로 RAID1+0 라는 의미이다.

disk read를 할때는 복제본 disk 두개중 어디에서 읽어도 상관없다. 다만 write은 두개의 disk에 모두 써주어야 한다. 이 2번의 write은 병렬로 처리할 수 있다.
또 위의 경우 복제본을 2개 저장했는데 이를 mirroring level이라고도 한다. 여기서는 mirroring level이 2이다.

RAID 1 Evaluation

RAID 1 에서 capacity를 먼저보자. capacity 관점에서 RAID 1은 비용이 비싸다. mirroring level 2 에서는 전체 용량의 절반만 저장할 수 있으므로 capacity는 N * B / 2가 되겠다.

reliability의 관점에서는 좋다. 아무 disk 1개의 failure를 허용할 수 있다. 사실 정확히는 최대 N/2개의 disk failure 까지 허용가능하다. 위에서 본 예제에서도 만약 운이 좋게도 disk 0과 disk 2가 동시에 fail 했다고 하더라도 data loss가 발생하지 않는다. 운이 좋게 각 copy 본을 저장하는 disk가 중복없이 fail이 발생했기 때문이다.

performance 관점을 살펴보자. single read request는 single disk와 성능이 동일하다.
다만 single write request는 살짝 더 latency가 늘어날 수 있는데, 각 copy 본을 병렬로 write한다고 하더라도 완료되는 시점은 두개의 disk중 더 오래걸린 시간이기 때문이다.

steady-state throughput을 살펴보자. sequential write에서는 각 logical write은 두개의 physical write으로 나누어진다. 그러므로 mirroring 방식에서 전체 bandwidth는 (N / 2) * S가 된다. 최대 bandwidth의 절반밖에 안된다.
sequential read도 똑같은데 얼핏 생각하면 각 logical read를 모든 disk에 나누어 처리하면 상황이 더 나아질 것 같지만 disk의 물리적 특성을 생각하면 그렇지 않다. 예를들어 위의 그림에서 block 0 부터 7까지 읽는다고 했을때 block 0은 disk 0에서, block 1은 disk 2에서, block 2는 disk 1에서 block 3은 disk 3에서 읽는다고 해보자. disk 0에서는 block 0을 읽고 그다음 block 4를 읽으면 될 것 같지만 어차피 중간에 block 2가 존재하기 때문에 rotation 하면서 이를 지나가야한다. 그러므로 성능향상이 없다. 그러므로 sequential read 에서의 전체 bandwidth도 N / 2) * S와 같다.

Random read는 mirroring 방식에서 best case이다. read를 전체 disk에 분산시킬 수 있으므로 N * R MB/s의 대역폭을 가진다.
Random write은 sequential write과 동일하게 두개의 physical write로 나누어지므로 (N / 2) * R MB/s 이다.


RAID Level 5: Rotating Parity

RAID Level 5에서는 parity 정보를 활용한다.
Parity bit은 오류가 생겼는지 검사하는 bit 인데 2가지 종류가 존재한다. 짝수 parity와 홀수 parity이다.
개념은 간단한데 짝수 parity에서는 데이터의 각 bit의 값에서 parity bit를 포함한 1의 개수가 짝수가 되도록 하는 것이다. 홀수 parity는 홀수가 되도록 하는것이다. 예를들어 다음과 같다.

짝수 parity라면 C0, C1, C2, C3의 bit에서 1의 개수가 2개이므로 parity bit을 0으로 둔다. 그래야 짝수인 2개로 유지되기 때문이다. parity bit을 활용하면 C0, C1, C2, C3 중 한개가 유실되어도 그 값을 bit 계산으로 알아낼 수 있다.

다시 RAID로 돌아와서 RAID Level 5 방식을 그림으로 보자.

RAID 5: Rotating Parity

여기서는 parity bit을 disk 별로 돌아가며 설정한다. 즉 위의 block 0, 1, 2, 3의 각 block의 bit를 계산해서 그에 대한 parity bit을 disk 4에 저장한다.

RAID 5 Evalutation

capacity 부터 확인해보자. stripe 당 1개의 parity block을 두기때문에 (N - 1) * B의 용량을 가지게 된다.
reliability 는 1개의 disk failure를 허용한다. parity bit를 활용해 recovery 가능하다. 다만 2개이상의 disk failure가 나면 복구할 방법이 없다.
그렇다면 performance를 계산해보자.
single read request는 1개의 disk로 매핑되기 때문에 single disk와 성능이 동일하다.
다만 single write request는 다른데 위 예제에서 block 0에 write을 하려면 block 0을 읽고 parity block도 읽어서 parity bit를 다시 계산해야한다. 즉 read 2번, write 2번이 필요하고 read, write 각각 병렬로 처리할 수 있으므로 single disk의 latency의 약 2배정도 걸린다.

sequential read는 parity block으로 인해 (N - 1) * S MB/s의 대역폭을 가진다. sequential write도 parity bit을 같이 써주어야 하므로 (N - 1) * S MB/s가 되겠다.
random read는 모든 disk를 활용할 수 있다. 다만 random write은 (N / 4) * R MB/s을 가진다.
왜냐하면 만약 random write가 위 예제에서 block 1과 block 10에 대해 요청이 왔다면 parity bit 계산을 위해 disk 1과 disk 4를 읽어 block 1을 처리하고, disk 0과 disk 2를 읽어 block 10을 처리한다. 각 요청안에서 block data write과 parity block write은 병렬로 처리할 수 있므으로 총 4개의 I/O 가 필요하다.(disk 1의 read/write + disk 0의 read/write)

RAID Level 별로의 evaluation 결과는 다음과 같다.

RAID Evaluation


Summary

reliability는 고려하지 않고 performance가 중요한 상황이라면 RAID 0 striping이 좋은 선택이 될 수 있다.
반대로 reliability가 중요하고 random I/O 성능이 중요하다면 RAID 1 mirroring이 좋은 선택이다. 다만 비용이 비싸다.
capacity와 reliability가 중요하다면 RAID 5 가 좋은 선택이다. 다만 small-write의 성능이 좋지않은면이 있다.
만약 sequential I/O가 주된 접근이고 capacity를 최대화 해야하는 상황이라면 이 경우에도 RAID 5가 좋은 선택이다.

여기서 살펴본 RAID 디자인 말고도 다른 여러가지 디자인이 존재한다. 예를들어 RAID 6은 multiple disk failure를 허용한다.
RAID는 hardware 자체로 구현되어 제공되기도 하고 software로도 구현되어 제공될 수 있다.


References