본문 바로가기

RAID의 용량·패리티·복원 경계

RAID0·1·5·6의 용량을 계산하고 한 stripe의 누락 복원·두 누락·정전·백업의 차이를 추적합니다.

디스크 여러 개를 묶으면 큰 저장 공간이나 고장에 대한 여유를 만들 수 있습니다. RAID는 디스크에 데이터를 나누거나 중복 정보를 두는 구성입니다. 비트 XOR과 저장의 완료 조건을 사용해 용량과 복원이 성립하는 조건을 따로 계산합니다.

어떤 중복 정보를 저장하는가

여기서는 디스크 N개가 모두 같은 사용 가능 크기 S이고 spare·메타데이터·파일시스템 비용은 제외합니다. spare는 고장 난 디스크를 대신할 예비 장치이며 정상 데이터 용량에 더하지 않습니다. 표의 최소 수는 일반적인 다중 디스크 구성 기준입니다. 특정 제품의 최소/최대 수·예약 공간은 제품 규격을 따릅니다.

구성일반적 최소 디스크데이터 용량온전한 나머지 데이터로 감당하는 디스크 누락
RAID02N×S중복 정보가 없어 0개
RAID1, 모든 N개에 동일 복사2S같은 데이터의 온전한 복사 하나가 남을 때 N−1개
RAID53(N−1)×S1개
RAID64(N−2)×S2개

예를 들어 2 TB 디스크 4개이면 RAID0 8 TB, 모두 같은 내용을 미러링한 RAID1 2 TB, RAID5 6 TB, RAID6 4 TB입니다. RAID1의 표는 N개 모두 한 내용을 복사하는 구성으로, 여러 mirror 쌍에 striping한 RAID10의 용량식과 다릅니다. 일부 소프트웨어는 한 장의 RAID0도 만들 수 있지만 그 경우 여러 장에 나누는 효과는 없습니다.

striping은 연속 데이터 덩어리를 여러 디스크에 나눕니다. mirroring은 같은 데이터를 복사합니다. parity는 데이터에서 계산한 복원용 검사 정보를 저장합니다. RAID5는 stripe마다 parity를 담는 디스크를 돌아가며 배치하고 RAID6는 독립적인 검사 정보 두 개를 둡니다. 용량식의 “디스크 하나·둘만큼”은 총 비용이며 전용 parity 디스크를 반드시 한 곳에 고정한다는 뜻이 아닙니다. 제조사 NI의 RAID 구성 표는 일반적인 최소 수 2·2·3·4를 제시합니다. Red Hat의 RAID 수준 설명은 여러 장 미러링의 용량과 RAID5·6의 parity 비용을 설명합니다. 아래 계산은 자체 정의한 stripe 모델입니다.

XOR parity로 누락 하나 복원하기

stripe는 같은 줄에서 여러 디스크에 나누어 둔 데이터/검사 블록 묶음입니다. 손 계산을 위해 각 블록을 1바이트로 줄입니다. 실제 chunk·sector 크기는 이 예제와 다릅니다. 데이터 세 개가 A=3C, B=A5, C=5A일 때 P=A XOR B XOR C = C3입니다. 숫자는 16진수입니다.

블록2진수계산
A001111003C
B10100101A5
A XOR B1001100199
C010110105A
P1100001199 XOR 5A = C3

XOR는 v XOR v=0, v XOR 0=v이므로 B가 누락되면 P XOR A XOR C = A5로 복원합니다. 어느 블록이 누락됐는지 알고, 나머지 데이터와 parity가 온전하다는 조건이 필요합니다. 위치를 모르는 임의 오염을 이 식만으로 고칠 수 있다고 단정하지 않습니다.

A·B가 함께 없으면 A XOR B = P XOR C = 99만 압니다. A 후보 00–FF 각각에 B=A XOR 99를 짝지을 수 있어 256쌍이 모두 같은 P를 만족합니다. parity 하나가 두 미지수를 독립적으로 결정하지 못합니다. P 블록만 누락됐다면 모든 데이터로 P를 다시 계산할 수 있습니다.

RAID6는 XOR를 똑같이 두 번 저장하지 않습니다

두 번째 검사값 Q=P를 저장하면 같은 식이 반복되므로 A·B를 구하는 정보가 늘지 않습니다. 이 절은 원리를 보이기 위해 바이트를 GF(2⁸)이라는 유한한 계산 체계의 원소로 둡니다. 덧셈은 XOR이며 곱셈은 보통 정수 곱셈이 아닙니다. 8비트 밖으로 넘친 항은 다항식 x⁸+x⁴+x³+x²+1(비트 표현 0x11D)로 줄입니다.

계수 1·2·4를 달리해 Q = A XOR (2×B) XOR (4×C)를 계산합니다. 여기서 2×는 한 비트 왼쪽 이동 후 필요한 경우 0x11D로 줄이는 연산입니다. 다음 세 단계를 손으로 확인합니다.

계산한 비트 왼쪽 이동9번째 비트가 있으면 줄이기결과
2×A5A5 << 1 = 14A14A XOR 11D57
2×5A5A << 1 = B48비트 안이므로 그대로B4
4×5A = 2×(2×5A)B4 << 1 = 168168 XOR 11D75

