Skip to content

๐Ÿ”— DB ์‹๋ณ„์ž ๊ฐ’์œผ๋กœ UUID v7 ์‚ฌ์šฉํ•˜๊ธฐ

์–‘์‹œ์ค€ edited this page Nov 29, 2023 · 11 revisions

๋ฌธ์ œ

์ฟผ๋ฆฌ๊ฐ€ ์˜๋„ํ•œ ๋Œ€๋กœ ๋™์ž‘ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ธ๋ฑ์Šค ์Šค์บ”์ด ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

select max(basechat2_.chat_id)  
from chat basechat2_  
where basechat2_.room_id = roomuser0_.room_id  
order by basechat2_.create_at desc,  
         basechat2_.chat_id desc

์ „์ฒด ์ฟผ๋ฆฌ๋กœ ํ•˜๋ฉด ๊ธธ์–ด์ง€๊ธฐ ๋•Œ๋ฌธ์— ์ „์ฒด ์ฟผ๋ฆฌ ์ค‘ ๋ฌธ์ œ์˜ ์„œ๋ธŒ์ฟผ๋ฆฌ๋งŒ ์ž‘์„ฑํ•˜์˜€์Šต๋‹ˆ๋‹ค.

create_at๋Š” ์‹œ๊ฐ„ ์ˆœ์ด๋ฉฐ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
chat_id๋„ ์‹œ๊ฐ„ ์ˆœ์œผ๋กœ ์ˆœ์ฐจ์ ์œผ๋กœ ์ถ”๊ฐ€๋˜๋ฉฐ ๋ณ€๊ฒฝํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

๊ทธ๋Ÿฌ๋‚˜ MySQL์—์„œ๋Š” A, B ๋ ˆ์ฝ”๋“œ๊ฐ€ ์žˆ์„ ๋•Œ, A๊ฐ€ B๋ณด๋‹ค create_at์ด ํฌ๋ฉด chat_id๋„ ํฌ๋‹ค๋Š” ๊ฑธ ๋ชจ๋ฆ…๋‹ˆ๋‹ค(์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋‹จ์—์„œ ์ •ํ•œ ๊ทœ์น™์ด๋ฏ€๋กœ).
ํ˜„์žฌ ์ธ๋ฑ์Šค๊ฐ€ basechat_idx(room_id, create_at, chat_id)์ธ๋ฐ, room_id๊นŒ์ง€๋งŒ ์œ ํšจํ•˜๊ณ  ์ค‘๊ฐ„์— create_at๊ฐ€ ์—†์–ด์„œ ์ฒ˜๋ฆฌ๊ฐ€ ๋˜์ง€์•Š์Šต๋‹ˆ๋‹ค.
๊ทธ๋ž˜์„œ room์˜ ๋ชจ๋“  chat์„ index full scanํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

์ฟผ๋ฆฌ ํ…Œ์ŠคํŠธ ์‹œ ์ƒํƒœ

  • user 100๋ช…
  • room 200
  • roomuser 100 ๊ฐœ
  • chat 100000 ๊ฐœ = ํ•œ ๋ฐฉ๋‹น 500๊ฐœ

์™œ ๋ฌธ์ œ์ผ๊นŒ?

index full scan = ํ…Œ์ด๋ธ” ๋ฐ์ดํ„ฐ๊ฐ€ ๋Š˜์–ด๋‚  ์ˆ˜๋ก ์ฝ๋Š” ๋Ÿ‰์ด ์ฆ๊ฐ€ -> ์„ฑ๋Šฅ ๊ฐ์†Œ

์›์ธ

