RAID의 용량·패리티·복원
RAID 0·1·5·6이 데이터를 나누고 복사하고 검사 정보를 두는 방식과 쓸 수 있는 용량을 계산하고, XOR 패리티로 잃어버린 블록 하나를 되찾는 과정을 따라갑니다. RAID 6에 독립적인 두 번째 검사 정보가 필요한 이유, 쓰다가 전원이 끊길 때의 문제, RAID와 백업의 차이를 다룹니다.
디스크 여러 개를 묶으면 하나보다 큰 저장 공간을 만들 수도 있고, 디스크 하나가 고장 나도 데이터를 잃지 않게 할 수도 있습니다. 이렇게 여러 디스크에 데이터를 나누어 두거나 여분의 정보를 함께 두는 구성을 RAID(redundant array of independent disks)라고 합니다. 다만 RAID가 무엇을 지켜 주는지는 구성마다 다르고, 어떤 고장은 RAID로 막을 수 없습니다. 이 글에서는 게이트와 진리표의 XOR로 RAID의 복원 원리를 계산하고, 쓰기 정책·보조기억장치에서 본 쓰기 완료의 문제를 RAID에서 다시 봅니다.
나누기, 복사하기, 검사 정보 두기
RAID는 세 가지 방법을 섞어 씁니다. 연속된 데이터를 일정한 크기로 잘라 여러 디스크에 번갈아 두는 것을 스트라이핑(striping), 같은 데이터를 여러 디스크에 똑같이 두는 것을 미러링(mirroring), 데이터에서 계산한 복원용 정보를 함께 두는 것을 패리티(parity)라고 합니다. 여러 디스크에 걸쳐 같은 줄에 놓인 블록들의 묶음을 스트라이프(stripe)라고 부릅니다.
크기가 S로 같은 디스크 N개를 쓴다고 하고, 예비 디스크나 관리 정보가 차지하는 공간은 빼겠습니다. 아래 표의 최소 디스크 수는 흔히 쓰는 기준이며, 제품마다 다를 수 있습니다.
| 구성 | 최소 디스크 | 쓸 수 있는 용량 | 견딜 수 있는 디스크 고장 |
|---|---|---|---|
| RAID 0 | 2 | N × S | 0개 |
| RAID 1(N개 모두 같은 내용) | 2 | S | N − 1개 |
| RAID 5 | 3 | (N − 1) × S | 1개 |
| RAID 6 | 4 | (N − 2) × S | 2개 |
RAID 0은 스트라이핑만 해서 디스크를 모두 용량과 속도에 쓰지만, 여분의 정보가 없어 디스크 하나만 고장 나도 데이터를 잃습니다. RAID 1은 모든 디스크에 같은 내용을 복사하므로 한 디스크만 남아도 되지만, 용량은 디스크 하나만큼입니다. RAID 5는 스트라이프마다 패리티 블록 하나를, RAID 6은 서로 다른 검사 정보 두 개를 둡니다. 2 TB 디스크 4개라면 RAID 0은 8 TB, RAID 1은 2 TB, RAID 5는 6 TB, RAID 6은 4 TB를 쓸 수 있습니다.
RAID 5의 패리티는 디스크 하나에 몰아 두지 않고 스트라이프마다 다른 디스크에 돌아가며 둡니다. 그래서 용량표의 "디스크 하나만큼"은 전체 비용을 말하는 것이지, 패리티 전용 디스크가 따로 있다는 뜻이 아닙니다. 미러 쌍 여러 개에 스트라이핑하는 RAID 10은 표의 RAID 1과 용량 계산이 다릅니다. 흔히 쓰는 구성은 NI의 RAID 설명과 Red Hat의 RAID 수준 설명에서 볼 수 있습니다.
XOR 패리티로 잃어버린 블록을 되찾는다
손으로 계산하기 쉽게 블록 하나를 1바이트로 줄이겠습니다. 실제 블록은 이보다 훨씬 큽니다. 한 스트라이프의 데이터가 A = 3C, B = A5, C = 5A(16진수)이면 패리티는 입니다. A와 B의 XOR는 99이고, 여기에 C를 XOR하면 C3입니다.
| 블록 | 이진수 | 16진수 |
|---|---|---|
| A | 00111100 | 3C |
| B | 10100101 | A5 |
| A XOR B | 10011001 | 99 |
| C | 01011010 | 5A |
| P | 11000011 | C3 |
XOR에는 이고 라는 성질이 있습니다. 그래서 B가 있던 디스크가 고장 나면 남은 것을 모두 XOR한 에서 A와 C가 지워져 B = A5만 남습니다. 이 복원이 되려면 어느 블록을 잃었는지 알고 있고, 나머지 데이터와 패리티가 모두 온전해야 합니다. 어느 블록이 틀렸는지 모르는 손상까지 이 식으로 고칠 수 있는 것은 아닙니다. 패리티 블록 자체를 잃었다면 데이터로 다시 계산하면 됩니다.
블록 두 개를 함께 잃으면 패리티 하나로는 부족합니다. A와 B가 모두 없으면 99라는 식 하나만 남는데, A가 00부터 FF까지 256가지 가운데 무엇이든 99로 짝을 지을 수 있어서, 256쌍 모두가 이 식을 만족합니다. 미지수가 둘인데 식이 하나뿐인 셈입니다.
RAID 6의 두 번째 검사 정보
RAID 6은 디스크 두 개를 잃어도 되찾을 수 있도록 검사 정보를 두 개 둡니다. 그런데 두 번째 정보를 P와 같은 XOR로 계산해 Q = P로 두면, 같은 식을 두 번 적은 것이라 새로운 정보가 없습니다. 그래서 Q는 블록마다 다른 계수를 곱해 만든 독립적인 식이어야 합니다.
이때의 곱셈은 보통의 정수 곱셈이 아니라, 바이트를 이라는 유한체(finite field)의 원소로 보고 하는 곱셈입니다. 이 체계에서 덧셈은 XOR이고, 2를 곱하는 것은 한 비트 왼쪽으로 시프트한 뒤 8비트를 넘친 자리가 생기면 0x11D, 곧 을 XOR해 8비트 안으로 줄이는 연산입니다. 예를 들어 A5를 시프트하면 14A라 9번째 비트가 생기므로 14A XOR 11D = 57이고, 5A를 시프트한 B4는 8비트 안이라 그대로입니다. 4를 곱하는 것은 2를 두 번 곱한 것이라 B4를 다시 시프트한 168을 줄여 75입니다. 그래서 3C XOR 57 XOR 75 = 1E입니다.
이제 A와 B를 함께 잃어도 C를 지우면 99와 6B라는 서로 다른 두 식이 생기고, 두 식을 함께 만족하는 쌍은 하나뿐입니다. 아래 코드는 A의 256가지 후보를 모두 넣어 이것을 확인합니다. 실제 RAID 6 구현은 이런 검색 대신 체의 나눗셈으로 바로 풀며, 디스크 배치와 복구 절차도 훨씬 복잡합니다.
def usable_capacity(level, disks, each_size):
minimum = {0: 2, 1: 2, 5: 3, 6: 4}
if level not in minimum or disks < minimum[level] or each_size <= 0:
raise ValueError("RAID 수준과 디스크 수를 확인하세요")
data_disks = {0: disks, 1: 1, 5: disks - 1, 6: disks - 2}[level]
return data_disks * each_size
def gf_mul(a, b):
result = 0
while b:
if b & 1:
result ^= a
a <<= 1
if a & 0x100:
a ^= 0x11D
b >>= 1
return result
print([usable_capacity(level, 4, 2) for level in (0, 1, 5, 6)])
a, b, c = 0x3C, 0xA5, 0x5A
p = a ^ b ^ c
q = a ^ gf_mul(2, b) ^ gf_mul(4, c)
print(f"P={p:02X} Q={q:02X} B 복원={p ^ a ^ c:02X}")
one_equation = [(x, p ^ c ^ x) for x in range(256)]
two_equations = [(x, y) for x, y in one_equation if x ^ gf_mul(2, y) ^ gf_mul(4, c) == q]
print(len(one_equation), [f"{x:02X},{y:02X}" for x, y in two_equations])
new_a = 0x3D
print(f"옛 P로 복원한 B={p ^ new_a ^ c:02X}")[8, 2, 6, 4]
P=C3 Q=1E B 복원=A5
256 ['3C,A5']
옛 P로 복원한 B=A4첫 줄은 2 TB 디스크 4개의 RAID 0, 1, 5, 6 용량입니다. gf_mul은 b의 비트가 1인 자리마다 a를 XOR로 더하고, a를 한 자리씩 시프트할 때마다 넘친 비트를 0x11D로 줄입니다. 둘째 줄은 손으로 구한 P와 Q, 그리고 B를 잃었을 때 P로 되찾은 값입니다. 셋째 줄은 A와 B를 함께 잃었을 때, P의 식 하나만으로는 후보가 256쌍이지만 Q의 식까지 쓰면 원래의 (3C, A5) 한 쌍만 남는다는 것을 보여 줍니다. 마지막 줄은 다음 절의 예입니다.
쓰다가 전원이 끊기면
A를 3C에서 3D로 고치면 패리티도 C2로 고쳐야 합니다. 그런데 디스크마다 쓰기가 따로 일어나므로, A는 3D로 쓰였는데 P는 아직 C3인 순간에 전원이 끊길 수 있습니다. 이 상태에서 B를 잃으면 옛 패리티로 계산한 B는 마지막 줄처럼 A4가 되어 원래의 A5와 다릅니다. 데이터와 패리티가 서로 다른 시점의 값을 갖게 되어 복원이 틀리는 이 문제를 쓰기 구멍(write hole)이라고 합니다.
그래서 실제 시스템은 쓰기를 먼저 별도의 기록에 남겨 두었다가 전원이 돌아오면 데이터와 패리티를 맞추는 등의 방법을 씁니다. Linux의 소프트웨어 RAID가 이 문제를 다루는 방법은 Linux 커널의 RAID 5 캐시 문서에, 쓰기가 덜 끝난 상태에서 디스크까지 잃은 배열을 다시 맞출 때의 위험은 Linux 커널의 RAID 배열 문서에 있습니다. 이 글의 계산은 한 스트라이프의 원리를 보인 것일 뿐, 그런 상태에서 복구가 된다는 뜻은 아닙니다.
더 깊이: 전용 장치 없이 쓰기 구멍을 막는 부분 패리티 로그
Linux의 소프트웨어 RAID에는 쓰기 구멍을 막는 방법이 두 가지 있습니다. 하나는 앞에서 링크한 RAID 5 캐시로, 쓰기를 별도의 저널 장치에 먼저 기록합니다. 다른 하나인 부분 패리티 로그(partial parity log, PPL)는 RAID 5 배열에서 쓰며, 전용 저널 장치 없이 배열에 속한 디스크들의 메타데이터 영역에 기록을 남깁니다.
부분 패리티는 이번 쓰기가 고치지 않는 데이터 블록들의 XOR입니다. 여기에 고친 블록을 XOR하면 그 스트라이프의 패리티가 나오므로, 전원이 끊긴 뒤에도 디스크에 남은 데이터에 맞는 패리티를 다시 계산할 수 있습니다. 앞의 예에서 A를 고치는 쓰기라면 부분 패리티는 FF이고, 디스크에 새 A 3D가 남았다면 FF와 XOR해 C2를, 옛 A 3C가 남았다면 C3를 패리티로 다시 씁니다.
다만 문서는 PPL이 진짜 저널이 아니라고 밝힙니다. 쓰는 중이던 데이터를 잃지 않게 해 주는 것이 아니라 패리티가 어긋나 생기는 소리 없는 데이터 손상만 막아 주며, 쓰는 중이던 디스크 자체를 잃으면 그 스트라이프는 PPL로 복구하지 않습니다. 또 쓰기 성능이 최대 30–40% 떨어질 수 있지만, 기록이 배열의 디스크들에 나뉘어 남으므로 디스크 수에 맞추어 성능이 늘어나고, 저널 장치 하나가 병목이나 단일 장애 지점이 되지도 않습니다.
RAID는 백업이 아니다
미러링과 패리티는 지금의 상태를 지킵니다. 그래서 사용자가 실수로 파일을 지우면 그 삭제도 모든 복사본과 패리티에 곧바로 반영되고, RAID만으로는 지우기 전의 내용을 되찾을 수 없습니다. 지우기 전의 내용을 되찾으려면 다른 시점의 상태를 따로 보관한 백업이 있어야 합니다. 백업도 얼마 동안 보관하는지, 원본과 같은 장소에서 함께 망가지지 않는지, 실제로 되살릴 수 있는지를 확인해야 합니다. 디스크 고장, 실수로 인한 삭제, 데이터 손상, 건물 전체의 재해는 서로 다른 위험이며, RAID는 그 가운데 디스크 고장에 대비하는 방법입니다.
정리
- RAID는 스트라이핑, 미러링, 패리티를 섞어 디스크 여러 개를 하나처럼 쓰며, RAID 0은 용량을, RAID 1은 복사를, RAID 5와 6은 패리티로 고장을 견딥니다.
- 크기 S인 디스크 N개의 용량은 RAID 0이 N × S, RAID 1이 S, RAID 5가 (N − 1) × S, RAID 6이 (N − 2) × S입니다.
- XOR 패리티는 어느 블록을 잃었는지 알고 나머지가 온전할 때 블록 하나를 되찾지만, 두 블록을 잃으면 식이 하나라 되찾을 수 없습니다.
- RAID 6의 두 번째 검사 정보는 의 곱셈으로 만든 독립적인 식이라서, 두 블록을 함께 잃어도 되찾을 수 있습니다.
- 데이터와 패리티의 쓰기 사이에 전원이 끊기면 쓰기 구멍이 생기며, RAID는 실수로 지운 데이터를 지켜 주지 못하므로 백업이 따로 필요합니다.
연습 문제
1. 용량과 복원
1 TB 디스크 5개로 RAID 5와 RAID 6을 만들면 쓸 수 있는 용량은 각각 얼마인가요? 본문의 A, B, C 예에서 C만 잃고 A, B, P가 온전하다면 C를 되찾으세요.
해설 보기
RAID 5는 디스크 하나만큼을 패리티에 쓰므로 4 TB, RAID 6은 두 개만큼을 쓰므로 3 TB입니다. C는 남은 것을 모두 XOR해 = C3 XOR 3C XOR A5로 구하는데, C3 XOR 3C가 FF이고 FF XOR A5가 5A이므로 C = 5A입니다. 원래의 C와 같으므로 복원이 맞습니다. 어느 블록을 잃든 남은 블록을 모두 XOR하면 된다는 점이 XOR 패리티의 편리한 점입니다.
2. 같은 패리티를 두 번 저장하면
A와 B를 함께 잃었는데, 두 번째 검사 정보 Q가 P와 똑같은 값을 복사해 둔 것이라면 A와 B를 하나로 정할 수 있나요?
해설 보기
정할 수 없습니다. Q = P라면 Q에서 얻는 식도 99로 P의 식과 똑같아서, 식이 사실상 하나뿐입니다. 본문에서 본 것처럼 이 식을 만족하는 (A, B)는 256쌍이므로 어느 것이 원래 값인지 알 수 없습니다. 디스크 두 개를 잃어도 되찾으려면 두 식이 서로 독립이어야 하고, RAID 6이 계수를 달리한 곱셈으로 Q를 만드는 이유가 여기에 있습니다.
3. 미러링과 삭제
RAID 1로 묶은 두 디스크에서 사용자가 중요한 파일을 실수로 지웠습니다. 한쪽 디스크를 떼어 내어 그 파일을 되찾을 수 있나요?
해설 보기
되찾을 수 없습니다. RAID 1은 두 디스크에 같은 쓰기를 동시에 하므로, 파일을 지우는 쓰기도 두 디스크에 똑같이 반영되었습니다. 한쪽을 떼어 내도 그 디스크에는 이미 파일이 지워진 상태가 들어 있습니다. 지우기 전의 파일을 되찾으려면 그 이전 시점에 따로 보관해 둔 백업이 필요합니다. RAID는 디스크가 고장 나도 지금의 상태를 잃지 않게 하는 방법이지, 지난 상태를 보관하는 방법이 아닙니다.
RAID 구성을 고를 때는 용량, 견딜 수 있는 디스크 고장의 수, 쓰기 도중의 전원 문제, 백업을 따로 적어 비교해 보세요. 다음 장의 I/O와 DMA에서는 저장장치 같은 외부 장치와 메모리 사이에서 데이터를 옮기는 방법을 다룹니다.