Database/SQL

[DB] PK (Primary Key) ID 길이가 성능에 영향을 미칠까?

pink_salt 2024. 12. 11. 02:33
728x90

 

Primary Key ?

  • PK (Primary Key)는 데이터베이스에서 각 행(row)을 고유하게 식별하기 위한 열(column)이다.
  • PK는 효율적인 데이터 검색, 외래 키 참조, 데이터 무결성 보장을 위해 사용된다.

 

PK 길이가 길어질 때의 문제점은 무엇일까?

  • 저장 공간 증가: 긴 PK는 테이블의 저장 공간을 늘리고, 외래 키로 참조되는 테이블에도 영향을 미친다.
  • 인덱스 크기 증가로 인한 검색 성능 저하: PK는 기본적으로 인덱스로 관리되며, 길이가 길수록 인덱스 크기가 증가하고 검색 속도가 느려진다.
  • 외래 키 테이블의 추가 비용: 외래 키가 긴 PK를 참조할 때, 외래 키 테이블도 더 많은 공간을 차지하게 된다.

 

PK 길이가 길어질 때 인덱스 크기 증가로 인한 검색 성능 저하에 대해서 더 자세히 알아보겠다.

 

 

  • PK는 기본적으로 클러스터드 인덱스(Clustered Index)로 관리된다.
    • MySQL은 PK를 기준으로 유사한 값들이 함께 조회되는 경우가 많다는 점에서 착안해, PK가 유사한 레코드들끼리 묶어서 저장한다. 유사한 것들을 묶는 것을 클러스터링이라고 하는데, 그래서 일반적으로 PK는 클러스터 인덱스(Clustered Index)라고도 불리며, 그 외의 일반적인 인덱스는 논클러스터 인덱스로 불린다. 그리고 이러한 클러스터링 특성 때문에 레코드의 저장이나 PK의 변경은 처리 속도가 느리다.(참고)
    • 그래서 클러스터드 인덱스는 테이블의 데이터가 PK 값 순서대로 정렬되며, 인덱스 노드와 데이터 페이지가 하나의 구조로 관리된다.
    • PK를 기반으로 자동 생성된 인덱스는 테이블의 데이터 정렬 및 검색에 사용되며, 모든 조회, 삽입, 삭제, 갱신 작업에 영향을 미친다.

 

 

Index란?

DB에서 index 는 단어 그대로 색인자의 역할을 한다. 보통 한 개의 테이블에는 수십 개의 column이 존재하며, MySQL은 데이터 조회 시 가장 첫 번째 column부터 차례대로 검색하기 시작한다. 만약 index 가 존재하지 않는다면 데이터를 조회할 때 언제나 테이블 전체를 Full scan 해야 하는데 이는 비용과 성능 면에서 매우 비효율적이다.

하지만 데이터가 Create, Update, Delete 되는 경우 Index도 함께 수정돼야 하기 때문에 index가 없는 경우보단 효율이 떨어진다. 즉 Read 성능을 살리고 CUD를 성능을 희생한다고 볼 수 있다. 따라서 잦은 수정이 필요한 테이블의 경우는 오히려 index를 활용하지 않는게 더 이로울 수 있다.(참고)

 

Page란?

Page(페이지)는 데이터베이스에서 데이터를 저장하는 기본 단위이다. MySQL에서는 innodb_page_size 시스템 변수로 4KB ~ 64 KB 사이 값 선택 가능하고, 기본 값은 16KB이다. B+Tree의 각 노드는 페이지로 구성되며, 각 페이지는 인덱스 키와 자식 노드 주소를 포함합니다. (B+Tree는 아래에서 더 자세하게 살펴보겠다.)

인덱스 페이지 내 키 값 구성 예시를 보면,

  • 인덱스 페이지 크기: 16KB (16 * 1024 = 16,384)
  • 인덱스 키 값: 28 Byte
    • 인덱스 키 영역: 16 Byte
    • 자식 노드 주소 영역: 12 Byte
    • 한 키 값의 전체 크기 = 16 + 12 = 28 Byte
  • 한 페이지에 저장 가능한 키 값 수 : 16,384/28 = 585
    • 한 페이지에 약 585개의 키 값을 저장할 수 있다.

 

키 값 크기와 페이지 효율 비교하면 키 값이 작을수록 페이지에 저장 가능한 키 수가 증가하는 것을 볼 수 있다.

키 값 크기 16KB 페이지 내 저장 가능 키 수
20 bytes 16,384/20 =
28 bytes 16,384/28 =
36 bytes 16,384/36 = 455
64 bytes 16,384/64 = 256

 

그렇기에 페이지 크기와 키 값 크기의 관계를 보면,

  • 키 값 크기가 커질수록 한 페이지에 저장할 수 있는 키 값 수가 줄어든다.
  • 페이지 크기가 클수록 한 페이지에 더 많은 키 값을 저장할 수 있어 트리의 높이가 낮아진다.

 

그러면 페이지 크기가 크면 무조건 좋은가에 대한 생각이 들 수 있지만, 페이지 크기가 클수록 트리의 높이가 줄어들지만, 큰 페이지는 메모리 사용량과 디스크 I/O 비용이 증가할 수 있기 때문에 주의하여야 한다.

  • 디스크 I/O 비용 증가 : 데이터베이스는 필요한 데이터를 페이지 단위로 읽어온다. 그래서 페이지 크기가 크면 요청한 데이터 외에 불필요한 데이터도 함께 로드하게 된다.
    • 예로, 4KB 페이지 크기에서 100바이트의 데이터를 읽으면 약 4KB를 로드하지만, 64KB 페이지 크기에서는 동일한 100바이트의 데이터를 읽기 위해 64KB를 로드하게 된다. 페이지 크기를 너무 크게 잡을 시, 디스크에서 더 많은 데이터를 읽으므로 I/O 비용이 증가한다.
    • 또한, 페이지가 클수록 데이터를 수정할 때 변경된 작은 데이터(예: 100바이트)를 반영하기 위해 전체 페이지를 다시 저장해야 한다.
    • 큰 페이지 크기는 특정 데이터를 찾기 위해 더 많은 데이터를 탐색해야 하므로 랜덤 I/O 비용을 증가시킨다.

 