์ธ๋ฑ์Šค ์กฐ๊ฑด์œผ๋กœ ์ธํ•œ ๋ฌธ์ œ์ด์ง€๋งŒ, ๋” ๊ทผ๋ณธ์ ์œผ๋กœ๋Š” id์™€ create_at์œผ๋กœ ์‹œ๊ฐ„ ์ˆœ ์ •๋ ฌ 2๊ฐ€์ง€ ๊ฐ’์„ ๋ชจ๋‘ ์‚ฌ์šฉํ•˜๋ ค๊ณ  ํ•ด์„œ ์ƒ๊ธด ๋ฌธ์ œ์ž…๋‹ˆ๋‹ค.

ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

  1. ์ธ๋ฑ์Šค ์ถ”๊ฐ€/์กฐ๊ฑด ์ˆ˜์ •
    • ์„ฑ๋Šฅ ๊ฐ์†Œ or ์œ ์—ฐํ•œ ์ฟผ๋ฆฌ(ํŠน์ • ์‹œ๊ฐ„ ์ˆœ ๊ฒ€์ƒ‰ ๋“ฑ) ์ค‘์— ํ•˜๋‚˜๋Š” ํฌ๊ธฐ๋ฅผ ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  2. ์‹œ๊ฐ„ ์ •๋ณด๊ฐ€ ํฌํ•จ๋œ ID๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ
    • ๊ทผ๋ณธ์ ์ธ ์›์ธ ํ•ด๊ฒฐ
  3. Native SQL์„ ์‚ฌ์šฉํ•˜๊ธฐ
    • JPA์˜ ํ•œ๊ณ„๋กœ from ์ ˆ ์„œ๋ธŒ์ฟผ๋ฆฌ ๋ฐ ์„œ๋ธŒ์ฟผ๋ฆฌ limit์ด ๋ถˆ๊ฐ€๋Šฅ, ํ•˜์ง€๋งŒ ์ง์ ‘ SQL๋ฌธ์„ ์ž‘์„ฑํ•œ๋‹ค๋ฉด ๊ธฐ์กด ๊ตฌ์กฐ๋กœ๋„ ์œ ํšจํ•œ ์„ค๊ณ„๋ฅผ ๊ฐ€์ ธ๊ฐˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํ•ด๊ฒฐ ๋ฐฉ๋ฒ• ์„ ํƒ

2๋ฒˆ ๋ฐฉ๋ฒ• ์ฑ„ํƒ ์ด์œ : ์„ค๊ณ„ ๋‹จ๊ณ„์ด๋ฏ€๋กœ, ๊ธฐ์กด ๋ฐ์ดํ„ฐ์™€์˜ ํ˜ธํ™˜์„ฑ์„ ๊ฑฑ์ •ํ•˜์ง€ ์•Š์•„๋„ ๋ฉ๋‹ˆ๋‹ค. long, localdate์˜ byte ํฌ๊ธฐ๋ฅผ ํ•ฉ์น˜๋ฉด uuid์™€ ๋™์ผํ•˜์—ฌ ์ €์žฅ๊ณต๊ฐ„ ๋ฌธ์ œ๋„ ์ ์Šต๋‹ˆ๋‹ค.

์–ด๋–ค ์‹๋ณ„์ž๋ฅผ ์‚ฌ์šฉํ•  ๊ฒƒ์ธ๊ฐ€?

  • ObjectID: MongoDB์—์„œ ์‚ฌ์šฉํ•จ, ์‹œ๊ฐ„ ์ˆœ ์ •๋ ฌ, 96bit(12-bytes)
  • UUID: 128bit(16byte), ํ‘œ์ค€์œผ๋กœ ๋„๋ฆฌ ์“ฐ์ž„. v1๊ณผ v7๋งŒ ์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ๊ฐ€ ํฌํ•จ๋˜๋ฉฐ, ์‹œ๊ฐ„ ์ˆœ ์ •๋ ฌ์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.
  • ULID: UUID์™€ ํ˜ธํ™˜๋จ, ์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•œ ์ปค์Šคํ…€ UUID ํ˜•์‹
  • Snowflake ID: ํŠธ์œ„ํ„ฐ์—์„œ ๋งŒ๋“  PK, ์˜คํ”ˆ์†Œ์Šค ํ–ˆ๋˜ ๊ฑธ๋กœ ์•„๋Š”๋ฐ ์ง€๊ธˆ์€ ์•„๋‹™๋‹ˆ๋‹ค.

