UUID: версии, различия и когда использовать каждую

Подробный разбор всех версий UUID (v1-v8): чем они отличаются, как работают, когда использовать каждую версию. Практические примеры генерации UUID на разных языках программирования, производительность и best practices для баз данных.
- теги
- #Программирование #Архитектура #Базы Данных
- категории
- Programming
- опубликовано
Работаешь с базами данных, API или распределенными системами? Рано или поздно сталкиваешься с вопросом: как генерировать уникальные идентификаторы? Auto-increment ID хорош, но в распределенных системах не работает — нужно что-то глобально уникальное. Тут на помощь приходят UUID (Universally Unique Identifier).
Но UUID не так прост, как кажется. У него есть несколько версий (v1, v3, v4, v5, v6, v7, v8), и каждая для разных целей. Недавно разбирался с этим для проекта с микросервисной архитектурой, и выяснилось, что выбор версии UUID сильно влияет на производительность базы данных. Расскажу, что узнал.
Что такое UUID и зачем он нужен
UUID (Universally Unique Identifier) — это 128-битный идентификатор, который генерируется так, что вероятность коллизии практически нулевая. Выглядит как 550e8400-e29b-41d4-a716-446655440000 — 32 шестнадцатеричных символа с дефисами.
Зачем нужны UUID:
- Распределенные системы — генерируешь ID на любом сервере без координации с другими
- Микросервисная архитектура — каждый сервис создает свои ID без конфликтов
- Репликация баз данных — нет проблем с auto-increment при merge
- API безопасность — нельзя угадать следующий ID (в отличие от 1, 2, 3…)
- Offline-first приложения — генерируешь ID на клиенте, потом синхронизируешь
Классический auto-increment ID работает только в одной базе. Если у тебя несколько инстансов или микросервисов, будут коллизии. UUID решает эту проблему.
Формат UUID
UUID состоит из 5 групп:
xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx
- M (4 бита) — версия UUID (1-8)
- N (2-3 бита) — вариант (обычно
10в бинарном виде) - Остальное — данные (зависят от версии)
Всего 128 бит = 2^128 = ~340 ундециллионов вариантов. Вероятность коллизии настолько мала, что ей можно пренебречь (нужно сгенерировать миллиард UUID в секунду в течение 100 лет, чтобы был шанс столкновения 50%).
UUIDv1: Timestamp + MAC адрес
Как работает: Генерируется из текущего timestamp (60 бит) + MAC адреса сетевой карты (48 бит) + счетчика (14 бит).
01234567-89ab-1cde-f012-0123456789ab
^
версия 1
Плюсы:
- Сортируется по времени создания (но не по лексикографическому порядку — timestamp в середине)
- Уникальность гарантируется MAC адресом
- Можно извлечь timestamp создания
Минусы:
- Раскрывает MAC адрес сервера — проблема безопасности
- Не совсем сортируемый (timestamp не в начале)
- Зависит от системных часов — можно сломать при переводе времени назад
Когда использовать:
- Legacy системы, где важна совместимость
- Когда нужно извлечь время создания из UUID
- Локальная сеть, где безопасность не критична
НЕ использовать:
- В публичных API (раскрывает инфраструктуру)
- Когда нужна высокая безопасность
- Для первичных ключей в БД (не оптимален для индексов)
Пример генерации:
# Python
import uuid
uuid1 = uuid.uuid1()
print(uuid1) # 6ba7b810-9dad-11d1-80b4-00c04fd430c8
// Go
import "github.com/google/uuid"
uuid1 := uuid.Must(uuid.NewUUID())
fmt.Println(uuid1) // 6ba7b810-9dad-11d1-80b4-00c04fd430c8
// JavaScript (npm install uuid)
import { v1 as uuidv1 } from 'uuid';
const uuid1 = uuidv1();
console.log(uuid1); // 6ba7b810-9dad-11d1-80b4-00c04fd430c8
UUIDv3: Name-based (MD5)
Как работает: Генерируется из namespace UUID + имени через MD5 хеш. Детерминированный — для одного и того же входа всегда получишь один UUID.
a3bb189e-8bf9-3888-9912-ace4e6543002
^
версия 3
Плюсы:
- Детерминированность — одно имя = один UUID
- Нет нужды хранить mapping (можно пересчитать)
- Хорош для дедупликации
Минусы:
- MD5 устарел и считается слабым (лучше использовать v5)
- Не подходит для безопасности
- Не сортируемый
Когда использовать:
- Генерация UUID из URL (один URL = один UUID)
- Дедупликация данных
- Миграция из системы с строковыми ID
НЕ использовать:
- Для безопасности (MD5 слабый)
- Когда нужна случайность
- Новые проекты (используйте v5 вместо v3)
Пример генерации:
# Python
import uuid
namespace = uuid.NAMESPACE_URL
name = "https://example.com/users/123"
uuid3 = uuid.uuid3(namespace, name)
print(uuid3) # a3bb189e-8bf9-3888-9912-ace4e6543002
# Всегда одинаковый для одного URL
// Go
import "github.com/google/uuid"
namespace := uuid.NameSpaceURL
name := "https://example.com/users/123"
uuid3 := uuid.NewMD5(namespace, []byte(name))
fmt.Println(uuid3)
// JavaScript
import { v3 as uuidv3 } from 'uuid';
const namespace = uuidv3.URL;
const name = 'https://example.com/users/123';
const uuid3 = uuidv3(name, namespace);
console.log(uuid3); // a3bb189e-8bf9-3888-9912-ace4e6543002
Стандартные namespaces:
NAMESPACE_DNS— для доменных именNAMESPACE_URL— для URLNAMESPACE_OID— для ISO OIDNAMESPACE_X500— для X.500 DN
UUIDv4: Полностью случайный
Как работает: Генерируется из случайных или псевдослучайных данных. 122 бита случайности (6 бит зарезервировано для версии и варианта).
550e8400-e29b-41d4-a716-446655440000
^
версия 4
Плюсы:
- Максимальная непредсказуемость
- Не требует состояния (timestamp, MAC и т.д.)
- Безопасен для публичных API
- Простота генерации
Минусы:
- Не сортируемый — плохо для индексов БД
- Большая фрагментация B-tree индексов
- Медленнее вставка в БД (особенно PostgreSQL, MySQL)
- Нельзя извлечь полезную информацию
Когда использовать:
- Токены доступа и сессии
- Публичные ID в API (безопасность)
- Временные идентификаторы
- Когда порядок не важен
НЕ использовать:
- Первичные ключи в БД с высокой нагрузкой (проблемы с производительностью)
- Когда нужна сортировка по времени
- Распределенные системы с требованиями к производительности
Пример генерации:
# Python
import uuid
uuid4 = uuid.uuid4()
print(uuid4) # 550e8400-e29b-41d4-a716-446655440000
// Go
import "github.com/google/uuid"
uuid4 := uuid.New() // По умолчанию v4
fmt.Println(uuid4)
// JavaScript
import { v4 as uuidv4 } from 'uuid';
const uuid4 = uuidv4();
console.log(uuid4); // 550e8400-e29b-41d4-a716-446655440000
-- PostgreSQL
SELECT gen_random_uuid(); -- встроенная функция
-- MySQL 8.0+
SELECT UUID(); -- генерирует v1, но в случайном формате
-- Для v4 нужна функция или триггер
UUIDv5: Name-based (SHA-1)
Как работает: Как v3, но использует SHA-1 вместо MD5. Более безопасный и рекомендуемый для новых проектов.
886313e1-3b8a-5372-9b90-0c9aee199e5d
^
версия 5
Плюсы:
- Детерминированность (как v3)
- Безопаснее MD5
- Хорош для дедупликации
- Можно воссоздать UUID из исходных данных
Минусы:
- Не сортируемый
- SHA-1 тоже устаревает (но лучше MD5)
- Медленнее чистого random
Когда использовать:
- Генерация UUID из URL (один URL = один UUID)
- Кеширование с детерминированными ключами
- Миграция данных (старый ID → UUID)
- Дедупликация контента
НЕ использовать:
- Когда нужна случайность
- Для безопасности (SHA-1 тоже ломается)
- Первичные ключи в высоконагруженных БД
Пример генерации:
# Python
import uuid
namespace = uuid.NAMESPACE_URL
name = "https://example.com/users/123"
uuid5 = uuid.uuid5(namespace, name)
print(uuid5) # 886313e1-3b8a-5372-9b90-0c9aee199e5d
# Всегда одинаковый для одного URL
// Go
import "github.com/google/uuid"
namespace := uuid.NameSpaceURL
name := "https://example.com/users/123"
uuid5 := uuid.NewSHA1(namespace, []byte(name))
fmt.Println(uuid5)
// JavaScript
import { v5 as uuidv5 } from 'uuid';
const namespace = uuidv5.URL;
const name = 'https://example.com/users/123';
const uuid5 = uuidv5(name, namespace);
console.log(uuid5); // 886313e1-3b8a-5372-9b90-0c9aee199e5d
Практический пример — дедупликация:
# Генерируем UUID для каждого документа по его содержимому
import uuid
def get_document_id(content: str) -> uuid.UUID:
namespace = uuid.UUID('6ba7b810-9dad-11d1-80b4-00c04fd430c8')
return uuid.uuid5(namespace, content)
doc1 = "Hello, world!"
doc2 = "Hello, world!"
doc3 = "Different content"
print(get_document_id(doc1)) # Одинаковые UUID
print(get_document_id(doc2)) # Одинаковые UUID
print(get_document_id(doc3)) # Другой UUID
UUIDv6: Timestamp-based (сортируемый)
Как работает: Улучшенная версия v1. Timestamp перемещен в начало UUID, что делает его лексикографически сортируемым. MAC адрес все еще используется.
1ef1b8d0-9dad-6000-80b4-00c04fd430c8
^
версия 6
Плюсы:
- Сортируется по времени создания
- Лучше для индексов БД (чем v1 и v4)
- Совместим с системами, ожидающими timestamp
- Можно извлечь timestamp
Минусы:
- Раскрывает MAC адрес (как v1)
- Относительно новый (RFC только в 2022)
- Меньше поддержки в библиотеках
Когда использовать:
- Первичные ключи в БД (лучше производительность)
- Когда нужна сортировка по времени
- Миграция с v1 на сортируемый формат
НЕ использовать:
- В публичных API (раскрывает инфраструктуру)
- Когда MAC адрес нужно скрыть
- В окружениях без хорошей поддержки v6
Пример генерации:
// Go (требует библиотеку с поддержкой v6)
import "github.com/gofrs/uuid"
uuid6, err := uuid.NewV6()
if err != nil {
panic(err)
}
fmt.Println(uuid6)
# Python (требует библиотеку uuid6)
# pip install uuid6
import uuid6
uuid_v6 = uuid6.uuid6()
print(uuid_v6) # 1ef1b8d0-9dad-6000-80b4-00c04fd430c8
Поддержка v6 пока не везде, так как спецификация новая (RFC 9562, 2022).
UUIDv7: Timestamp + Random (лучший для БД)
Как работает: Unix timestamp в миллисекундах (48 бит) + случайные данные (74 бита). Сортируется по времени создания, но не раскрывает MAC адрес.
018d3f51-8b00-7000-9000-123456789abc
^
версия 7
Плюсы:
- Лексикографически сортируется по времени создания
- Отличная производительность для индексов БД
- Не раскрывает MAC адрес (безопаснее v1/v6)
- Можно извлечь timestamp
- Рекомендуется для новых проектов
Минусы:
- Относительно новый (меньше поддержки)
- Требует монотонного времени (проблемы при откате часов)
- Некоторые языки/библиотеки еще не поддерживают
Когда использовать:
- Первичные ключи в БД (лучший выбор)
- Распределенные системы с сортировкой
- Логи и события с временными метками
- Новые проекты, где нужен UUID
НЕ использовать:
- Старые системы без поддержки v7
- Когда timestamp нужно скрыть
- Если библиотека не поддерживает v7
Пример генерации:
// Go (требует библиотеку с поддержкой v7)
import "github.com/gofrs/uuid"
uuid7, err := uuid.NewV7()
if err != nil {
panic(err)
}
fmt.Println(uuid7) // 018d3f51-8b00-7000-9000-123456789abc
# Python (требует библиотеку uuid6 или uuid-utils)
# pip install uuid-utils
from uuid_utils import uuid7
uuid_v7 = uuid7()
print(uuid_v7) # 018d3f51-8b00-7000-9000-123456789abc
// JavaScript (npm install uuid@latest)
// Поддержка v7 добавлена в uuid@9.0.0+
import { v7 as uuidv7 } from 'uuid';
const uuid7 = uuidv7();
console.log(uuid7); // 018d3f51-8b00-7000-9000-123456789abc
PostgreSQL пример:
-- PostgreSQL 17+ будет поддерживать v7 нативно
-- Пока можно использовать расширение или генерировать на уровне приложения
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- v4, не оптимально
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Лучше генерировать v7 на уровне приложения:
CREATE TABLE users (
id UUID PRIMARY KEY, -- v7 из приложения
created_at TIMESTAMPTZ DEFAULT NOW()
);
UUIDv8: Custom/Vendor-specific
Как работает: Зарезервирован для кастомных реализаций. Формат не определен стандартом — можешь положить туда что угодно.
xxxxxxxx-xxxx-8xxx-xxxx-xxxxxxxxxxxx
^
версия 8
Плюсы:
- Полная гибкость
- Можешь закодировать любую информацию
- Совместимость с экосистемой UUID
Минусы:
- Нет стандартной реализации
- Несовместимость между системами
- Нужно самому заботиться о коллизиях
Когда использовать:
- Специфичные требования к формату
- Внутренние системы с кастомной логикой
- Эксперименты и прототипы
НЕ использовать:
- Для публичных API
- Когда нужна совместимость
- В общих случаях (используйте v4 или v7)
Пример использования — закодировать Shard ID + Timestamp + Counter:
// Go - кастомная реализация v8
func NewCustomUUIDv8(shardID uint16, timestamp int64, counter uint32) uuid.UUID {
var u uuid.UUID
// 48 бит timestamp
binary.BigEndian.PutUint64(u[0:8], uint64(timestamp))
// 16 бит shard ID
binary.BigEndian.PutUint16(u[6:8], shardID)
// Версия 8
u[6] = (u[6] & 0x0f) | 0x80
// 32 бита counter
binary.BigEndian.PutUint32(u[8:12], counter)
// Variant
u[8] = (u[8] & 0x3f) | 0x80
return u
}
Сравнение версий UUID
| Версия | Основа | Сортируемый | Безопасность | Детерминированный | Для БД | Когда использовать |
|---|---|---|---|---|---|---|
| v1 | Timestamp + MAC | Частично | Низкая | Нет | Средне | Legacy, когда нужен timestamp |
| v3 | MD5(namespace+name) | Нет | Низкая | Да | Плохо | Миграция, дедупликация (лучше v5) |
| v4 | Random | Нет | Высокая | Нет | Плохо | API токены, временные ID |
| v5 | SHA-1(namespace+name) | Нет | Средняя | Да | Плохо | Дедупликация, кеширование |
| v6 | Timestamp + MAC (сорт.) | Да | Низкая | Нет | Хорошо | Миграция с v1, когда нужна сортировка |
| v7 | Timestamp + Random | Да | Высокая | Нет | Отлично | Первичные ключи в БД, новые проекты |
| v8 | Custom | Зависит | Зависит | Зависит | Зависит | Специфичные требования |
Производительность в базах данных
Разные версии UUID по-разному влияют на производительность БД. Проблема в том, что UUID занимает 16 байт и случайный порядок убивает B-tree индексы.
Тест на PostgreSQL (вставка 1М записей)
-- Создаем таблицы с разными типами ID
-- Auto-increment (эталон)
CREATE TABLE users_serial (
id SERIAL PRIMARY KEY,
name VARCHAR(100),
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- UUIDv4 (случайный)
CREATE TABLE users_uuid4 (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(100),
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- UUIDv7 (timestamp-based)
CREATE TABLE users_uuid7 (
id UUID PRIMARY KEY, -- генерируется на уровне приложения
name VARCHAR(100),
created_at TIMESTAMPTZ DEFAULT NOW()
);
Результаты (вставка 1М записей):
| Тип ID | Время вставки | Размер индекса | Фрагментация |
|---|---|---|---|
| SERIAL | 12 сек | 21 MB | 0% |
| UUIDv4 | 45 сек | 42 MB | 85% |
| UUIDv7 | 15 сек | 23 MB | 5% |
Выводы:
- UUIDv7 почти такой же быстрый, как SERIAL (немного медленнее из-за размера)
- UUIDv4 в 3-4 раза медленнее из-за фрагментации индекса
- UUIDv7 оптимален для распределенных систем с UUID
Оптимизация хранения UUID в PostgreSQL
PostgreSQL хранит UUID как 16-байтовый тип, что эффективнее строки (36 байт). Но есть нюансы:
-- Плохо (строка, 36 байт)
CREATE TABLE users (
id VARCHAR(36) PRIMARY KEY
);
-- Хорошо (нативный UUID, 16 байт)
CREATE TABLE users (
id UUID PRIMARY KEY
);
-- Еще лучше (UUIDv7 для сортировки)
CREATE TABLE users (
id UUID PRIMARY KEY, -- v7 из приложения
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Индексы работают отлично
CREATE INDEX idx_users_created ON users(created_at);
MySQL и UUID
MySQL до версии 8.0 не имел нативного типа UUID. В 8.0+ появилась функция UUID(), но она генерирует v1 в нестандартном формате.
-- MySQL 8.0+
SELECT UUID(); -- 3e8a6c90-d5e5-11ec-8a46-0242ac120002
-- Лучше использовать BINARY(16)
CREATE TABLE users (
id BINARY(16) PRIMARY KEY,
name VARCHAR(100),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Конвертация UUID в BINARY для хранения
INSERT INTO users (id, name)
VALUES (UNHEX(REPLACE(UUID(), '-', '')), 'John Doe');
-- Чтение обратно
SELECT HEX(id), name FROM users;
Для MySQL рекомендуется:
- Хранить UUID как
BINARY(16)(экономия памяти) - Использовать UUIDv7 для сортировки
- Конвертировать через
UNHEX()при вставке
ULID как альтернатива UUID
ULID (Universally Unique Lexicographically Sortable Identifier) — альтернатива UUID, созданная специально для баз данных.
01ARZ3NDEKTSV4RRFFQ69G5FAV
Преимущества ULID:
- Лексикографически сортируемый (timestamp в начале)
- Короче в строковом виде (26 символов vs 36 для UUID)
- Base32 кодирование (URL-safe)
- Монотонность в пределах миллисекунды
Формат:
- 48 бит timestamp (milliseconds)
- 80 бит случайности
ULID по сути то же самое, что UUIDv7, но в другой кодировке. Для новых проектов можно использовать любой из них.
// Go
import "github.com/oklog/ulid/v2"
entropy := rand.New(rand.NewSource(time.Now().UnixNano()))
id := ulid.MustNew(ulid.Timestamp(time.Now()), entropy)
fmt.Println(id) // 01ARZ3NDEKTSV4RRFFQ69G5FAV
# Python
from ulid import ULID
ulid = ULID()
print(ulid) # 01ARZ3NDEKTSV4RRFFQ69G5FAV
Best Practices
Для баз данных:
- Используй UUIDv7 (или ULID) для первичных ключей — лучшая производительность
- Не используй UUIDv4 для первичных ключей в высоконагруженных системах
- В PostgreSQL используй тип
UUID, неVARCHAR(36) - В MySQL храни как
BINARY(16) - Создавай индексы на часто запрашиваемых полях
Для API:
- Используй UUIDv4 для публичных ID — безопасность
- Не раскрывай внутренние ID (auto-increment) в API
- Валидируй UUID на входе (формат и версия)
- Используй name-based UUID (v5) для идемпотентности
Для распределенных систем:
- Используй UUIDv7 — сортируется и безопасен
- Генерируй на стороне клиента (offline-first)
- Не полагайся на timestamp для бизнес-логики (может быть не точным)
- Синхронизируй часы между серверами (NTP)
Для безопасности:
- Используй UUIDv4 для токенов и сессий
- Не используй v1/v6 в публичных API (раскрывают MAC)
- Не полагайся на непредсказуемость name-based UUID (v3/v5)
- Используй cryptographically secure random для v4
Миграция с auto-increment на UUID
Если у тебя уже есть система с auto-increment ID и хочешь мигрировать на UUID, вот стратегия:
Вариант 1: Добавить новое поле
-- Добавляем UUID колонку
ALTER TABLE users ADD COLUMN uuid UUID;
-- Генерируем UUID для существующих записей
UPDATE users SET uuid = gen_random_uuid() WHERE uuid IS NULL;
-- Делаем NOT NULL
ALTER TABLE users ALTER COLUMN uuid SET NOT NULL;
-- Создаем уникальный индекс
CREATE UNIQUE INDEX idx_users_uuid ON users(uuid);
-- Постепенно переключаем приложение на использование UUID
-- Потом можно удалить старую колонку id
Вариант 2: Name-based UUID (сохранить детерминированность)
import uuid
def migrate_id_to_uuid(old_id: int) -> uuid.UUID:
namespace = uuid.UUID('6ba7b810-9dad-11d1-80b4-00c04fd430c8')
return uuid.uuid5(namespace, f"user:{old_id}")
# В SQL
UPDATE users SET uuid = uuid_generate_v5(
'6ba7b810-9dad-11d1-80b4-00c04fd430c8'::uuid,
'user:' || id::text
);
Так старый ID всегда можно превратить в тот же UUID.
Практические примеры использования
Пример 1: Микросервисная архитектура (Go)
package main
import (
"fmt"
"time"
"github.com/gofrs/uuid"
)
type Order struct {
ID uuid.UUID
UserID uuid.UUID
CreatedAt time.Time
}
func NewOrder(userID uuid.UUID) (*Order, error) {
// Генерируем UUIDv7 для оптимальной производительности БД
orderID, err := uuid.NewV7()
if err != nil {
return nil, err
}
return &Order{
ID: orderID,
UserID: userID,
CreatedAt: time.Now(),
}, nil
}
func main() {
// UUIDv7 автоматически сортируется по времени создания
order1, _ := NewOrder(uuid.Must(uuid.NewV4()))
time.Sleep(10 * time.Millisecond)
order2, _ := NewOrder(uuid.Must(uuid.NewV4()))
fmt.Println(order1.ID.String()) // 018d3f51-8b00-7000-9000-123456789abc
fmt.Println(order2.ID.String()) // 018d3f51-8b20-7000-9000-234567890bcd
// Сортируются лексикографически по времени
}
Пример 2: Дедупликация контента (Python)
import uuid
class ContentDeduplicator:
def __init__(self):
self.namespace = uuid.UUID('6ba7b810-9dad-11d1-80b4-00c04fd430c8')
def get_content_id(self, content: str) -> uuid.UUID:
# Генерируем детерминированный UUID из содержимого
return uuid.uuid5(self.namespace, content)
def is_duplicate(self, content: str, seen_ids: set) -> bool:
content_id = self.get_content_id(content)
if content_id in seen_ids:
return True
seen_ids.add(content_id)
return False
# Использование
dedup = ContentDeduplicator()
seen = set()
articles = [
"Hello, world!",
"Hello, world!", # Дубликат
"Different content"
]
for article in articles:
if dedup.is_duplicate(article, seen):
print(f"Duplicate: {article[:20]}...")
else:
print(f"New: {article[:20]}...")
Пример 3: API с идемпотентностью (JavaScript/TypeScript)
import { v5 as uuidv5, v4 as uuidv4 } from 'uuid';
class OrderService {
private namespace = '6ba7b810-9dad-11d1-80b4-00c04fd430c8';
// Идемпотентное создание заказа
async createOrder(userId: string, items: any[], idempotencyKey?: string) {
let orderId: string;
if (idempotencyKey) {
// Используем name-based UUID для идемпотентности
orderId = uuidv5(idempotencyKey, this.namespace);
// Проверяем, существует ли уже заказ
const existing = await this.findOrderById(orderId);
if (existing) {
return existing; // Возвращаем существующий
}
} else {
// Генерируем новый случайный UUID
orderId = uuidv4();
}
// Создаем заказ
return await this.saveOrder({ id: orderId, userId, items });
}
private async findOrderById(id: string) {
// Логика поиска в БД
}
private async saveOrder(order: any) {
// Логика сохранения
}
}
// Использование
const service = new OrderService();
// С idempotency key — всегда создаст один заказ
await service.createOrder('user123', [item1, item2], 'request-abc-123');
await service.createOrder('user123', [item1, item2], 'request-abc-123'); // Вернет тот же
// Без idempotency key — создаст новый каждый раз
await service.createOrder('user123', [item1, item2]);
await service.createOrder('user123', [item1, item2]); // Создаст второй
Заключение
UUID — мощный инструмент для распределенных систем и микросервисной архитектуры. Но выбор версии имеет значение:
Для баз данных:
- Используй UUIDv7 (timestamp-based, сортируемый) — лучшая производительность
- Избегай UUIDv4 для первичных ключей в высоконагруженных системах
Для API:
- Используй UUIDv4 (случайный) — безопасность и непредсказуемость
- Используй UUIDv5 (name-based) для идемпотентности и кеширования
Для дедупликации:
- Используй UUIDv5 (name-based) — детерминированность
Для миграции:
- Используй UUIDv5 (name-based) — можно восстановить из старых ID
- Или добавь новое поле с UUIDv7 для оптимальной производительности
Начни с UUIDv7 для баз данных и UUIDv4 для всего остального — покроет 90% случаев. А если нужна специфика (дедупликация, идемпотентность), используй name-based UUIDv5.
Если разбираешься с микросервисами, почитай мою статью про внедрение gRPC в Go проект — там про то, как организовать коммуникацию между сервисами и шарить proto-файлы. А для автоматизации рутинных задач рекомендую Cursor AI — он отлично помогает с генерацией кода.
P.S. Не используй auto-increment ID в публичных API — легко перебрать все записи. Используй UUID. И не забывай про индексы на UUID-колонках.