옛날에는 단순 코드 잘 작성하는 것에 급급하는 주니어 개발자였다면,
현재는 나에게 기회나 설명은 전~~~혀 오지 않지만 시스템 디자인에 자꾸 눈이 가는 주니어 개발자다
물론 결론은 동일하게 주니어 개발자다!
말하고자 하는 내용은 내가 아직도 핫바리 주니어라는 것이 아니라...
지난번에 2023년 인프런에서 개최한 스프링 컨퍼런스에 참여했었는데
같이 참석한 지인과 함께 개인용 학습 프로젝트에 대해 이야기하고 있었는데
T사의 면접에서 uuid 정렬에 관련해서 질문을 받았다고 했다.
처음에 나의 생각은
"uuid를 왜 정렬하지?" create_date를 별도로 넣어서 사용하면 되는거 아닌가? ㅎㅎ
머... 머기업에서 쓰라는데 이유가 있겠지~ ㅋㅋㄹㅃㅃ ^^
이러한 생각을 가지고 있었다.
그리고 최근들어 시스템디자인에 관심이 많아졌느데 unique한 primary key를 생성하는데 중요한 이슈사항이 있음을 발견했다.
이번 내용은 이와 관련해서 내가 찾아보고 학습한 내용을 정리할 것이다.
1. 다양한 PK(Primary Key) 생성 전략
우선 여러가지 PK 생성 전략을 살펴보도록 하겠다.
1-1. auto_increment으로 PK생성하기
데이터베이스에서 PK를 생성시 자동으로 counting을 해주는 방법이다.
(TMI - 필자가 대덕 인재 개발원에서 처음으로 프로젝트를 만들면서 배울 때 썼음 ㅎ)
가령, T_CUST(고객) 테이블이 있다고 하자.
새로운 고객 정보를 생성할 때 마다 PK가 1, 2, 3, ... 하는 방식으로 1씩 증가하는 것이다.
단순하게 Long 으로 auto_increment를 할 때를 생각해보자.
java언어를 기준으로 한다면
long 타입은 8byte이다.
1비트는 2가지 경우의 수를 가졌고, 1바이트는 8비트다.
8바이트는 그러면 64비트가 되는 것이다.
64비트의 정수형 최대값은 찾아보니 9,223,372,036,854,775,807 이라고한다.
1-2. UUID (Universally unique identifier)로 PK 생성하기
어쩌면 가장 흔하게 볼 수 있는 방식으로, 겹칠 확률을 계산해보면 다음과 같다.
인간이 매년 하늘에서 떨어지는 운석에 맞을 확률이 0.00000000006 이라고 한다. 이 확률은 수십조의 uuid(v4)를 생성할 때 단 하나의 중복된 uuid가 생성될 확률과 같다.
또한, 매 초 10억개의 uuid를 100년에 걸쳐서 생성할 때 단 하나의 uuid가 중복될 확률은 50%이다.
출처 : 위키피디아 - Universally unique identifier
UUID는 16 옥텟 (128비트)의 수이다. 표준 형식에서 UUID는 32개의 십육진수로 표현되며 총 36개 문자(32개 문자와 4개의 하이픈)로 된 8-4-4-4-12라는 5개의 그룹을 하이픈으로 구분한다.
1-3. 트위터 스노우 플레이크 ID (Twitter Snowflake ID)
트위터에서 64비트의 형식의 pk를 생성하기 위해 고안한 pk 생성 방법이라고 한다.
Sign Bit : 음수 양수 구별
Timestamp ID : 실제 시간 범위
Worker ID : 근로자가 가진 ID
Local Counter : 시퀀스 number
그림을 보면 41비트의 timestamp로 PK 를 생성시에 최대 69.7년을 겹치지 않는다고 한다.
그렇다는 말은 주변의 많은 사람이 죽고 69.7년 뒤에는 나는 죽을때쯤에 겹치는걸 볼 수 있다는 말이다!
(그때쯤이면 정말 죽겠는데...?)
그래! 열심히 정리하자!
2. 왜 PK(Primary Key)는 숫자(Numeric)으로 생성해야 좋나?
근데 PK를 숫자로 생성하는것에 장점이 있다.
이에 대해서는 chatGPT에게 물어보았다.
공대키메라 : why making pk as numeric is better?
chatGPT : 솰라라라라~ 아힝흥행 ㅎㅎ...
축약하면 다음과 같다.
- 성능 (Performance) : 메모리가 적고, 문자열보다 비교가 빠르다(조인 성능도 그러면 상승)
- 자동 증가 (Auto-Increment) : 자동증가 사용시 기본키 고유성 보장이 용이
- 비서술적 (Non-Descriptive) : 식별의 목적이기 때문에 기본키는 의미가 없어야 함.
- 확장성(Scalability) : 긴 정수같은 데이터 유형으로 생성할 수 있는 수가 매우 길음.
- 표준화(Standardization) : 텍스트 인코딩, 대소문자 구분, 공백 등 문자열 키에서 발생하는 문제가 사라짐.
- 호환성(Compatibility) : 외래 키 참조가 숫자 ID로 처리하는 것이 더 쉽고, 다른 시스템과 플랫폼 간에 더 호환성이 있음
물론 이것이 항상 좋은 선택은 아니며,
자연키 혹은 복합키의 경우가 더 나은 선택지인 경우도 존재한다는 설명까지 덧붙였다. (리얼 쩐다...)
그러면 인덱스와 관련해서는 어떤 장점이 있을까?
이에 대해 알아보니 대부분의 관계형 데이터베이스는 pk를 정의시 자동으로 해당 pk에 대한 인덱스를 생성한다고 한다.
솔직히 필자는 해당 정보를 어디서 또 검색해서 찾을지 몰라서 GPT에게 질문을 다시 해보았다. (지송...ㅠㅠ)
해당 사항이 궁금하면 밑의 글을 읽어보면 된다.
나의 질문
pk에 uuid 대신 numeric이 좋다는데 index와 관련해서는 왜 좋은지 모르겠어.
GPT
기본키(primary key, PK)로 숫자형(numeric) ID를 사용하는 것이 UUID보다 인덱싱(indexing) 측면에서 더 나은 이유를 설명하겠습니다. 인덱싱의 효율성은 데이터베이스의 성능에 큰 영향을 미칩니다. 다음은 숫자형 ID가 인덱싱에서 더 나은 이유입니다:
- 공간 효율성: 숫자형 ID는 UUID보다 공간을 적게 차지합니다. 숫자형 ID는 일반적으로 4바이트(32비트 정수) 또는 8바이트(64비트 정수)인 반면, UUID는 16바이트입니다. 인덱스 크기가 작으면 디스크 공간을 적게 사용하고, 메모리에 더 많은 인덱스를 캐싱할 수 있어 성능이 향상됩니다.
- 순차성 및 삽입 성능: 숫자형 ID는 보통 자동 증가(auto-increment)되므로 새로운 레코드가 인덱스의 마지막 부분에 삽입됩니다. 이는 삽입 작업을 더 빠르고 효율적으로 만듭니다. 반면, UUID는 무작위성(randomness) 때문에 새 데이터가 인덱스의 어느 곳에나 삽입될 수 있으며, 이는 인덱스 재구성(reorganization)이나 페이지 분할(page splits)을 더 자주 일으킬 수 있습니다.
- 검색 및 비교 성능: 숫자형 ID는 크기가 작고 단순한 비교 연산에 최적화되어 있어 검색 및 정렬 작업에서 더 빠릅니다. UUID는 길고 복잡한 비교 연산을 필요로 하며, 이는 특히 대량의 데이터를 다룰 때 성능 저하를 일으킬 수 있습니다.
- 캐시 및 I/O 효율성: 인덱스의 작은 크기는 디스크 I/O를 줄이고 메모리 내 캐시 효율성을 증가시킵니다. 이는 데이터베이스 성능에 중요한 요소입니다.
- 범위 기반 쿼리 최적화: 숫자형 ID는 범위(range) 쿼리에 적합합니다. 예를 들어, 특정 ID 범위에 해당하는 레코드를 빠르게 검색할 수 있습니다. UUID는 이러한 유형의 쿼리에 최적화되어 있지 않습니다.
그러나 UUID가 제공하는 유니크함과 분산 시스템에서의 장점도 있기 때문에, 결국 선택은 어플리케이션의 요구사항과 특정 사용 사례에 달려 있습니다. 예를 들어, 확장성이 중요하고 시스템이 여러 데이터베이스 노드에서 작동해야 하는 경우 UUID가 더 적합할 수 있습니다.
여기서 모르는 용어에 대해서 나와서 이에 대해 정보를 찾아서 정리한다.
* 인덱스 재구성(Index Rebuild)
오래된 인덱스를 지우고 새로운 인덱스로 대체하는것을 의미한다.
* 페이지 분할(page split)
우선 page란 SQL SERVER의 기본 I/O 단위로써, 하나의 Page에 8KB의 크기를 가지는 데이터는 Page에 저장된다.
이 페이지가 데이터베이스 내에서 꽉 차서 별도의 두 페이지로 나누어야 할 때 페이지 분할이 발생한다.
이것은 새 데이터가 테이블에 추가되고 기존 페이지에 저장할 공간이 더 이상 없을 때 발생할 수 있다.
그래서 index의 측면에서도 pk를 numeric하게 만드는 것이 좋다는 것이다!
4. 요구사항에 따른 ID 생성 전략 선택
그렇다면 우리는 어떤 방식을 선택해야하나?
우선 UUID를 통해 정렬 시에 사라질 이점을 바로 위에서 확인했다. (탈락!)
만약에 당신이 대규모 분산 처리 시스템에서 PK를 생성한다고 가정해보자.
자동 증가 전략을 PK로 선택시에는 잘못된 선택이 될 것이다.
예를 들어서 분산 서버로 load balancer에서 서버 A와 서버 B로 client의 요청을 분산한다고 가정하다.
PK는 겹치면 안되기에 서버 A는 2씩 수를 증가시켜서 홀수로, B는 짝수로 pk를 생성한다고 해보자.
그런데 이런! 갑자기 서버가 하나 더 늘었네~ (탈락!)
그러면 현재 우리에게 남은 선택지는 메모리도 적게 잡아먹고, 필요하면 정렬도 하고 join시에 용이한 Twitter Snowflake가 남아있다.
정리하자면, 서버가 여러개 있는, 그리고 비교적 적은 메모리로, 2번째 주제에서 다룬 여러가지 장점을 고려해 봤을 때 Twitter Snowflake가 적절하다는 판단이다.
5. TSID (Time Sort ID)?
우연히 접한 ID 생성 룰인데, 이것도 twitter snowflake에 영감을 얻어서 생성했다고 한다.
Time-Sorted Unique Identifier를 생성하는 java library로, Twitter의 snowflake와 ULID spec을 합쳐 만들었다고 한다.
이번 시간에 어떤것이 적절한 ID geneterating 전략인지 알아보았다.
이러한 내용은 시스템 디자인에 대한 정보를 찾으면 매우 쉽게 접할 수 있는것이며,
필자는 매우 간단하게 정리하였다.
더 많은 정보를 원한다면 우리 인도 공대 형님들의 영상을 youtube에서 쉽게 접할 수 있으니 언제든지 찾아보길 바란다.
출처
https://ko.wikipedia.org/wiki/2147483647
https://en.wikipedia.org/wiki/Universally_unique_identifier
https://www.youtube.com/watch?v=V3btjD1Jv6Y&ab_channel=CrushingTechInterview
https://en.wikipedia.org/wiki/Snowflake_ID
https://www.geeksforgeeks.org/vector-clocks-in-distributed-systems/