Embedded : : Arm Architecture

디스크를 교체했더니 디스크 I/O 지연이 발생했다면? "파티션 정렬" 하고 가세요!

Jay.P Morgan 2024. 4. 24. 12:55

 

  0 파티션 정렬의 필요성

 

데이터베이스나 Hadoop 어플리케이션을 띄우고, 데이터는 4 TByte(Tera Byte)의 하드 디스크에 저장하며 사용하고 있었습니다. 어느 날, 디스크 깨져서 새로 교체를 하고 파일시스템을 포맷 하였는데요, 뒤로 I/O 눈에 띄게 지연되는 것을 느끼게습니다.

그 이유가 뭘까요?

이유는 바로 파티션의 시작점이 정렬되지 않았기 때문입니다. OS 설치 시에 인스톨러 똑똑하게도 알아서 파티션의 시작 위치를 정렬하여 첫번째 파티션을 만들어주기에, 파티션 정렬이라는 개념을 고려할 필요가 없었습니다. 그러나 OS 설치된 이후에 어떤 이유에 의해서 사용자가 직접 파티션을 만들게 되는 위와 같은 상황일 파티션 정렬(Partition Alignment) 필요성을 모르는 사용자라면 인스톨러 했던 파티션 시작 위치 정렬 작업을 누락하게 됩니다. 그리고 사용자는 I/O 지연을 경험하게 되겠지요.

사실은 얼마 까지만 해도 파티션 정렬이라는 개념을 고려하지 않아도 됐습니다. 지금은 물리 디스크 데이터 접근 단위인 섹터(Sector) 크기를 성능 향상을 위해 키움에 따라서 문제가 되기 시작했습니다.

무슨 말인지 모르셔도 괜찮습니다! 지금부터 하나하나 설명해 보도록 하겠습니다.

  1 파티션 정렬의 기술적 이야기

 

  1.1 '파티션 정렬'이 필요하게 된 히스토리

 

섹터(Sector) 물리 디스크에 입출력을 요청하는 최소 단위 입니다. 전통적인 하드 디스크는 512 Byte 크기의 섹터들로 구성 되어 있습니다. 데이터가 점점 많아지고, 크기가 커짐에 따라 하드 디스크 업계도 점점 용량의 디스크를 놓게 되었습니다. 사용자가 데이터에 접근 하고자 하는 경우 섹터 크기가 작을수록 많은 수의 입출력 요청이 발생하게 되기에, 디스크 업계에서는 퍼포먼스를 올리기 위해서 섹터 자체의 크기를 늘리고자 하게 되었습니다. 이전부터 년에 걸쳐 관련한 작업이 진행되었고 2011 1 모든 하드 디스크 제조 업체가 섹터 크기를 4096 KByte 표준으로 하는 것에 합의하여 제품을 출하하기 시작 했습니다.

과정에서 섹터 크기를 키울 경우 기존의 전통적인 섹터 크기인 512 Byte 기준으로 설계된 여러가지 시스템, 프로그램들은 무방비로 영향을 받게 됩니다. 때문에 현재는 과도기로서 물리적으로는 섹터 크기가 4096 Byte(4 KByte) 것으로 입출력을 요청 하나, 하드디스크에 내장 컨트롤러가 논리적으로는 섹터 크기가 512 Byte 것과 같이 에뮬레이션 하는 어드밴스드 포맷(Advanced Format) 디스크가 나오게 되었습니다.

실제 물리 디스크에 입출력을 요청하는 단위를 '물리 섹터' 부르고 크기는 4096 Byte 입니다. 그리고 디스크 상단으로 에뮬레이션 되는 단위를 '논리 섹터'라 부르고, 그 크기는 전통적인 섹터와 동일한 512 Byte입니다. 논리, 물리 섹터 크기가 8배의 차이를 가지고 있는 것이지요.

어드밴스드 포맷이 사용되고 , 데이터의 크기가 커짐에 따라 레이드(RAID) 데이터 연속 단위인 스트립(Stripe), 어플리케이션 들의 데이터 접근 단위들도 덩달아 커지게 되었는데요. 전통적인 방식의 하드디스크에서는 조용했던 문제가 대두되기 시작합니다.

 

  1.2 '파티션 정렬'의 의미

 

