도구스개발

ULID 디코더

ULID에서 만들어진 시각과 난수 부분을 뜯어 봅니다. 특정 시각의 ULID 범위를 만들어 기간 조회 조건으로 쓸 수도 있습니다.

26글자입니다. 소문자와 I·L·O도 받아 정규화합니다.

만들어진 시각

2016-07-30T23:54:10.259Z

밀리초로 1,469,922,850,259입니다

타임스탬프 (앞 10자, 48비트)01ARZ3NDEK
난수 (뒤 16자, 80비트)TSV4RRFFQ69G5FAV
난수를 16진수로D6764C61EFB99302BD5B
128비트 전체를 16진수로01563E3AB5D3D6764C61EFB99302BD5B
계산 근거앞 10자 → 48비트 밀리초 = 1,469,922,850,259뒤 16자 → 80비트 난수 = 0xD6764C61EFB99302BD5B문자표 0123456789ABCDEFGHJKMNPQRSTVWXYZ26자 × 5비트 = 130비트라 앞의 두 비트가 남습니다. 그 두 비트가 0이어야 128비트에 들어가므로 첫 글자는 7을 넘을 수 없습니다.
사전순 정렬이 곧 시간순 정렬입니다. 시각이 앞쪽 10자에 있고 Crockford Base32의 문자표가 값 순서대로 늘어서 있어서, 문자열을 그냥 정렬하면 만들어진 순서가 됩니다. UUID v4는 전부 난수라 이것이 되지 않습니다.
데이터베이스 인덱스에서 이 차이가 큽니다. B-tree에 무작위 키를 넣으면 삽입 지점이 매번 흩어져 페이지가 자주 쪼개지고 캐시도 잘 맞지 않습니다. 증가하는 키는 늘 오른쪽 끝에 붙어 쪼개짐이 적습니다. 다만 그만큼 마지막 페이지에 쓰기가 몰리므로, 쓰기가 아주 많은 분산 환경에서는 오히려 불리할 수도 있습니다.
Crockford Base32는 I·L·O·U를 뺐습니다. I와 L은 1과, O는 0과 헷갈리기 때문이고 U를 뺀 것은 우연히 욕설이 만들어지는 것을 피하려는 것입니다. RFC 4648의 base32(A~Z, 2~7)와 문자표가 완전히 달라 서로 호환되지 않습니다. 읽을 때는 I·L을 1로, O를 0으로 받아 주고 대소문자도 가리지 않습니다.
48비트 밀리초는 서기 10889년까지 담습니다(281,474,976,710,655ms). 그보다 큰 값은 26글자에 들어가지 않으며, 가장 큰 ULID는 7ZZZZZZZZZZZZZZZZZZZZZZZZZ입니다. 8부터 Z로 시작하는 26글자는 형식은 맞아 보여도 ULID가 아닙니다.
입력하신 값은 서버로 전송되지 않습니다. 모든 계산은 브라우저 안에서만 이루어집니다. 다만 ULID의 앞부분은 만들어진 시각을 그대로 담고 있으므로, 시각이 드러나면 곤란한 곳에는 쓰지 않는 편이 좋습니다.

사용 방법

  1. 1ULID 26글자를 붙여 넣으면 만들어진 시각과 난수 부분이 나옵니다.
  2. 2시각으로 범위 만들기로 바꾸면 그 밀리초의 가장 작은 ULID와 가장 큰 ULID가 나옵니다.
  3. 3그 두 값을 BETWEEN 조건에 넣으면 인덱스를 그대로 타는 기간 조회가 됩니다.

자주 묻는 질문

128비트를 48비트 밀리초 타임스탬프와 80비트 난수로 나누고 Crockford Base32로 26글자에 담습니다. 앞 10글자가 시각이고 뒤 16글자가 난수입니다. 시각이 앞쪽에 있어 문자열을 그냥 정렬하면 만들어진 순서가 됩니다.

사전순 정렬이 곧 시간순 정렬이 됩니다. UUID v4는 전부 난수라 정렬해도 순서에 뜻이 없습니다. 데이터베이스 인덱스에서도 차이가 나는데, B-tree에 무작위 키를 넣으면 삽입 지점이 흩어져 페이지가 자주 쪼개지지만 증가하는 키는 오른쪽 끝에 붙어 쪼개짐이 적습니다.

완전히 다릅니다. RFC 4648의 base32는 A~Z와 2~7을 쓰지만 Crockford는 0~9와 A~Z에서 I·L·O·U를 뺀 32글자를 씁니다. I와 L은 1과, O는 0과 헷갈리기 때문이고 U를 뺀 것은 우연히 욕설이 만들어지는 것을 피하려는 것입니다. 읽을 때는 I·L을 1로, O를 0으로 받아 주고 대소문자도 가리지 않습니다.

26글자 × 5비트 = 130비트라 128비트보다 두 비트가 많기 때문입니다. 남는 두 비트는 맨 앞에 붙어 언제나 0이어야 하므로 첫 글자의 값이 7을 넘을 수 없습니다. 가장 큰 ULID는 7ZZZZZZZZZZZZZZZZZZZZZZZZZ이고, 8부터 Z로 시작하는 26글자는 형식은 맞아 보여도 ULID가 아닙니다.

시작 시각의 가장 작은 ULID와 끝 시각의 가장 큰 ULID를 만들어 BETWEEN 조건에 넣습니다. 난수 부분을 전부 0으로 채우면 그 밀리초의 하한, 전부 Z로 채우면 상한이 됩니다. 사전순이 시간순과 같으므로 인덱스를 그대로 타며, 별도의 생성시각 컬럼이나 함수 호출이 필요 없습니다.

48비트 밀리초라 서기 10889년까지입니다. 그보다 큰 값은 26글자에 담을 수 없습니다. 실무에서 걸릴 일은 없지만, 시각을 직접 넣어 ULID를 만들 때는 범위를 검사해야 하는 이유입니다.

전송되지 않습니다. 모든 계산은 브라우저 안에서만 이루어집니다. 다만 ULID의 앞부분은 만들어진 시각을 그대로 담고 있으니, 시각이 드러나면 곤란한 곳에는 쓰지 않는 편이 좋습니다.

알아두면 좋은 점

  • ULID의 앞부분은 만들어진 시각을 그대로 드러냅니다. 시각을 감춰야 하는 곳에는 적합하지 않습니다.
  • 증가하는 키는 인덱스의 마지막 페이지에 쓰기가 몰립니다. 쓰기가 아주 많은 분산 환경에서는 오히려 불리할 수 있습니다.
  • Crockford Base32는 RFC 4648의 base32와 문자표가 달라 서로 호환되지 않습니다.

함께 보면 좋은 도구

마지막 검증: 2026년 8월 31일 · 결과는 참고용 추정치입니다.