๊ฒฐ๋ก : UUIDv7 ์ฑ„ํƒ

  • ULID์™€ ๋‹ฌ๋ฆฌ ํ‘œ์ค€์ž…๋‹ˆ๋‹ค.
  • v1์ฒ˜๋Ÿผ ํ•„์š”ํ•˜์ง€ ์•Š์€ MAC address(๋ณด์•ˆ ์ƒ ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธธ ์ˆ˜๋„ ์žˆ๋‹ค๊ณ  ํ•จ)๊ฐ™์€ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.
  • ์‹œ๊ฐ„ ์ œ์™ธํ•˜๊ณ  ๋‚˜๋จธ์ง€ ์š”์†Œ๋Š” ์ „๋ถ€ ์ˆœ์ฐจ์ ์ธ ๋žœ๋ค ๋ฐ์ดํ„ฐ์ž…๋‹ˆ๋‹ค (๊ตฌํ˜„์— ๋”ฐ๋ผ ๋‹ค๋ฅผ ์ˆ˜ ์žˆ๋‹ค๊ณ  ํ•จ)
  • ์Šคํƒ€๊ฐ€ ๊ฝค ๋งŽ์€ ์˜คํ”ˆ์†Œ์Šค ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์กด์žฌํ•จ (ํƒ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋Œ€๋น„)
  • ๊ทธ๋ฆฌ๊ณ  JPA์˜ ํ˜ธํ™˜์„ ์œ„ํ•ด ULID ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ ULID ํƒ€์ž…์„ ์‚ฌ์šฉํ•˜๋”๋ผ๋„ ์ €์žฅ์„ UUID๋กœ ํ•˜๊ณ  ULID์˜ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ ํ˜•๋ณ‘ํ™˜์„ ํ•ด์•ผ ํ•˜๋Š”๋ฐ, ์ด๋Ÿฌํ•œ ๋ณ€ํ™˜ ๊ณผ์ •์ด ๋ฒˆ๊ฑฐ๋กœ์šธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์‹ค์ œ ์„œ๋น„์Šค์—์„œ ์‹๋ณ„์ž๋กœ ์‚ฌ์šฉํ•˜๊ธฐ๋„ ํ•ฉ๋‹ˆ๋‹ค. CICD ํˆด์„ ์ œ๊ณตํ•˜๋Š” Buildkite ์„œ๋น„์Šค์—์„œ ์‹๋ณ„์ž๋กœ UUIDv7์„ ์ฑ„ํƒํ•˜๊ธฐ๋„ ํ–ˆ์Šต๋‹ˆ๋‹ค.

๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์„ ํƒ

  1. java-uuid-generator
  2. uuid-creator

์Šคํƒ€ ์ˆ˜๊ฐ€ ๋” ๋งŽ์€ 1 ์„ ํƒ

์‹œ๊ฐ„ ์กฐ๊ฑด ์ฟผ๋ฆฌ ์‚ฌ์šฉ๋ฒ•

https://buildkite.com/blog/goodbye-integers-hello-uuids << ์ฐธ๊ณ 

0x018B5642A68F7892AD50248BAC01E76C << Chat ID ์ผ๋•Œ, 0x018B5642A68F ๋ฅผ Hex to Decimal ํ•˜๋ฉด 1697959290511๊ฐ€ ๋‚˜์˜ต๋‹ˆ๋‹ค. ์ด ๊ฐ’์ด ์‹œ๊ฐ„ ์ •๋ณด(epoch time)์ž…๋‹ˆ๋‹ค.

์ฝ”ํ‹€๋ฆฐ ์ฝ”๋“œ๋กœ UUID๋ฅผ ์‹œ๊ฐ„ ์ •๋ณด๋ฅผ ํฌํ•จํ•˜๋Š” long ํƒ€์ž…์œผ๋กœ ๋ณ€ํ™˜ํ•˜๋ ค๋ฉด ์•„๋ž˜์ฒ˜๋Ÿผ ๊ตฌํ˜„ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

fun getTime(uuid: UUID): Long { return uuid.mostSignificantBits ushr 16 }

msb๊ฐ€ 64๋น„ํŠธ๋‹ˆ๊นŒ ์˜ค๋ฅธ์ชฝ์œผ๋กœ 16๋น„ํŠธ ์‹œํ”„ํŠธ ํ•ด์„œ 48๋น„ํŠธ๋งŒ ๊ตฌํ•ด์„œ ์‹œ๊ฐ„ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.

