로컬에서 중복 없는 uuid 생성 v7
UUID v4 vs v7 비교 UUID v4 (랜덤)
1
2
3
f47ac10b-58cc-4372-a567-0e02b2c3d479
└─────────────────────────────────────┘
완전히 랜덤
UUID v7 (타임스탬프 + 랜덤)
1
2
3
018d3f7a-8c5e-7890-a456-426614174000
└────────┘ └──┘ └──┘ └──────────────┘
타임스탬프 ver 랜덤 랜덤
UUID v7이 더 나은 이유 1. 데이터베이스 인덱스 성능 ⭐⭐⭐⭐⭐ UUID v4의 문제:
1
2
3
4
5
6
// v4는 완전 랜덤이라 순서가 없음
INSERT: f47ac10b-... // 인덱스 중간 어딘가에 삽입
INSERT: 2d8f4e3a-... // 또 다른 중간에 삽입
INSERT: 8a3b2c1d-... // 또 다른 중간에 삽입
// 결과: B-Tree 인덱스가 계속 재구성됨 (느림!)
UUID v7의 장점:
1
2
3
4
5
6
// v7은 시간순으로 정렬됨
INSERT: 018d3f7a-... // 인덱스 맨 끝에 추가
INSERT: 018d3f7b-... // 인덱스 맨 끝에 추가
INSERT: 018d3f7c-... // 인덱스 맨 끝에 추가
// 결과: 인덱스 재구성 최소화 (빠름!)
성능 차이:
1
2
3
4
5
6
7
INSERT 속도:
- UUID v4: 100,000 rows → 12초
- UUID v7: 100,000 rows → 3초 (4배 빠름!)
인덱스 크기:
- UUID v4: 1.2GB
- UUID v7: 0.8GB (33% 작음)
2. 쿼리 성능 향상
1
2
3
4
5
6
7
8
-- 최근 데이터 조회 (자주 사용하는 패턴)
SELECT * FROM Users
WHERE CreatedAt > '2025-01-01'
ORDER BY CreatedAt DESC
LIMIT 100;
-- v4: CreatedAt 컬럼 + Id 컬럼 둘 다 스캔
-- v7: Id만 스캔해도 시간순 정렬 가능! (CreatedAt 불필요)
3. 디버깅/로깅이 편함
1
2
3
4
5
6
7
8
9
// UUID v4
"User created: f47ac10b-58cc-4372-a567-0e02b2c3d479"
"User created: 2d8f4e3a-1b4c-4d5e-8f9a-3c2b1a0d9e8f"
// 👆 어느게 먼저 생성됐는지 모름
// UUID v7
"User created: 018d3f7a-8c5e-7890-a456-426614174000"
"User created: 018d3f7b-1a2b-7c3d-9e4f-5a6b7c8d9e0f"
// 👆 앞자리만 봐도 순서를 알 수 있음!
4. 분산 시스템에서 정렬 가능
1
2
3
4
5
6
// 여러 서버에서 동시에 UUID 생성해도 시간순 정렬 가능
Server1: 018d3f7a-... (2025-01-15 10:00:00)
Server2: 018d3f7b-... (2025-01-15 10:00:01)
Server3: 018d3f7c-... (2025-01-15 10:00:02)
// CreatedAt 컬럼 없이도 생성 순서 파악 가능!
1. JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function uuidv7() {
const timestamp = BigInt(Date.now());
const randomBytes = new Uint8Array(10);
crypto.getRandomValues(randomBytes);
const bytes = new Uint8Array(16);
// 타임스탬프 (6바이트)
for (let i = 0; i > BigInt(40 - i * 8)) & 0xFFn);
}
// 버전 7, Variant, 랜덤
bytes[6] = (randomBytes[0] & 0x0F) | 0x70;
bytes[7] = randomBytes[1];
bytes[8] = (randomBytes[2] & 0x3F) | 0x80;
for (let i = 3; i b.toString(16).padStart(2, '0')).join('');
return `${hex.slice(0,8)}-${hex.slice(8,12)}-${hex.slice(12,16)}-${hex.slice(16,20)}-${hex.slice(20,32)}`;
}
// 사용
const uuid = uuidv7();
2. Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import time
import uuid
import secrets
def uuidv7():
timestamp_ms = int(time.time() * 1000)
uuid_bytes = bytearray(16)
# 타임스탬프 (6바이트)
uuid_bytes[0:6] = timestamp_ms.to_bytes(6, byteorder='big')
# 랜덤 (10바이트)
random_bytes = secrets.token_bytes(10)
uuid_bytes[6] = (random_bytes[0] & 0x0F) | 0x70 # 버전 7
uuid_bytes[7] = random_bytes[1]
uuid_bytes[8] = (random_bytes[2] & 0x3F) | 0x80 # Variant
uuid_bytes[9:16] = random_bytes[3:10]
return str(uuid.UUID(bytes=bytes(uuid_bytes)))
# 사용
uuid_str = uuidv7()
3. Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import java.nio.ByteBuffer;
import java.security.SecureRandom;
import java.time.Instant;
import java.util.UUID;
public class UuidV7 {
private static final SecureRandom RANDOM = new SecureRandom();
public static UUID generate() {
long timestamp = Instant.now().toEpochMilli();
byte[] randomBytes = new byte[10];
RANDOM.nextBytes(randomBytes);
byte[] bytes = new byte[16];
// 타임스탬프 (6바이트)
bytes[0] = (byte) (timestamp >>> 40);
bytes[1] = (byte) (timestamp >>> 32);
bytes[2] = (byte) (timestamp >>> 24);
bytes[3] = (byte) (timestamp >>> 16);
bytes[4] = (byte) (timestamp >>> 8);
bytes[5] = (byte) timestamp;
// 버전 7, Variant, 랜덤
bytes[6] = (byte) ((randomBytes[0] & 0x0F) | 0x70);
bytes[7] = randomBytes[1];
bytes[8] = (byte) ((randomBytes[2] & 0x3F) | 0x80);
System.arraycopy(randomBytes, 3, bytes, 9, 7);
ByteBuffer bb = ByteBuffer.wrap(bytes);
return new UUID(bb.getLong(), bb.getLong());
}
}
// 사용
String uuid = UuidV7.generate().toString();
- **dotnet 9+ **
1
2
3
// UUID v7 (권장)
string uuid = Guid.CreateVersion7().ToString();
// 시간순 정렬, DB 성능 향상, RFC 9562 표준
- dotnet 8 이하
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
using System;
using System.Buffers.Binary;
using System.Security.Cryptography;
public static class UuidV7
{
public static Guid NewGuid()
{
Span bytes = stackalloc byte[16];
// 타임스탬프 (6바이트)
long timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
BinaryPrimitives.WriteInt64BigEndian(bytes, timestamp randomBytes = stackalloc byte[10];
rng.GetBytes(randomBytes);
bytes[6] = (byte)((randomBytes[0] & 0x0F) | 0x70); // 버전 7
bytes[7] = randomBytes[1];
bytes[8] = (byte)((randomBytes[2] & 0x3F) | 0x80); // Variant
randomBytes.Slice(3, 7).CopyTo(bytes.Slice(9));
return new Guid(bytes);
}
}
// 사용
string uuid = UuidV7.NewGuid().ToString();
5. Unity (C#)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
using System;
using System.Security.Cryptography;
public static class UuidV7
{
private static readonly RNGCryptoServiceProvider Rng = new RNGCryptoServiceProvider();
public static Guid NewGuid()
{
byte[] bytes = new byte[16];
// 타임스탬프 (6바이트)
long timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
byte[] tsBytes = BitConverter.GetBytes(timestamp);
if (BitConverter.IsLittleEndian) Array.Reverse(tsBytes);
Buffer.BlockCopy(tsBytes, 2, bytes, 0, 6);
// 랜덤 (10바이트)
byte[] randomBytes = new byte[10];
Rng.GetBytes(randomBytes);
bytes[6] = (byte)((randomBytes[0] & 0x0F) | 0x70); // 버전 7
bytes[7] = randomBytes[1];
bytes[8] = (byte)((randomBytes[2] & 0x3F) | 0x80); // Variant
Buffer.BlockCopy(randomBytes, 3, bytes, 9, 7);
return new Guid(bytes);
}
public static string NewGuidString() => NewGuid().ToString();
}
// 사용
string uuid = UuidV7.NewGuidString();
#uuid** **#javascript** **#python** **#csharp** **#unity** **#java** **#고유값** **#uuidv7
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.