Page Split은 인덱스에 새로운 데이터가 삽입될 때 기존 페이지에 빈 공간이 부족하면 발생한다.
새로운 페이지를 생성하여 기존 데이터를 분리하고, 삽입 데이터를 적절한 위치에 배치한다.

(문제점) Page Split은 추가적인 디스크 I/O를 유발하며 성능 저하를 초래할 수 있다. 특히, 랜덤하게 생성되는 UUID는 Page Split이 빈번하게 발생한다.

 

 

MySQL 은 PK 를 설정하면 해당 값을 index 로 잡아 데이터를 B+tree 구조로 저장한다.

InnoDB 클러스터링 인덱스도 사실상 B-Tree 구조로 관리된다. 

하지만 클러스터링 인덱스는 테이블의 데이터 자체를 포함하고 있으며, 다음과 같은 특징이 있다.

데이터와 인덱스의 결합

  • 클러스터링 인덱스는 B-Tree 구조로 정렬되어 있지만, 인덱스의 각 노드(리프 노드)는 테이블의 실제 레코드 자체를 포함 .
  • 클러스터링 인덱스를 통해 데이터를 찾으면, 바로 해당 레코드의 모든 데이터를 가져올 수 있음. 
  • 이를 인덱스-오거나이즈드 테이블(Index-Organized Table) 이라고도 부름

B+Tree 구조

  • 클러스터링 인덱스도 실제로는 B+Tree 구조에 기반하고 있음
  • 기본 키를 기준으로 데이터를 정렬하고, B-Tree 구조의 각 노드가 테이블의 레코드를 가리키는 것이 아니라 레코드를 포함하고 있는 형태임
  • 이 때문에 클러스터링 인덱스도 B+Tree 인덱스의 일종으로 간주할 수 있음.

(참고)

 

B+Tree?

B+Tree는 데이터베이스에서 인덱스를 관리하기 위해 사용되는 트리 자료 구조다. 모든 리프 노드가 동일한 깊이에 있으며, 리프 노드에는 실제 데이터의 위치(포인터)가 저장된다.

  • 각 노드는 키 값(PK)을 기준으로 정렬.
  • 리프 노드(Leaf Node)는 실제 데이터 행(Row)에 대한 포인터를 포함.
  • 비 리프 노드(Non-leaf Node)는 탐색 경로를 제공하며, 리프 노드로 이어짐. 

 

B+Tree의 구조

- 루트 노드: 트리의 최상단 노드. 검색 시작점.
- 중간 노드: 탐색 경로를 제공.
- 리프 노드: 실제 데이터 위치를 가리키는 포인터를 포함.


          [50]
         /    \
      [25]    [75]
     /   \    /   \
[1~24] [26~49] [51~74] [76~100]

 

  • 루트 노드: [50]
  • 중간 노드: [25], [75]
  • 리프 노드: [1~24], [26~49], [51~74], [76~100]

 

 

PK가 인덱스로 설정되면

  • 데이터는 PK 값 기준으로 정렬되며, 빠른 검색이 가능하다.
  • 키 값이 길어질수록 트리의 높이(Depth)가 증가하여 성능에 영향을 미친다.

 

그렇기 때문에 PK 가 길어지면, 아래와 같은 현상이 발생한다.

 인덱스 키 값 크기 증가

→ 인덱스 페이지에 담을 수 있는 인덱스 키 값 갯수 감소

→ B-Tree 깊이 증가

→ 디스크 읽기 증가

(참고)

 

예시 1) PK: bigint (Auto-increment)

  • PK 값이 짧고 순차적으로 증가하기 때문에 트리의 균형이 유지되고 노드에 더 많은 키 값을 저장할 수 있음.
    • 노드에 많은 키 값을 저장할 수 있어 트리의 높이가 낮음
  • 트리의 높이가 낮아 탐색 속도가 빠름.
    • 디스크 I/O가 최소화되어 검색 성능이 우수.

 

 

예시 2) PK: UUID

  • PK 값이 길고 랜덤하게 생성되므로 트리의 균형을 유지하기 어렵고, 노드에 저장 가능한 키 값 수가 줄어듦.
    • 노드 당 저장할 수 있는 키 값 수가 줄어들어 트리의 높이가 증가
  • 트리의 높이가 증가하여 탐색 속도가 느려짐.
    • 디스크 I/O가 증가하여 검색 성능이 저하.

 

 

 

[PK 선택 시 고려해야 할 사항]

1. 성능
2. 분산 시스템

-> UUID를 사용해야 하는 경우:
   데이터가 분산 환경에서 생성될 때.
   각 시스템 간 데이터 충돌을 방지해야 할 때.
   - 장점: 전역 고유성 보장.
   - 단점: 길이가 길고, 랜덤한 값이기 때문에 인덱스 정렬 효율이 떨어짐.


-> Auto-increment가 적합한 경우:
   단일 데이터베이스에서 간단한 고유성이 필요할 때.
   - 장점: 짧고 정렬된 값으로 성능이 좋음.
   - 단점: 분산 시스템에서 고유성을 보장하기 어려움.
728x90
반응형