๋‚ ์งœ ๊ธฐ์ค€์œผ๋กœ ์ฟผ๋ฆฌ ์‚ฌ์šฉ ์‹œ์—๋Š”

select *  
from chat  
where chat_id <= 1697959290511

๋กœ ๊ฒ€์ƒ‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (16์ง„์ˆ˜๋‚˜ 10์ง„์ˆ˜๋ž‘ ํ˜ธํ™˜๋˜๋Š” ๋“ฏ)

์•„๋‹ˆ๋ฉด 0x018B5B123FAA72E2BE5953AAF2A0741B ์ฒ˜๋Ÿผ ๊ฑ 16์ง„์ˆ˜ ์จ๋„ ์‹คํ–‰ ๊ฐ€๋Šฅ

๋Œ€์‹  UUIDv7 ํŠน์ง• ์ƒ ์•ž์ž๋ฆฌ๊ฐ€ ์‹œ๊ฐ„์ด๊ณ  ๋‚˜๋จธ์ง€๋Š” ๋žœ๋ค์ด๋ผ ์ •ํ™•ํžˆ ๊ทธ ์‹œ๊ฐ„๋Œ€๋”๋ผ๋„ <= ์กฐ๊ฑด์œผ๋กœ ๊ฒ€์ƒ‰ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค(์–ด์ฐจํ”ผ == ์กฐ๊ฑด ์“ฐ๋ฉด ์—๋Ÿฌ).

๋งŒ์•ฝ ํŠน์ • ์‹œ๊ฐ„๋Œ€ ์ดํ•˜(>=) ์กฐ๊ฑด์„ ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด?

select *  
from chat  
where chat_id > 1697959290511 + 1 # ์‹œ๊ฐ„๋Œ€์— 1 ๋ฐ€๋ฆฌ์ดˆ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ  ๋ฏธ๋งŒ ์กฐ๊ฑด์œผ๋กœ ๊ฒ€์ƒ‰ํ•˜๋Š” ๊ฒƒ

์ฒ˜๋Ÿผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

java UUID ์‚ฌ์šฉ ์‹œ ์ฃผ์˜์ 

JDK์—๋Š” 64๋น„ํŠธ ๊ฐ’์˜ ์ˆœ์ง„ํ•œ ๋น„๊ต๋ฅผ ์‚ฌ์šฉํ•˜๋Š” java.util.UUID์˜ ๊ตฌํ˜„์— ๊ฒฐํ•จ์ด ์žˆ์Šต๋‹ˆ๋‹ค.
compareTo()๊ธฐ๋ณธ ์ฝ˜ํ…์ธ ๊ฐ€ ๋ชจ๋“  ๋ชฉ์ ์— ๋Œ€ํ•ด ์„œ๋ช…๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ ์ด๋Š” ์˜ˆ์ƒ๋Œ€๋กœ ์ž‘๋™ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
์˜ˆ๋ฅผ ๋“ค์–ด ๋‘ ๊ฐœ์˜ UUID๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

7f905a0b-bb6e-11e3-9e8f-000000000000
8028f08c-bb6e-11e3-9e8f-000000000000

๋ถ€ํ˜ธ ํ™•์žฅ์œผ๋กœ ์ธํ•ด ๋‘ ๋ฒˆ์งธ ๊ฐ’๋ถ€ํ„ฐ ๋จผ์ € ์ฃผ๋ฌธ๋ฉ๋‹ˆ๋‹ค(๋‘ ๋ฒˆ์งธ ๊ฐ’์€ ์Œ์ˆ˜๋กœ ๊ฐ„์ฃผ๋˜๋ฏ€๋กœ "๋” ์ž‘์Œ").

์ด ๋•Œ๋ฌธ์— ํ•ญ์ƒ com.fasterxml.uuid.UUIDComparator๊ฐ„๋‹จํ•œ ๋ถ€ํ˜ธ ์—†๋Š” ์ •๋ ฌ์ธ ์˜ˆ์ƒ ์ •๋ ฌ ์ˆœ์„œ๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ์™€ ๊ฐ™์€ ์™ธ๋ถ€ ๋น„๊ต๊ธฐ๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
์ด๋Š” UUID์˜ ์‚ฌ์ „์‹(์•ŒํŒŒ๋ฒณ) ์ •๋ ฌ(๊ท ์ผํ•œ ๋Œ€๋ฌธ์ž ์‚ฌ์šฉ์„ ๊ฐ€์ •ํ•  ๋•Œ)๊ณผ ๋™์ผํ•ฉ๋‹ˆ๋‹ค.