따라서 Q=3C XOR 57 XOR 75 = 1E입니다. 남은 C를 제거하면 두 식은 A XOR B=99, A XOR 2×B=6B가 됩니다. 코드에서는 A의 후보 256개를 검사해 두 식을 함께 만족하는 한 쌍만 남기는 방법을 사용합니다. 실제 RAID6 구현의 전체 인코딩·디스크 배치·복구 절차를 작성하는 코드는 아닙니다.

Python에서 ^는 XOR, b & 1은 b의 마지막 비트가 1인지 확인하는 연산입니다. <<= 1과 >>= 1은 각각 한 비트 왼쪽·오른쪽 이동입니다. gf_mul은 b의 각 1비트에 해당하는 a의 항만 XOR로 더하고, a를 다음 항으로 이동할 때 위 표처럼 넘친 비트를 줄입니다. 마지막 비트부터 처리한 b가 0이 되면 while b가 끝납니다.

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("이 표의 다중 디스크·같은 크기 조건을 확인하세요")
    data_disks = {0: disks, 1: 1, 5: disks - 1, 6: disks - 2}[level]
    return data_disks * each_size

def gf_mul(a, b):
    if not (0 <= a < 256 and 0 <= b < 256):
        raise ValueError("한 바이트만 계산합니다")
    result = 0
    while b:
        if b & 1:
            result ^= a
        a <<= 1
        if a & 0x100:
            a ^= 0x11D
        b >>= 1
    return result

a, b, c = 0x3C, 0xA5, 0x5A
assert [usable_capacity(level, 4, 2) for level in (0, 1, 5, 6)] == [8, 2, 6, 4]
p = a ^ b ^ c
q = a ^ gf_mul(2, b) ^ gf_mul(4, c)
assert (p, q) == (0xC3, 0x1E)
assert p ^ a ^ c == 0xA5  # B만 누락: 손으로 계산한 예상
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
]
assert len(one_equation) == 256
assert two_equations == [(0x3C, 0xA5)]
print(f"P={p:02X}, Q={q:02X}")
print("누락 2개의 후보 수:", len(one_equation), len(two_equations))

# A만 먼저 새 값으로 저장된 뒤 정전: 옛 parity를 믿는 복원은 틀립니다.
new_a = 0x3D
assert p ^ new_a ^ c == 0xA4  # 실제 남아 있어야 할 B는 A5
assert (new_a ^ b ^ c) != p

raid_stripe.py로 실행하면 P=C3, Q=1E, 후보 수 256 1을 출력합니다. RAID6의 두 독립 식이 필요한 이유를 이 한 stripe에서 확인합니다. 디스크 드라이버·정전 복구·오염 검출을 실제 실행한 결과는 아닙니다.

write hole과 백업은 다른 문제

위 stripe에서 A를 3C→3D로 바꾸면 새 P는 C2여야 합니다. A만 3D로 기록되고 P는 C3인 상태에서 전원이 끊기면 서로 다른 시점의 정보가 남습니다. B가 누락된 뒤 옛 P로 계산하면 A4를 얻어 원래 B=A5와 다릅니다. 데이터와 parity의 부분 기록 때문에 중복 정보가 어긋나는 write hole의 작은 예입니다. 전체 stripe를 정상적으로 쓰는 중이라도 실제 기록 완료·내구성 조건을 정해야 합니다.

Linux RAID cache 문서는 기록 journal/cache의 동작과 write hole을 설명합니다. Linux RAID arrays 문서는 dirty이면서 degraded인 RAID5/6에서 parity를 신뢰하지 못해 복원이 위험한 경계를 명시합니다. 이 교재의 두 식 성공을 그런 상태의 복구 보장으로 확대하지 않습니다.

mirroring은 현재 상태를 복사하므로 정상 삭제도 복사됩니다. RAID1의 두 복사본에 같은 삭제가 적용되면 삭제 전 내용을 그 복사본만으로 복원할 수 없습니다. 삭제 전 버전을 따로 보관한 백업에는 다른 시간의 상태가 있어야 합니다. 백업도 보존 기간·분리된 장애 영역·복원 가능성을 확인해야 합니다. 디스크 누락, 논리 삭제, 오염, 사이트 전체 장애는 서로 다른 조건입니다.

독립 문제: 용량과 복원 전제

같은 크기 1 TB 디스크 5개의 RAID5·RAID6 데이터 용량을 구하세요. 위 A·B·C 예에서 C만 누락되고 A·B·P는 온전하면 C를 복원하세요. A·B가 둘 다 누락되었는데 Q는 P와 똑같은 복사본이면 두 값을 유일하게 구할 수 있나요?

힌트

용량에서 parity 비용은 한 장·두 장 분량입니다. 같은 식을 두 번 적는 것은 독립적인 두 식이 아닙니다.

풀이

RAID5는 4 TB, RAID6는 3 TB입니다. C=C3 XOR 3C XOR A5 = 5A입니다. Q=P이면 A·B 후보는 여전히 256쌍이라 유일하게 구하지 못합니다. RAID0/1/5/6를 다뤘으며 RAID2/3/4·RAID10 상세 배치와 실제 재구축 운영은 이후 범위입니다. 다음 I/O와 DMA에서 장치와 메모리 사이의 전송 책임을 봅니다.