UUID와 B-Tree 인덱스와의 관계
UUID는 버전과 관계 없이 모두 랜덤한 값을 생성한다. 물론 UUID 버전 1에선 짧은 시간 동안 단조 증가하는 값을 생성하지만 7분 간격으로 리셋되기 때문에 계속 랜던한 값이 생성된다. 그리고 UUID는 128비트, 즉 16바이트 이진값으로 구성되어 있지만 일반적으로 가독성을 위해 16진수 문자열로 변환하여 DBMS에 저장하는 편이다. 결과적으로 16진수 문자열을 저장하기 위해 char(32) 혹은 binary(16) 타입을 사용하고 있는 것이다.
Tip)
국제인터넷 표준화 기구인 IETF에서 정식 표준으로 채택된 UUID는 5개의 버전이 있다. 그 중 버전 1과 버전 4를 자주 쓴다고 한다. 그래서 1과 4 버전의 특징만 간단하게 말하자면 UUID 1 버전은 Timestamp 기반의 UUID를 생성하고 별도의 유니크한 값 입력없이 생성이 가능하다. 그리고 UUID 4 버전은 완전한 랜덤 UUID를 생성한다고 한다. 그 외 자세한 내용은 공식문서를 참고 바란다.
그런데, b-tree 인덱스는 길이가 짧고 정렬된 값이 순차적으로 저장될 때 효과적으로 작동할 수 있다. 반면 UUID는 랜던한 값이 생성되고 길이까지 32바이트를 사용하기 때문에 b-tree 인덱스가 효과적으로 사용되기 어렵다. 심지어 mysql 서버에선 인덱스의 변경을 체인지 버퍼란 공간을 이용해 빠르게 처리될 수 있는데 인덱스가 유니크 제약을 가진 경우에는 체인지 버퍼를 사용할 수도 없다. 즉 랜덤한 UUID 값을 저장하는 컬럼이 유니크 제약까지 가지게 되면서 성능을 훨씬 더 안좋게 만들 가능성이 높아진다. 그리고 mysql 서버에서는 UUID 값이 PK 컬럼으로 사용되는 경우 모든 세컨더리 인덱스는 PK를 포함하게 된다. 결국 모든 인덱스의 저장 공간 크기가 커지고 정렬되지 않은 랜덤한 값의 PK 악영향이 모든 세컨더리 인덱스에도 영향을 미치면서 성능이 더 악화된다.
일반적으로 mysql 서버에 저장되는 데이터는 최근 데이터가 쿼리에 필요한 경우가 많다. 그런데 UUID는 매번 랜덤한 값을 생성하기 때문에 최근 데이터가 큰 값을 가지거나 작은 값을 가진다는 보장이 전혀 없다. 때문에 어제 데이터를 읽는 경우라도 UUID 컬럼 값의 prefix가 0부터 f까지 모든 범위의 값을 가질 수 있게 된다. 즉 UUID 컬럼을 인덱스로 사용하는 경우 시점에 관계없이 전체 인덱스가 워킹셋이 되고 이는 인덱스 크기가 10GB인 경우 메모리도 그만큼 있어야 빠른 쿼리 성능을 낼 수 있게 되는 것이다.
Tip)
워킹셋은 DB 성능 최적화 및 메모리 관리와 관련된 중요한 개념으로, 쿼리 처리를 위해 필요한 데이터의 부분 집합을 의미. 예를 들어 DB에서 쿼리를 실행할 때, 모든 데이터를 메모리에 로드할 수 없기 때문에 쿼리 실행에 자주 사용되는 데이터를 주로 메모리에 상주시켜 성능을 높이기 위한 전략을 말한다. 즉 워킹셋이 충분히 작아서 메모리에 상주할 수 있다면, 쿼리 성능이 향상됩니다. 반대로, 워킹셋이 너무 크면 디스크 I/O가 자주 발생하여 성능이 저하될 수 있다.
다시 정리하면, mysql 서버에서 인덱스는 경우에 따라 용량이 클 수도 작을 수도 있다. 하지만 인덱스의 용량이 너무 크더라도 꼭 필요한 부분만 메모리로 읽어서 쿼리를 처리할 수 있다. 이를 워킹셋이라 한다. 결과적으로 인덱스가 아무리 커도 워킹셋의 크기가 작다면 mysql 서버는 가용한 메모리가 적더라도 충분히 빠른 쿼리 처리 성능을 낼 수가 있게 된다. 반면 인덱스 전체 데이터가 워킹셋인 경우라면 DBMS 서버는 매우 많은 메모리가 필요해질 것이고 결국 디스크 읽기 작업이 필요해지면 DISK I/O가 증가하면서 쿼리 성능이 저하된다
결론
외부에 노출되지도 않고 시스템 내부적으로만 사용되는 PK 또는 식별자 값을 위해 UUID를 사용하면 아무런 혜택없이 자원 낭비만 초래하는 모델링이 될 가능성이 있다. 만약 UUID의 수많은 단점에도 불구하고 꼭 UUID를 사용해야 한다면, 테이블의 PK는 AutoIncrement를 사용하고 해당 테이블의 UUID 값을 저장하는 컬럼을 별도로 만들어서 유니크 인덱스를 생성하는 방법을 권장한다. 이는 테이블의 크기와 성능 효율성을 최대한 유지하면서 외부에 노출되는 랜덤 값의 기밀성도 함께 유지할 수 있게 된다.
reference
Real MySQL - 이성욱님, 백은빈님
'Study > [무럭무럭 시즌 2] Real MySQL 8.0' 카테고리의 다른 글
무럭무럭 STUDY - SELECT ... FOR UPDATE (MySQL) (3) | 2024.10.10 |
---|---|
무럭무럭 STUDY - Index를 타지 않고 Table Full-Scan 처리? (MySQL) (0) | 2024.10.06 |
무럭무럭 STUDY - PREPARED STATEMENT (MySQL) (0) | 2024.09.28 |
무럭무럭 STUDY - LEFT JOIN 주의사항 그리고 튜닝 (MySQL) (0) | 2024.09.28 |
무럭무럭 STUDY - 끄적끄적 (MySQL) (0) | 2024.09.28 |