RDB에서 UUID를 사용할 때 고민해볼점

devkingsejong·2022년 1월 31일
5

데이터베이스

목록 보기
1/1
post-thumbnail

UUID를 Primary Key로 사용하는 것은 이점이 많습니다.

구조상 중복 발생 확률이 매우 적으며(약 수백조 분의 일 확률),
길이는 일정하고 알파벳과 숫자로만 이루어져 있어 다루기도 쉽죠.

또한, 비즈니스적 요구로 uuid가 사용될 수도 있습니다.

예를 들어, 게시글 id나 주문 id등과 같은 값을 auto_increment가 아닌 uuid로 사용하게 한다면 자연스럽게 외부사용자가 총 생성 횟수를 유추할 수 없게 되고, 악성 사용자가 이전 혹은 다음 id를 쉽게 유추할 수 없도록 할 수 있습니다.


하지만, uuid를 Mysql(Innodb) 환경에서 쓸 때는 Insert, Select, Order등의 연산을 처리할 때 단순 long(integer) 형태의 sequencial pk를 쓸 때보다 생각보다 많은 성능 저하가 있는 것을 염두에 두고 사용해야 합니다.

실제로 제가 수백만건의 레코드를 가지고 있는 서비스를 운영할 당시,
uuid와 그냥 auto_increment를 사용할 때 사람이 인지할 수 있을 정도의 성능차가 있었으며,이를 개선하기 위해 꽤 많은 시간을 할애하였었습니다.

오늘은 이와 관련한 몇가지 테스트를 해보고자 합니다.


성능 비교

uuid를 키로 가지는 테이블의 성능과 auto_increment를 키로 가지는 테이블의 성능 비교를 위해 사용한 테이블의 DDL은 아래와 같습니다.

# auto_increment 를 id로 가지는 테이블 DDL
create table justid_local
(
    id         int(11) unsigned auto_increment primary key,
    title      text                               null,
    created_at datetime default CURRENT_TIMESTAMP null
);
# uuid 를 id로 가지는 테이블 DDL
create table justuuid_local
(
    uuid   binary(16) default not null primary key,
    title      text                                     null,
    created_at datetime   default CURRENT_TIMESTAMP     null
);

1. Insert 성능 비교

100만건 Insert 시간 성능 테스트

# auto_increment 를 id로 가지는 테이블 Insert 시간
1회차2회차3회차
149.514s150.595s164.657s
# uuid4 를 id로 가지는 테이블 Insert 시간
1회차2회차3회차
160.347s176.548s178.298s

100만건의 Bulk Sql Insert 작업을 수행하는데,
평균적으로 약 10% 정도의 Insert 성능 차이가 발생합니다. (UUID가 더 느립니다.)

물론 UUID V1을 사용했을 때에는 시간차가 거의 발생하지 않기 때문에,
uuid v4 사용으로 인한 시간차가 크다는 것을 알 수 있습니다.

2. Select 성능 비교(By id)

단순 Hash 기반 Select의 성능 테스트.

# auto_increment의 Select 성능 비교를 위한 sql
SELECT * FROM justid_local WHERE id in (
	1, 11, 43, 100, 12, 12000, 234443, 234453, 87000, 123000
);
# uuid의 Select 성능 비교를 위한 sql
SELECT * FROM justuuid_local WHERE uuid IN(
    UNHEX(REPLACE('087a75e2-12b4-43d8-a9a6-d1a4a26daa1d', '-', '')),
    UNHEX(REPLACE('0913c3de-ac18-40f6-8ba5-1cf3d0532a98', '-', '')),
    UNHEX(REPLACE('00645bcf-1b50-4ed4-ae63-648421f96b3d', '-', '')),
    UNHEX(REPLACE('0086577c-6e86-4f98-918a-f7a4ebfd14e8', '-', '')),
    UNHEX(REPLACE('008688ae-fc7f-40bb-a899-0720e8c559c7', '-', '')),
    UNHEX(REPLACE('01913cd3-1749-4db1-9077-b2d932a691fa', '-', '')),
    UNHEX(REPLACE('02170e81-4c97-43f4-8dd1-e9394dac5ca5', '-', '')),
    UNHEX(REPLACE('04cb4ec4-32f6-4c56-946f-d73da40e1e8d', '-', '')),
    UNHEX(REPLACE('058a1854-8fb5-4943-b6e3-a1693a065a26', '-', '')),
    UNHEX(REPLACE('0777878b-629b-4a85-992c-ea5583321ff5', '-', ''))
);
# auto_increment 를 id로 가지는 테이블 Select 시간
1회차2회차3회차
42ms33ms35ms
# uuid 를 id로 가지는 테이블 Select 시간
1회차2회차3회차
51ms48ms41ms

