본문으로 건너뛰기

ULID 생성기 - 온라인에서 고유하고 정렬 가능한 ID 무료 생성

크록포드 베이스32 인코딩을 사용해 26자리의 고유하고 사전순으로 정렬 가능한 식별자(ULID)를 즉시 생성합니다. 데이터베이스 기본 키와 API, 분산 시스템에서 UUID 대신 쓸 수 있는 개발자를 위한 무료 온라인 생성 도구입니다.

ULID 생성기

결과를 보려면 값을 입력하세요
로딩 계산기...
📚

문서화

ULID란 무엇인가?

ULID(보편적으로 고유하고 사전순 정렬이 가능한 식별자)는 데이터베이스의 레코드나 분산 시스템의 이벤트를 표시하는 데 사용하는 26자 코드입니다. 현재 시각과 무작위 데이터를 결합하므로, 사전순으로 정렬한 ULID 목록은 각 ULID가 생성된 시각순으로 정렬됩니다. ULID 생성기는 이러한 코드를 생성하는 도구입니다.

ULID 구조

ULID는 구분 기호 없이 두 부분을 차례로 이어서 작성합니다.

  • 타임스탬프(10자): 1970년 1월 1일(유닉스 에포크) 이후 경과한 밀리초 수로, 최대 48비트를 포함합니다.
  • 무작위성(16자): 80비트의 무작위 데이터가 추가되어, 같은 밀리초에 생성된 두 ULID도 거의 확실히 서로 다릅니다.

두 부분 모두 Crockford의 Base32 알파벳을 사용해 작성합니다. 숫자 0–9와 A-Z의 문자 중 I, L, O, U를 제외한 문자로 구성됩니다. 이 네 문자는 숫자 0과 1 또는 서로 다른 문자로 잘못 읽을 수 있으므로 제외합니다. 그 결과 문자열이 짧고 소리 내어 읽기 쉬우며 URL에 바로 넣어도 안전합니다.

ULID 계산 방법

ULID 생성기는 세 단계로 식별자를 만듭니다.

  1. 현재 시각 읽기: 유닉스 에포크 이후 경과한 밀리초 단위의 시각을 읽습니다. 이는 48비트 숫자입니다.
  2. 80비트의 무작위 데이터 생성: 일반적으로 암호학적으로 안전한 난수 생성기에서 얻으며, 10개의 무작위 바이트가 됩니다.
  3. 두 부분을 별도로 인코딩: Crockford의 Base32 알파벳을 사용합니다. 48비트 타임스탬프는 10자가 되고, 80비트 무작위 값은 16자가 됩니다. 인코딩된 두 문자열을 이어 최종 26자 ULID를 만듭니다.

인코딩은 한 번에 5비트씩 숫자를 읽는 방식으로 이루어집니다. 각 Base32 문자가 32개의 가능한 값 중 하나를 나타내기 때문입니다(2^5 = 32). 10자는 최대 50비트를 담을 수 있으므로 48비트 타임스탬프를 담기에 충분합니다. 16자는 정확히 80비트를 담으며, 무작위성 부분과 일치하므로 남는 비트가 없습니다.

풀이 예시

ULID 생성기가 타임스탬프 1712345678901(에포크 이후 밀리초 단위)에서 실행되고 무작위 바이트 [12, 240, 88, 3, 199, 45, 6, 231, 128, 17]를 생성한다고 가정해 보겠습니다.

1단계 – 타임스탬프 인코딩. 1712345678901을 32로 반복해서 나누고, 매번 나머지를 구한 다음 숫자를 뒤에서부터 읽습니다(표준 진법 변환 방식입니다). 그러면 10자 문자열 01HTQW311N이 생성됩니다.

2단계 – 무작위성 인코딩. 10개의 무작위 바이트를 80비트 스트림으로 묶은 다음, 한 번에 5비트씩 읽어 각 5비트 묶음을 Base32 문자로 변환합니다. 그러면 16자 문자열 1KR5G0Y75M3EF00H이 생성됩니다.

단계 3 – 두 부분 결합. 최종 ULID는 다음과 같습니다.

101HTQW311N 1KR5G0Y75M3EF00H
2