디스크의 번째 논리 섹터(CHS[1]*(0,0,1) / LBA[2]* 0 / 크기 : 512 Byte)에는 MBR영역이 위치하고 있습니다. MBR영역에는 부트 코드, 파티션 정보 등이 담긴 공간으로 해당 파티션이 부팅 가능한지, 관련 주소 그리고 섹터 등이 명시되어 있습니다. MBR 영역 뒤에는 어느정도의 공간이 파일시스템 관련 정보를 저장해두거나 비워둘 있는 공간이 있고 이를 'Hidden Sectors' 부릅니다. (파일시스템의 종류나 파티션 테이블 종류에 따라서 Hidden Sectors 수는 다르기도 합니다.) 뒤에 이어 데이터를 저장할 있는 번째 파티션이 시작 있는데요. 아래의 그림은 63개의 논리 섹터 단위의 Hidden Sector 가진 상태입니다.


[1] CHS(Cylinder-Head-Sector) 데이터를 읽고 쓰려면 해당 섹터위치를 읽어와야 하며, Cylinder, Head, Sector 기반으로 위치를 지정하는 방식을 CHS 말합니다. 예를 들어 어떤 파일의 시작 섹터위치가 CHS(1,2,3,)이라고 정하면, 하드디스크의 2번째 Head 1번째 Cylinder, 3번째의 Sector 위치시킵니다. 그렇지만 이후에 ATA 용량제한이 발생하여서 LBA 방식으로 넘어가게되었습니다.

[2] LBA (Logical Block Addressing) 전체 섹터에 0부터 일련번호를 붙여서 지정하는 방식으로 읽고자 하는 파일의 논리적인 시작섹터주소를 찾으면 디스크 컨트롤러가 물리적 주소로 변환하여 접근가능하게 해줍니다

[그림1]

[그림 1] 디스크의 부분에는 Reserved Hidden Sectors(LBA 0 ~ 62 / 63개의 논리 섹터들) 있고 바로 뒤인 LBA 63 섹터부터 파티션이 시작 되는 것을 있습니다. 경우 논리 섹터 입장에서는 경계를 지켜 데이터가 위치 것으로 보이지만, 물리 섹터 경우에는 경계에 맞물려 데이터가 담긴 블록 위치하게 됩니다. 기존 섹터 크기가 512 Byte였을 당시와 비교할 섹터 단위의 입출력 8번을 수행하는 것에 변화가 없습니다.

하지만 섹터 4096 Byte크기로 커진 어드밴스드 포맷(Advanced Format) 사용하는 현재의 디스크들의 경우 512 Byte크기의 섹터 크기로 에뮬레이션 하나 실제로는 4096 Byte 크기의 섹터 단위로 입출력을 수행하기 때문에 [그림 1] 물리 섹터 8번과 9 섹터 각각 접근해야 합니다. 블록 크기는 4096 Byte 물리 섹터 같아 어떠한 경우에는 1번의 입출력 수행으로 블록 개의 데이터에 접근할 있을 입니다.

만약 8 물리 섹터 남은 512 Byte 만큼을 인덴트(Indent) 한다면 아래 [그림 2] 같습니다.

[그림2]

[그림 2]의 경우 [그림 1]과 동일하게 Reserved 된 Hidden Sectors(LBA 0 ~ 62 번 / 총 63개의 논리 섹터들)를 가지고 있습니다. LBA 63번 논리 섹터가 있지만 1개 논리 섹터(512 Byte)만큼 Indent를 하여 물리 섹터의 경계에 정렬했습니다. 이 때 첫 번째 블록에 접근을 시도 한다면 9번 물리 섹터에만 입출력을 요청 하면 됩니다. 따라서 [그림 1]의 상황에 반해 그 절반인 1번의 입출력만 요청하면 되는 것 이지요. 이와 같이 불필요한 입출력을 통한 오버헤드를 줄이고 퍼포먼스를 높일 수 있도록 파티션의 시작 지점을 물리 섹터의 경계에 맞춰 주는 것이 '파티션 정렬'의 기본적인 개념입니다.

위의 그림 들은 특정파일시스템을 기준으로 하여 LBA 62번 까지가 Reserved 된 Hidden Sectors (총 63개의 논리 Sectors / 512 * 63 Byte)였는데요, 파일시스템이나 파티션 종류마다 파티션이 시작 되기 전에 필요한 섹터의 개수가 다를 수 있기 때문에 OS 인스톨러는 오랜시간동안 보수적으로 결정된 크기인 1024 KByte (2048 개의 논리 섹터 / 256개의 물리 섹터) 만큼을 Indent하여 파티션의 시작점을 정렬하고 있습니다.

 

  2 파티션 정렬의 예시

 

  2.1 RAID 구성 시의 파티션 정렬

 

