본문 바로가기
Database

관계형 데이터베이스의 기본키로 UUID Type을 사용한다면 고려사항

by oncerun 2023. 2. 3.
반응형

 

데이터베이스의 기본 키의 유형을 결정하는 것은 장기적으로 매우 중요한 요소를 갖는다고 생각한다.

 

나는 기본 키 유형을 선택해야 할 때 보통 자동으로 증분 되는 자연수를 선택한다.

 

이는 가장 적은 공간을 사용하면서 unique 특성을 잘 지킬 수 있게 하는 아주 쉬운 충분조건이라고 생각하기 때문이다.

 

그러던 와중 미리 데이터베이스와 Node.js 기반의 백엔드 서버로 구성된 한 애플리케이션을 봐야 하는 일이 생겼는데, 이때 기본 키의 타입이 UUID인 것을 발견했다.

 

이를 설계한 사람은 왜 UUID Type을 사용해야 했는지 의도를 좀 알려고 관련 정보를 찾아본다.

 

우선 UUID 타입의 간단한 정의가 필요하다.

 

Universally Unique Identifier이라는 약자로 표준 128 bit pseudo-random sequence이다.

 

식별자의 고유성을 보장하는 single centralized system 필요 없이 생성될 수 있는 고유 식별자라고 한다.

 

RFC 4122 스펙에는 5가지의 표준화된 UUID 버전을 정의하였고, 이를 프로그래밍 언어나 데이터베이스에서 이를 구현하여 사용하고 있다.

  1. MySQL 버전에 따라 다르지만 mysql function 중에 UUID()는 version 1을 사용한다.
  2. PostgreSQL은 UUID dataType을 지원하며 함수와 모듈로 다양한 UUID version을 포함할 수 있다.
  3. Java의 UUID.randomUUID() 함수는 version 4를 사용한다.

그렇다면 UUID 특성 중에 어떠한 장점에 의해 데이터베이스의 기본 키로 사용되는 근거가 있을까?

 

첫 번째로는 고유성을 보장하기 위한 시스템이 필요하지 않고 애플리케이션에서 쉽게 생성할 수 있다는 것이다.

 서비스에 따라 식별자의 고유성을 반드시 별도의 시스템으로 확인해야 하는 서비스가 존재할 수 있다. 이 경우 시스템은 식별자의 고유성을 확인하고 만들어 줄 것이다.

그렇지 않고 우리가 고유 식별자의 생성을 데이터베이스에 위임하는 경우도 있다.

sequence function을 만들어 놓는 것이다. 이후 식별자가 필요할 때 시퀀스를 조회하여 다음 증분된 값을 가져와 사용할 수 도 있을 것이다.

 

두 번째로는 식별자의 충돌가능성이 현저히 적다는 것이다.

 

세 번째로는 고유 식별자의 값이 랜덤 하다는 것이다.

 이는 보안상의 이유로 적합할 수 있다. 악의적인 사용자가 고유 식별자를 유추할 수 없기 때문에 데이터를 UI에 전달하는 것에 대하여 안전성을 챙길 수 있다.

 

이를 보면 UUID를 사용하여 데이터베이스 기본키를 사용하는 것이 아무런 문제가 없다고 생각할 수 있다.

하지만 랜덤 한 UUID를 기본 키로 사용하게 되면 성능적인 측면에서 트레이드오프가 발생할 수 있다는 것을 알고 있어야 하며 이를 발생시키는 다양한 원인에 대해 알고 사용해야 한다.

 

UUID를 데이터베이스 기본키로 사용하였을 때 떠안게 되는 bad idea는 무엇일까?

 

 첫 번째는 UUID는 거대하다는 것이다.

 

일반적으로 정수를 사용하고 서비스가 잘돼서 데이터베이스의 데이터가 충분히 넘쳐흐른다고 가정했을 때 4 bytes (약 21억) 개의 레코드가 부족하다면 보통 8byte의 datatype을 통해 주 식별자 키를 사용해도 충분할 것이다.

그런데 UUID는 16 bytes를 사용한다. 매번 하나의 레코드의 주 식별자 자리를 위해 16bytes를 필요로 한다는 것을 의미한다.

 

이는 다른 레코드와 연관된 Foreign Key에서도 동일한 공간이 필요하다는 것을 의미합니다.

이 문제는 단순히 데이터베이스 저장공간에 한정된 문제가 아닐 수도 있습니다.

 

우리는 데이터베이스를 조회하는 작업을 상당히 많이 합니다. 그리고 주 식별자키를 포함한 데이터를 클라이언트에게 전달하는 작업을 진행할 수도 있습니다.

 

만약 이러한 데이터가 많은 경우 이는 네트워크의 트래픽과 대역폭에 대해 영향을 미칠 수 있습니다. 적은 데이터양이라면 그 차이가 미묘할 테지만 데이터가 매우 커지는 경우 단지 몇 바이트 차이가 아주 커다란 성능 문제를 야기할 수 있습니다.

 

 두 번째로는 대부분의 데이터베이스의 기본 키는 B+ Tree index가 적용된다는 점입니다.

 

조회속도나 join의 속도를 향상하기 위해 기본 키로 지정되는 column은 인덱스가 기본적으로 적용되는 경우가 많습니다. 인덱스는 별도의 공간에 data를 정렬시키는 작업을 진행합니다.

 

여기서 랜덤 한 UUID의 값을 B+Tree로 인덱싱 하는 데에는 정말 많은 문제가 포함되어 있습니다.

 