공백 없이 작성하면 01HTQW311N1KR5G0Y75M3EF00H이며, 전체 26자입니다.

이 생성기 사용하기

생성기는 브라우저에서 실행됩니다. 각 ULID는 브라우저 시계와 브라우저의 암호학적 난수 생성기에서 얻은 80개의 새로운 무작위 비트로 만들어지므로, 어떤 식별자도 서버로 전송되거나 서버에서 가져오지 않습니다.

  • ULID 개수는 1에서 50까지의 정수를 입력받습니다. 기본값은 1입니다.
  • ULID 생성은 새로운 묶음을 생성합니다. 개수를 변경해도 새로운 묶음이 생성됩니다.
  • 결과의 복사는 해당 ULID 하나만 복사합니다. 둘 이상 표시된 경우 모두 복사는 전체 목록을 한 줄에 ULID 하나씩 복사합니다.
  • 초기화는 개수를 1로 되돌리고 새로운 ULID 하나를 생성합니다.

결과 아래의 ULID 구조 패널은 묶음의 첫 번째 ULID를 두 부분으로 나눕니다. 처음 10자는 타임스탬프이고 마지막 16자는 무작위 데이터입니다.

ULID와 UUID 비교

기능ULIDUUID(버전 4)
길이26자하이픈을 포함한 36자
생성 시각순 정렬 가능예아니요
인코딩Crockford Base3216진수
타임스탬프 포함예(48비트)아니요
이스케이프 없이 URL에 안전함예예

두 형식 모두 중앙 기관이 번호를 배정하지 않아도 여러 시스템에서 고유한 식별자를 생성하는 것을 목표로 합니다. 실질적인 주요 차이는 정렬입니다. ULID는 타임스탬프로 시작하므로 일반 텍스트로 정렬해도 시간순으로 정렬됩니다. 무작위 UUID에는 이 특성이 없습니다. 모든 부분이 무작위이기 때문입니다.

일반적인 활용

  • 데이터베이스 기본 키: ULID를 사용하면 데이터베이스가 새 행을 삽입된 순서와 대략 비슷한 순서로 색인할 수 있어, 많은 데이터베이스 엔진에서 완전히 무작위인 키보다 효율적으로 처리합니다.
  • 분산 시스템: 여러 서버가 중앙 카운터와 조정하지 않고도 각자 독립적으로 ULID를 생성할 수 있으며, 그 결과는 여전히 거의 확실히 고유합니다.
  • 이벤트 로그: ULID가 시간순으로 정렬되므로 이벤트 ID 목록을 대략적인 시간 순서로도 사용할 수 있습니다.
  • API 및 파일 식별자: 고정된 26자 URL 안전 형식은 웹 주소와 파일 이름에서 깔끔하게 작동합니다.

관련 식별자 형식으로는 정렬을 위해 타임스탬프도 인코딩하는 KSUID와, Twitter 등에서 사용하는 Snowflake ID가 있습니다. Snowflake ID는 타임스탬프, 기계 식별자, 카운터를 결합한 형식입니다.

코드 예제

아래의 각 예제는 ULID 사양에 설명된 동일한 방식으로 ULID를 만듭니다. 48비트 타임스탬프를 10개의 Crockford Base32 문자로 인코딩하고, 80비트의 무작위 데이터를 16개의 문자로 추가 인코딩한 다음 두 부분을 이어 붙입니다.

JavaScript

1const ENCODING_CHARS = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
2
3function encodeULID(time, randomBytes) {
4  // Timestamp: 48 bits -> 10 characters
5  let timestampStr = '';
6  let t = time;
7  for (let i = 0; i < 10; i++) {
8    timestampStr = ENCODING_CHARS[t % 32] + timestampStr;
9    t = Math.floor(t / 32);
10  }
11
12  // Randomness: 80 bits -> 16 characters
13  let result = timestampStr;
14  let bits = 0;
15  let bitCount = 0;
16  for (let i = 0; i < 10; i++) {
17    bits = (bits << 8) | randomBytes[i];
18    bitCount += 8;
19    while (bitCount >= 5) {
20      bitCount -= 5;
21      result += ENCODING_CHARS[(bits >> bitCount) & 0x1f];
22    }
23  }
24  return result;
25}
26
27function generateULID() {
28  const randomBytes = crypto.getRandomValues(new Uint8Array(10));
29  return encodeULID(Date.now(), randomBytes);
30}
31
32console.log(generateULID());
33