RAID를 구성하면 스트립(Stripe)라는 단위로 데이터가 연속하여 저장 됩니다. 스트립는 RAID 구성 시에 그 크기를 결정하는 단위로 보통의 경우 64 KByte 또는 128 KByte, 또는 더 큰 크기, 즉 물리 섹터(4096 Byte)보다 훨씬 큰 단위로 설정을 합니다. 그 이유 역시 데이터가 커지고 많아짐에 따라서 입출력 요청 횟수를 줄여 퍼포먼스를 올리고자 함 입니다.

예를 들어 2개의 디스크를 RAID0, 스트립 사이즈 64 KByte로 구성을 하였을 때, 128 KByte의 데이터가 쓰여진다면 1번 디스크에스트립 크기인 64 KByte만큼의 데이터가 쓰여지고, 나머지 데이터는 2번 디스크에 쓰여집니다. 4096 Byte의 물리 섹터의 경계에는 잘 정렬했더라도 파일시스템 블록이 스트립의 경계에 정렬되지 않는다면 1개의 스트립에 쓰여질 수 있는 데이터가 2개의 스트립에 나뉘어서 쓰여져 불필요 하게 2번의 입출력이 발생하여 I/O 지연을 경험하게 될 수 있습니다.

[그림 3]에 3,4번째 줄에 등장하는 클러스터(Cluster)는 데이터베이스에서 자주 사용되는 테이블의 데이터들을 '클러스터'라는 단위로 그룹화하는 것 입니다. 이렇게 자주 접근이 있는 데이터들을 클러스터 단위로 그룹화 하여 그 단위로 입출력을 요청 함으로서, 디스크에 적은 횟수의 입출력을 하는 효과를 보게 됩니다. 그 효과를 최대화 하기 위해서는 클러스터가 물리 디스크의 입장에서 봤을 때 물리 섹터 그리고 레이드 입장에서의 스트립의 경계에 위치하게 되어야 할 것 입니다.​

[그림 3]의 3번째 줄은 [그림 1]과 동일 상황이라고 볼 수 있습니다. 그리고 [그림 3]의 4번째 줄은 [그림 2]와 동일 상황이라고 볼 수 있습니다. 3번째 줄의 경우 물리 섹터에 정렬을 하지않아 어떤 클러스터에 접근하고자 할 때 불필요 하게 2개 물리 섹터에 접근해야하는 상황뿐 아니라 또 어떤 클러스터에 접근 할 때에는 스트립의 경계에도 위치하는 상황도 있어 2개의 스트립에 접근 해야하는 상황이기 때문에 I/O지연을 경험하게 됩니다. 4번째 줄의 경우 클러스터 크기가 비교적 작은 (4096 Byte)로 물리 디스크의 앞부분에 위치한 Hidden Sectors (MBR을 포함한 Reserved Area + Indent / 64 논리 섹터)뒤에 클러스터들이 놓였을 때 스트립의 경계에 잘 위치하여 입출력을 최소한으로 할 수 있었습니다.

[그림 4]의 경우에는 [그림 3] 과 비슷해 보이나 클러스터의 크기가 64 KByte로 스트립 크기와 동일하게 셋팅이 되었습니다. 3번째 줄은 물리 디스크의 앞부분에 위치한 Hidden Sectors (MBR을 포함한 Reserved Area / 63 논리 Sector)의 뒤에 64 KB의 크기를 가진 클러스터들이 놓여 있습니다. 그리고 스트립 경계에도 놓였는데요, 클러스터의 크기가 이렇게 비교적 큰 경우에는 [그림 3]의 4번째 줄과 같이 1개의 논리 섹터 크기만큼 Indent를 주어 정렬을 했더라도 스트립 경계에 놓이게 되었을 것입니다. 이 때 [그림 4]의 4번째 줄과 같이 1개의 논리 섹터 만큼이 아닌 65개 논리 섹터만큼를 준다면 스트립의 경계에 클러스터가 놓이게 되지 않을 것 입니다. [그림 4]에서와 같이 4번째 줄의 첫 번째 클러스터의 데이터로 접근하기 위해 입출력을 한다면 2번째 스트립에서 데이터 입출력을 끝낼 수 있습니다.