index page는 테이블에서 데이터를 효율적으로 검색하고 검색할 수 있는 데이터 구조입니다.

 

이러한 index page의 크기는 8kb, 16kb 등 데이터베이스마다 다를 수 있습니다.

 

UUID를 기본키로 사용하는 경우 값이 랜덤 하게 인덱스 페이지에 전달되기 때문에 인덱스 페이지에 값이 채워지는 비율자체가 낮을 수밖에 없습니다.

 

그렇기에 8kb, 16kb 페이지에는 아주 적은 몇 가지 요소들이 저장될 것이며 버퍼 풀에 인덱스 페이지가 캐싱될 때 디스크와 메모리의 낭비가 발생합니다.

 

또한 B+Tree 인덱스는 equidistant tree structure를 유지하기 위해 재조정되는 과정이 필요한고 랜덤 한 값은 더 많은 인덱스 페이지를 분할하고 합병하는데 이는 랜덤한 키 값에 대해 사전에 정의된 순서가 없기 때문입니다.

 

만약 여러분들이 SQL Server 혹은 MYSQL을 사용한다면 이는 더 심각한 문제를 발생시킬 수 있습니다. 기본적으로 테이블에 대해 clustered index를 적용하기 때문입니다.

 

 

B+Tree 인덱스는 자체적으로 균형을 이루기 때문에 값이 단조롭게 증가하는 기본 키 열을 선택하는 것이 좋습니다.

 

리프 노드에 저장된 인덱스 페이지에는 여러 레코드가 저장되는데, 이를 차례대로 추가하면 페이지를 채우는 비율이 매우 높아집니다.

 

따라서 비율이 높기 때문에 페이지의 수를 줄일 수 있고 이는 조회 시 탐색해야 하는 범위가 적어질 수 있다는 것을 의미합니다.

 

만약 여기서 UUID를 사용하게 되면 클러스터 인덱스의 리프 노드에는 데이터도 포함되어 기본 키로 정렬되어 있는 상태이기 때문에 페이지가 채워지는 비율이 적어질 수밖에 없고

 

이로 인해 페이지 수가 많아지고 조회나 범위 조회 시 탐색해야 할 페이지 수가 많아지기 때문에 성능적으로 악영향을 준다는 것입니다.

 

만약 클러스터 인덱스와 보조 인덱스(논 클러스터 인덱스)를 같이 설정했다면 문제는 더 심각해질 수 있습니다.

 

보조 인덱스는 리프 노드에 레코드 포인터를 저장하는데 이 레코드 포인터의 값이 클러스터 인덱스의 기본 키를 저장합니다.

 

그렇기 때문에 인덱스를 저장하는 총공간도 무거워지며 페이지 증가로 성능도 악화되는 결과를 초래합니다.

 

만약 UUID 값을 주 식별자로 사용해야 한다면 TSID ( tiem-sorted unique identifier)를 고려할 수 있습니다.

https://github.com/f4b6a3/tsid-creator

 

GitHub - f4b6a3/tsid-creator: A Java library for generating Time-Sorted Unique Identifiers (TSID).

A Java library for generating Time-Sorted Unique Identifiers (TSID). - GitHub - f4b6a3/tsid-creator: A Java library for generating Time-Sorted Unique Identifiers (TSID).

github.com

TSID는 시간순으로 정렬된 64비트의 숫자입니다. 따라서 데이터베이스에 저장하는 가장 좋은 타입은 bigint 타입으로 사용하는 것을 추천드립니다.

 

마무리

자동으로 증분 되지 않는 UUID를 추천하지 않는 여러 가지 이유를 살펴보았습니다.

 

UUID를 기본 키로 지정하는 순간 디스크와 메모리에 많은 공간을 차지하게 되며 조회의 성능에서도 더 많은 데이터 페이지 기반으로 하기에 성능 저하를 유발할 수 있다는 것.

 

추가적인 용량은 네트워크 트래픽까지 영향을 가거나 애플리케이션 메모리에도 무리를 줄 수 있다는 것까지 알아보았습니다.

 

다만 추천하지 않는 이유지 사용하지 말라는 이야기는 아닙니다.

 

실제 UUID를 NOSQL에서 사용한다면 어떨까요?

 

그리 큰 성능의 문제는 발생하지 않을 수 있습니다.

 

그러면서 여러 가지 UUID의 이점도 챙긴다면 그 또한 나쁘지 않을 수 있습니다.

 

 

 

https://en.wikipedia.org/wiki/Universally_unique_identifier#Versions

 

Universally unique identifier - Wikipedia

From Wikipedia, the free encyclopedia Label used for information in computer systems A universally unique identifier (UUID) is a 128-bit label used for information in computer systems. The term globally unique identifier (GUID) is also used.[1] When genera

en.wikipedia.org

https://vladmihalcea.com/

 

Home - Vlad Mihalcea

Hi! My name is Vlad Mihalcea, and I’m a Java Champion. I wrote the High-Performance Java Persistence book, which became one of the best-selling Java books on Amazon.I'm currently developing the amazing Hypersistence Optimizer, and in my free time, I d

vladmihalcea.com

 

반응형

'Database' 카테고리의 다른 글

DataBase 선택 가이드 (짧)  (1) 2023.06.15
트랜잭션 격리 수준, 락, MVCC  (0) 2022.09.16
데이터베이스 특수문자 패스워드 및 인덱스  (0) 2021.10.28
그룹 함수 (1)  (0) 2021.05.23
집합 연산자  (0) 2021.05.23

댓글