์œ„ ๊ธ€์€ java-uuid-generator์—์„œ ์ฐธ๊ณ 

์ฝ”๋“œ ๊ตฌํ˜„ ์‹œ ๋ฌธ์ œ์ 

LocalDateTime์„ Long์œผ๋กœ ๋ณ€ํ™˜ or ๋ฐ˜๋Œ€ ๋กœ์ง์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๊ฒฝ์šฐ์—์„œ LocalDateTime.MAX๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด Long ๋ณ€ํ™˜ ๊ณผ์ •์—์„œ overflow ๋ฐœ์ƒ -> LocalDateTime.MAX ๋Œ€์‹  5000 ๋…„๋„ ์‹œ๊ฐ„์œผ๋กœ ๋ณ€๊ฒฝํ•˜๊ณ  Util ํด๋ž˜์Šค์—์„œ ๊ณต์œ ํ•ด์„œ ์‚ฌ์šฉํ•˜๋„๋ก ๋ณ€๊ฒฝ

UUID ์ ์šฉ ํ›„

id์— ์‹œ๊ฐ„ ์ •๋ณด๊ฐ€ ํฌํ•จ๋˜๊ธฐ ๋•Œ๋ฌธ์— create_at์„ ์‚ญ์ œ, ์ธ๋ฑ์Šค ๊ตฌ์กฐ๋ฅผ chat_idx_1(room_id ASC, chat_id DESC),๋กœ ๋ณ€๊ฒฝ

SELECT MAX(chat.chat_id) FROM chat WHERE chat.room_id = 11

์‹คํ–‰ ๊ณ„ํš

-> Zero input rows (No matching min/max row), aggregated into one output row  (cost=0..0 rows=1) (actual time=0.00954..0.00963 rows=1 loops=1)

room_id=11์—๋Š” 500๊ฐœ์˜ ์ฑ„ํŒ…์ด ์žˆ์ง€๋งŒ ํ•˜๋‚˜์˜ ๋ ˆ์ฝ”๋“œ๋งŒ ์ฝ๊ณ  ์ฟผ๋ฆฌ๋ฅผ ์ข…๋ฃŒํ•ฉ๋‹ˆ๋‹ค.

SELECT count(chat.chat_id) FROM chat WHERE chat.room_id = 11
-> Aggregate: count(chat.chat_id)  (cost=101 rows=1) (actual time=0.257..0.257 rows=1 loops=1)
    -> Covering index lookup on chat using chat_idx_1 (room_id=11)  (cost=50.9 rows=500) (actual time=0.0692..0.223 rows=500 loops=1)

๊ฒฐ๋ก 

์‹œ๊ฐ„ ์ˆœ ์ •๋ ฌ๊ณผ ์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ๊นŒ์ง€ ํ•˜๋‚˜๋กœ ์ฒ˜๋ฆฌํ•จ์œผ๋กœ์จ ์ธ๋ฑ์Šค๋ฅผ ๋”์šฑ ํšจ๊ณผ์ ์œผ๋กœ ์„ค๊ณ„ํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

๋‚˜์ค‘์— ์—”ํ‹ฐํ‹ฐ ๊ตฌ์กฐ ์ž์ฒด๊ฐ€ ๋ณ€๊ฒฝ๋˜์–ด์„œ ํ•ด๋‹น ์ฟผ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜์ง€๋Š” ์•Š๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ UUID๋ฅผ ์‚ฌ์šฉํ•จ์œผ๋กœ์จ ํšจ์œจ์ ์ธ ์ธ๋ฑ์Šค ์‚ฌ์šฉ์ด ๊ฐ€๋Šฅํ•ด์กŒ๊ณ , ์ดํ›„ ์ƒˆ๋กญ๊ฒŒ ๊ตฌํ˜„ํ•˜๋Š” ๊ตฌ์กฐ์—์„œ๋„ ์‚ฌ์šฉ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.