이러한 '정렬(Alignment)'을 이해하고 수행 한다면, 직접 파일시스템을 포맷하고, 파티션을 만들 때에도 1024 KByte 만큼 파티션 시작점을 미뤄 정렬을 한다면 디스크 I/O시 성능 저하를 경험하지 않을 수 있습니다.

그렇다면 어떻게 파티션 정렬을 할 수 있을지 보겠습니다.

  3 파티션 정렬을 하는 법

 

  3.1 fdisk를 이용한 파티셔닝

 

fdisk의 경우에는util-linux-ng_2.17.1 부터 제대로 된 정렬을 지원 합니다. 그러므로 오래된 OS를 사용하고 있다면 fdisk보다는parted 를 이용할 것을 권고 합니다.

fdisk를 이용하여 파티션 정렬을 할 경우, 2.17.1 이후 버전을 사용하는 것을 권고하며, DOS 호환 모드를 비활성화 시키면, fdisk는 1MB 경계에서 정렬이 됩니다.

fdisk를 이용할 경우, 다음의 사항을 권고 합니다.


1. util-linux-ng 2.17.1 이후 버전을 사용

2. fdisk 의 경고를 잘 보십시오.

3. DOS 호환 모드 를 비활성화 (-c 옵션)

4. 표시 단위는 섹터를 이용 (-u 옵션)

5. 파티션의 끝을 지정할 경우에는, + 사이즈(M,G) 옵션 사용


a. 잘못된 정렬 예제

아래의 예제는 잘못된 정렬의 예를 보여 주며, 원인은 DOS 호환 모드 때문 입니다.

[root@host ~]$ fdisk /dev/sdb
Device contains neither a valid DOS partition table, nor Sun, SGI or OSF disklabel
Building a new DOS disklabel with disk identifier 0xe4909079.
Changes will remain in memory only, until you decide to write them.
After that, of course, the previous content won't be recoverable.
Warning: invalid flag 0x0000 of partition table 4 will be corrected by w(rite)
WARNING: DOS-compatible mode is deprecated. It's strongly recommended to
switch off the mode (command 'c') and change display units to
sectors (command 'u').
Command (m for help): p
Disk /dev/sdb: 160.0 GB, 160041885696 bytes
255 heads, 63 sectors/track, 19457 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0xe4909079
Device Boot Start End Blocks Id System
Command (m for help): n
Command action
e extended
p primary partition (1-4)
p
Partition number (1-4): 1
First cylinder (1-19457, default 1):
Using default value 1
Last cylinder, +cylinders or +size{K,M,G} (1-19457, default 19457): +10G
Command (m for help): u
Changing display/entry units to sectors
Command (m for help): p
Disk /dev/sdb: 160.0 GB, 160041885696 bytes
255 heads, 63 sectors/track, 19457 cylinders, total 312581808 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0xe4909079
Device Boot Start End Blocks Id System
/dev/sdb1 63 20980889 10490413+ 83 Linux
Command (m for help): q
[root@host ~]$

b. fdisk 2.17.1 이전 버전을 이용한 파티션 정렬

-S 32 -H 64옵션을 사용하여, 두번째 synlinder를 정렬에 사용할 수 있습니다.

[root@host ~]$ fdisk -S 32 -H 64 /dev/sdc
The number of cylinders for this disk is set to 65536.
There is nothing wrong with that, but this is larger than 1024,
and could in certain setups cause problems with:
1) software that runs at boot time (e.g., old versions of LILO)
2) booting and partitioning software from other OSs
(e.g., DOS FDISK, OS/2 FDISK)
Command (m for help): p
Disk /dev/sdc: 68.7 GB, 68719476736 bytes
64 heads, 32 sectors/track, 65536 cylinders
Units = cylinders of 2048 * 512 = 1048576 bytes
Disk identifier: 0x5a3b93b6
Device Boot Start End Blocks Id System
Command (m for help): n
Command action
e extended
p primary partition (1-4)
p
Partition number (1-4): 1
First cylinder (1-65536, default 1): 2
Last cylinder or +size or +sizeM or +sizeK (2-65536, default 65536):
Using default value 65536
Command (m for help): p
Disk /dev/sdc: 68.7 GB, 68719476736 bytes
64 heads, 32 sectors/track, 65536 cylinders
Units = cylinders of 2048 * 512 = 1048576 bytes
Disk identifier: 0x5a3b93b6
Device Boot Start End Blocks Id System
/dev/sdc1 2 65536 67107840 83 Linux
Command (m for help): w
The partition table has been altered!
Calling ioctl() to re-read partition table.
Syncing disks.
[root@host ~]$ fdisk -lu /dev/sdc
Disk /dev/sdc: 68.7 GB, 68719476736 bytes
64 heads, 32 sectors/track, 65536 cylinders, total 134217728 sectors
Units = sectors of 1 * 512 = 512 bytes
Disk identifier: 0x5a3b93b6
Device Boot Start End Blocks Id System
/dev/sdc1 2048 134217727 67107840 83 Linux
[root@host ~]$

