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

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

Подробный разбор всех версий UUID (v1-v8): чем они отличаются, как работают, когда использовать каждую версию. Практические примеры генерации UUID на разных языках программирования, производительность и best practices для баз данных.

Работаешь с базами данных, 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 — для URL
  • NAMESPACE_OID — для ISO OID
  • NAMESPACE_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

ВерсияОсноваСортируемыйБезопасностьДетерминированныйДля БДКогда использовать
v1Timestamp + MACЧастичноНизкаяНетСреднеLegacy, когда нужен timestamp
v3MD5(namespace+name)НетНизкаяДаПлохоМиграция, дедупликация (лучше v5)
v4RandomНетВысокаяНетПлохоAPI токены, временные ID
v5SHA-1(namespace+name)НетСредняяДаПлохоДедупликация, кеширование
v6Timestamp + MAC (сорт.)ДаНизкаяНетХорошоМиграция с v1, когда нужна сортировка
v7Timestamp + RandomДаВысокаяНетОтличноПервичные ключи в БД, новые проекты
v8CustomЗависитЗависитЗависитЗависитСпецифичные требования

Производительность в базах данных

Разные версии 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Время вставкиРазмер индексаФрагментация
SERIAL12 сек21 MB0%
UUIDv445 сек42 MB85%
UUIDv715 сек23 MB5%

Выводы:

  • 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 рекомендуется:

  1. Хранить UUID как BINARY(16) (экономия памяти)
  2. Использовать UUIDv7 для сортировки
  3. Конвертировать через 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

Для баз данных:

  1. Используй UUIDv7 (или ULID) для первичных ключей — лучшая производительность
  2. Не используй UUIDv4 для первичных ключей в высоконагруженных системах
  3. В PostgreSQL используй тип UUID, не VARCHAR(36)
  4. В MySQL храни как BINARY(16)
  5. Создавай индексы на часто запрашиваемых полях

Для API:

  1. Используй UUIDv4 для публичных ID — безопасность
  2. Не раскрывай внутренние ID (auto-increment) в API
  3. Валидируй UUID на входе (формат и версия)
  4. Используй name-based UUID (v5) для идемпотентности

Для распределенных систем:

  1. Используй UUIDv7 — сортируется и безопасен
  2. Генерируй на стороне клиента (offline-first)
  3. Не полагайся на timestamp для бизнес-логики (может быть не точным)
  4. Синхронизируй часы между серверами (NTP)

Для безопасности:

  1. Используй UUIDv4 для токенов и сессий
  2. Не используй v1/v6 в публичных API (раскрывают MAC)
  3. Не полагайся на непредсказуемость name-based UUID (v3/v5)
  4. Используй 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-колонках.