100만건의 컬럼에서 무작위 컬럼 10개를 Select 하는 데에는 25% 정도의 성능차가 발생합니다.

다만, (1)번의 경우와 유사하게 이는 UNHEXReplace 함수 이용으로 인한 성능차 입니다. 물론 유저(클라이언트) 레이어 단에서는 HEX형태보다 String 형태로 UUID가 사용되는 경우가 많기 때문에 꼭 필요한 단계이기도 합니다.

3. Select 성능 비교(Sequence 연관)

컬럼의 순서와 연관되어 있는 Select관련 성능 테스트

# auto_increment의 Select 성능 비교를 위한 sql
SELECT * FROM justid_local where created_at like '2022%' Limit 3, 300;
SELECT * FROM justid_local where created_at like '2022%' Limit 3, 3000;
SELECT * FROM justid_local where created_at like '2022%' Limit 3, 3000;
SELECT * FROM justid_local where created_at like '2022%' Limit 3, 30000;
SELECT * FROM justid_local where created_at like '2022%' Limit 3, 300000;
# uuid의 Select 성능 비교를 위한 sql
SELECT * FROM justuuid_local where created_at like '2022%' Limit 3, 300;
SELECT * FROM justuuid_local where created_at like '2022%' Limit 3, 3000;
SELECT * FROM justuuid_local where created_at like '2022%' Limit 3, 3000;
SELECT * FROM justuuid_local where created_at like '2022%' Limit 3, 30000;
SELECT * FROM justuuid_local where created_at like '2022%' Limit 3, 300000;
# auto_increment 를 id로 가지는 테이블 Select 시간
1회차2회차3회차4회차5회차6회차7회차8회차9회차10회차
826ms828ms803ms982ms802ms804ms814ms815ms820ms787ms

mean: 828ms

# uuid 를 id로 가지는 테이블 Select 시간
1회차2회차3회차4회차5회차6회차7회차8회차9회차10회차
934ms901ms897ms937ms918ms916ms1000ms917ms936ms892ms

mean : 925ms

위 결과에서 볼 수 있듯 순서와 연관된 부분에서의 성능 차이는 약 11% 정도로,
그 차이가 더 확연하게 나타납니다.

이 차이는 uuid의 값을 이용해 순서와 연관된 sub 쿼리를 작성하였을 때 더욱 눈에 띄게 발생하게 됩니다.


마치며

앞서 UUID4 혹은 기타 함수 사용으로 인한 성능 차라고 말한 부분의 경우에도,
거의 대부분의 테스트에서 거의 항상 (3)번 항목과 유사하게 UUID가 더 느린 양상을 보였습니다.

물론, 적은 레코드 수나 적은 부하에서는 거의 차이가 발생하지 않거나 오히려 uuid 가 더 빠른 경우도 있었지만, 최소 수백만건의 레코드가 존재하는 환경이나 복잡하거나 오래걸리는 쿼리를 질의할 때는 최소 50% 확률 정도로 uuid가 더 느렸습니다.

그렇기 때문에 uuid가 auto_increment보다 거의 항상 느리다고 말해도 영 틀리지는 않을 것입니다.

만약 비즈니스적으로 n 번째 이후 컬럼, UUID를 이용한 filter 등의 기능 구현이 필요할 경우, auto_increment를 사용하는 id를 복합키로 사용하거나 auto_increment를 가지는 unique notnull column 를 추가하여 사용하는 것이 성능에 큰 도움이 됩니다.

4. 기타

.sql 파일 형태로 덤프된 500만건의 레코드를 Insert 하는 시간 비교

auto_incrementuuid
23 min, 47 sec, 163 ms23 min, 47 sec, 729 ms

-> 이미 정해진 pk를 bulk로 insert하는 경우에는 성능차가 크지 않습니다.

UUID v1을 사용하는 신규 500만건 Insert 시간 비교

auto_incrementuuid
491.0724868774414 s488.1903250217438 s

-> UUID V1()은 시간을 변수로 하여 UUID를 생성함으로 중복 발생 확률이 높고,
비슷한 시간대에 만들어졌을 경우 아래와 같이 값이 유사하다는 단점이 있어 주의하며
사용해야 합니다.

2개의 댓글

comment-user-thumbnail
2022년 4월 28일

좋은 글 감사합니다!

답글 달기
comment-user-thumbnail
2022년 8월 8일

좋은 정보 감사합니다.

답글 달기