c. 최신 버전의 fdisk를 이용한 파티션 정렬

Dos 호환 모드 (-c) 옵션을 비활성화 시키고, 섹터 단위를 사용하도록 옵션을 주어, LBA 주소 2048 에서 파티션을 시작할 수 있도록 할 수 있습니다.

[root@host ~]$ fdisk -c -u /dev/sdb
Device contains neither a valid DOS partition table, nor Sun, SGI or OSF disklabel
Building a new DOS disklabel with disk identifier 0xfae13403.
Changes will remain in memory only, until you decide to write them.
After that, of course, the previous content won't be recoverable.
Warning: invalid flag 0x0000 of partition table 4 will be corrected by w(rite)
Command (m for help): p
Disk /dev/sdb: 160.0 GB, 160041885696 bytes
255 heads, 63 sectors/track, 19457 cylinders, total 312581808 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0xfae13403
Device Boot Start End Blocks Id System
Command (m for help): n
Command action
e extended
p primary partition (1-4)
p
Partition number (1-4): 1
First sector (2048-312581807, default 2048):
Using default value 2048
Last sector, +sectors or +size{K,M,G} (2048-312581807, default 312581807): +10G
Command (m for help): p
Disk /dev/sdb: 160.0 GB, 160041885696 bytes
255 heads, 63 sectors/track, 19457 cylinders, total 312581808 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0xfae13403
Device Boot Start End Blocks Id System
/dev/sdb1 2048 20973567 10485760 83 Linux
Command (m for help): q
[root@host ~]$

  3.2 parted를 이용한 파티셔닝

 

parted 를 이용하여 파티셔닝 정렬을 할 경우에는, label을 GPT로 만들면 첫번째 실린더를 2048 섹터부터 생성해 줍니다.

(parted) mklabel GTP

이 의미는mkpart시에 시작이0이 아니라,1이어야 한다는 것입니다.

(parted) mkpart primary 0 100%
Warning: The resulting partition is not properly aligned for best performance.
Ignore/Cancel?

시작을 0으로 하면, 위와 같이 정렬이 되지 않는다는 메시지가 나오게 됩니다. 그러므로 아래와 같이 1로 설정을 하도록 합니다.

(parted) mkpart primary 1 100%
(parted) print
Model: ATA ST3000DM001-9YN1 (scsi)
Disk /dev/sdb: 3001GB
Sector size (logical/physical): 512B/4096B
Partition Table: gpt
Number Start End Size File system Name Flags
1 1049kB 3001GB 3001GB primary

또는 유닛(unit)을 섹터로 변경을 하여 설정을 하는 것이 더 직관적 입니다.

(parted) mkpart primary 2048s 100%
(parted) print
Model: ATA ST3000DM001-9YN1 (scsi)
Disk /dev/sdb: 5860533168s
Sector size (logical/physical): 512B/4096B
Partition Table: gpt
Number Start End Size File system Name Flags
1 2048s 5860532223s 5860530176s primary

  3.3 파티션 정렬 확인

 

Partition_Offset ÷ Stripe_Unit_Size = 정수
Stripe_Unit_Size ÷ File_Allocation_Unit_Size = 정수

Partition StartingOffset을 1024로 나누었을 경우, 정수의 값이 나온다면 정렬이 잘 되어 있다고 볼 수 있습니다. StartingOffset 값은 섹터 값으로 보시면 되며, 첫번째 파티션의 시작 섹터 값이 1024 의 배수이면 된다는 의미 입니다. 그냥 적정 값으로 2048 섹터에서 시작하면 된다고 외워도 되겠습니다.

또,parted를 이용하여 확인이 가능 합니다.

[root@host ~]$ parted /dev/sda align-check optimal 1
1 aligned
[root@host ~]$