Python

1import os
2import time
3
4ENCODING_CHARS = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"
5
6def encode_ulid(time_ms, random_bytes):
7    # Timestamp: 48 bits -> 10 characters
8    chars = []
9    t = time_ms
10    for _ in range(10):
11        chars.append(ENCODING_CHARS[t % 32])
12        t //= 32
13    timestamp_part = "".join(reversed(chars))
14
15    # Randomness: 80 bits -> 16 characters
16    bits = 0
17    bit_count = 0
18    random_part = []
19    for byte in random_bytes:
20        bits = (bits << 8) | byte
21        bit_count += 8
22        while bit_count >= 5:
23            bit_count -= 5
24            random_part.append(ENCODING_CHARS[(bits >> bit_count) & 0x1F])
25
26    return timestamp_part + "".join(random_part)
27
28def generate_ulid():
29    return encode_ulid(int(time.time() * 1000), os.urandom(10))
30
31print(generate_ulid())
32

Java

1import java.security.SecureRandom;
2import java.time.Instant;
3
4public class ULIDGenerator {
5    private static final SecureRandom random = new SecureRandom();
6    private static final char[] ENCODING_CHARS =
7        "0123456789ABCDEFGHJKMNPQRSTVWXYZ".toCharArray();
8
9    public static String generateULID() {
10        long timestamp = Instant.now().toEpochMilli();
11        byte[] randomBytes = new byte[10];
12        random.nextBytes(randomBytes);
13
14        // Timestamp: 48 bits -> 10 characters
15        char[] timestampChars = new char[10];
16        long t = timestamp;
17        for (int i = 9; i >= 0; i--) {
18            timestampChars[i] = ENCODING_CHARS[(int) (t % 32)];
19            t /= 32;
20        }
21
22        // Randomness: 80 bits -> 16 characters
23        StringBuilder result = new StringBuilder(new String(timestampChars));
24        long bits = 0;
25        int bitCount = 0;
26        for (byte b : randomBytes) {
27            bits = (bits << 8) | (b & 0xFF);
28            bitCount += 8;
29            while (bitCount >= 5) {
30                bitCount -= 5;
31                result.append(ENCODING_CHARS[(int) ((bits >> bitCount) & 0x1F)]);
32            }
33        }
34        return result.toString();
35    }
36
37    public static void main(String[] args) {
38        System.out.println(generateULID());
39    }
40}
41

자주 묻는 질문

ULID는 어디에 사용하나요? ULID는 생성 시각순 정렬도 유용한 경우 데이터베이스 행, API 리소스 또는 기록된 이벤트의 고유 식별자로 사용됩니다.

ULID의 길이는 얼마인가요? ULID는 항상 26자입니다. 타임스탬프에 10자, 무작위성 부분에 16자를 사용합니다.

ULID는 어떤 인코딩을 사용하나요? ULID는 숫자 0–9와 A-Z 문자에서 I, L, O, U를 제거해 만든 Crockford의 Base32 알파벳을 사용합니다. 다른 문자와 혼동하지 않기 위해 이 문자들을 제거합니다.

ULID는 UUID와 어떻게 다른가요? ULID는 생성 시각순으로 정렬되며 길이는 26자입니다. 무작위 UUID(버전 4)는 시간순으로 정렬되지 않으며 하이픈을 포함해 36자입니다.

두 ULID가 충돌할 가능성은 얼마나 되나요? 80비트의 무작위성 덕분에 충돌 가능성은 극히 낮습니다. 동일한 밀리초 안에 약 1.3조 개의 ULID를 생성해야 둘 이상이 일치할 확률이 50%에 도달합니다.

인터넷 연결 없이 ULID를 생성할 수 있나요? 예. ULID에는 로컬 시계와 난수 생성기만 필요하므로 완전히 오프라인에서 만들 수 있습니다.

참고 문헌

  1. "ULID Specification." GitHub, https://github.com/ulid/spec.
  2. "Crockford's Base32 Encoding." http://www.crockford.com/base32.html.