때는 사이드 프로젝트에서 파일 업로드 API를 구현하기 위해 attachment 테이블을 설계하고 있을 때였다.
다른 테이블들의 경우엔 PK를 auto_increment를 통해 자동으로 고유한 값을 갖도록 했었는데, File Upload의 경우엔 직접 pk키의 값을 지정하자는 얘기를 선배에게 들었다.
이유를 알려면 File Upload API의 플로우를 대충 알아야 한다.
보통은 파일을 저장하기 위한 Storage로 S3를 구성하게 될 것이다.
그리고 upload된 file의 URL, MIME-TYPE, 용량, 업로드 한 유저의 ID등등의 정보를 저장하기 위해 DB의 관련 테이블에 로우를 생성할 것이다.
우리의 경우엔 file의 path를 db에 생성된 파일의 고유 ID값으로 주기로 했고, PK를 생성하는 가장 일반적인 방식인 auto_increment 전략을 채택했다면 대략 플로우는 이렇게 될 것이다.
DB Insert --> 생성된 ID 파싱 --> S3 Upload
굉장히 비효율적이라고 볼 수 있다.
만약 우리가 PK키의 값을 직접 생성한다면
UUID 등을 이용해 난수 값 생성 --> DB Insert --> S3 Upload 혹은 반대로
이처럼 생성된 ID를 따로 파싱하지 않아도 일련의 과정을 진행할 수 있게 된다.
당연히 성능상 이점이 있을 것이다.
여기까진 좋은데, 그래서 고유한 값을 생성하기 위해 무엇을 이용할 것인가? 에 대한 난관에 봉착했다.
UUID의 경우엔 랜덤값이기 때문에 추측이 불가능한 값이라는 보안상의 이점이 있긴 하지만 이 부분만 보고 PK를 UUID 값으로 주기엔 단점이 너무 많다.
조사를 했을 때, 정말 수많은 단점을 마주했지만 대표적으로 몇개만 적어보겠다.
1. 정렬불가
당연한 얘기지만 정렬이 안된다.
2. 용량
16바이트나 차지함.
3. B+tree 인덱스 구조에 적합하지 않음.
내가 생각한 제일 큰 문제점(아닐 수도) 이었다.
B+tree란 자식 노드를 최대 2개 이상 가질 수 있고 leaf 노드의 레벨이 전부 같은 자료구조.
B+tree는 leaf 노드가 아니면 자신의 Key와 자식 노드에 대한 포인터를 가지고 있다.
그리고 각각의 leaf 노드는 Key와 data pointer를 가지고 있는 각각의 레코드가 정렬되어 있으며 같은 레벨에 있는 다음 leaf 노드로 가기 위한 포인터로 구성되어 있다.
그렇다면 순차적으로 증가하는 AUTO_INCREMENT의 경우 바로 마지막 leaf 노드로 가서 값을 추가하면 되지만 랜던 값으로 생성되는 UUID의 경우 자신이 들어갈 알맞은 순서를 찾아야 하기 때문에 속도면에서 더 느리다.
https://vladmihalcea.com/uuid-database-primary-key/
더 자세한 내용은 해당 참조 링크를 확인하는게 더 좋을 것 같습니다.
그렇다면 도대체 PK에 어떤 값을 줘야 할까?
그렇게 내가 찾은 것은 TSID이다. 위에 참조 링크에도 이에 대한 내용이 상세하게 설명 되어 있지만 정말 간단히 요약하면
그래서 내가 내린 결론은 TSID를 사용하는 것이었다.
근데 여기서 또 문제가 터지는데
TSID를 사용하려면 Bigint를 사용하는 것을 추천한다고 한다.
JS에도 Bigint 타입이 존재하긴 한다. 여기까진 다 좋은데 문제는 JSON을 통해 전달하려고 할 때 문제가 발생한다.
JSON이 지원하는 자료형을 보자.
JSON은 BigInt자체를 지원하지 않는다. 그래서 JSON.stringify 메서드에 Bigint값을 입력하면 에러를 발생시킨다.
여기서부터 머리가 굉장히 아파지기 시작했다.
해결 방법을 찾아 봤고, Bigint에 toJSON 메서드를 추가하는 방법이었는데 여기서 string으로 변환 하냐, number로 변환 하냐에 대한 고민이 있었다. 그리고 그 고민은 의외로 빨리 풀렸다.
결론은 그냥 처음 Bigint값을 생성하고 바로 number 혹은 string으로 변환해서 사용하자는 것이었다. 그리고 string으로 쐐기를 박게 됐는데 그 이유는 다음과 같다.

해당 코드의 결과는

JS number의 경우에 16번째 자리 이후의 숫자는 다 0으로 만들어 버린다.
사실 애플리케이션 코드 상에서 string으로 관리 한다고 딱히 문제될 것도 없을 것 같아서 그냥 string 타입으로 사용하기로 했다.
글 잘보고 갑니다.