Severity: major Commit:b2218dd — char -> uuid Затронутые места:
models/document.go строки 14–14
В чём проблема
Использование default:gen_random_uuid() в тегах GORM для первичного ключа может привести к тому, что GORM будет игнорировать сгенерированные базой данных значения при создании записи, если в структуре Go Go-поле ID имеет нулевое значение (zero value) для типа uuid.UUID. В PostgreSQL, если поле определено как uuid с дефолтным значением, но GORM отправляет 00000000-0000-0000-0000-000000000000 (нулевой UUID), база данных примет этот нулевой UUID вместо того, чтобы сгенерировать новый через gen_random_uuid().
Цитата кода
ID uuid.UUID `json:"id" gorm:"type:uuid;default:gen_random_uuid();primaryKey" example:"550e8400-e29b-41d4-a716-446655440000"`
Как исправить
Вариант 1 — Использовать default в миграции, но оставить поле в Go как указатель или использовать хук BeforeCreate для генерации UUID на стороне приложения, чтобы избежать отправки нулевого UUID в БД.
Это гарантирует, что ID будет сгенерирован либо в приложении, либо в БД без конфликтов с нулевыми значениями.
Вариант 2 — Использовательность type:uuid без default в тегах, и генерация UUID в коде приложения перед сохранением.
Это более надежный способ управления идентификаторами в Go-приложениях с использованием GORM.
ID uuid.UUID `json:"id" gorm:"type:uuid;primaryKey"`
Автоматическая проверка commit-keeper · модель gemma4:31b · sha=b2218dd
**Severity:** major
**Commit:** [`b2218dd`](https://git.gm6.ru/icewind/seadoc/commit/b2218dd1989bfef233bcac9657bb620c352dc1e8) — char -> uuid
**Затронутые места:**
- `models/document.go` строки 14–14
## В чём проблема
Использование `default:gen_random_uuid()` в тегах GORM для первичного ключа может привести к тому, что GORM будет игнорировать сгенерированные базой данных значения при создании записи, если в структуре Go Go-поле `ID` имеет нулевое значение (zero value) для типа `uuid.UUID`. В PostgreSQL, если поле определено как `uuid` с дефолтным значением, но GORM отправляет `00000000-0000-0000-0000-000000000000` (нулевой UUID), база данных примет этот нулевой UUID вместо того, чтобы сгенерировать новый через `gen_random_uuid()`.
## Цитата кода
```
ID uuid.UUID `json:"id" gorm:"type:uuid;default:gen_random_uuid();primaryKey" example:"550e8400-e29b-41d4-a716-446655440000"`
```
## Как исправить
### Вариант 1 — Использовать `default` в миграции, но оставить поле в Go как указатель или использовать хук `BeforeCreate` для генерации UUID на стороне приложения, чтобы избежать отправки нулевого UUID в БД.
Это гарантирует, что ID будет сгенерирован либо в приложении, либо в БД без конфликтов с нулевыми значениями.
```
func (d *Document) BeforeCreate(tx *gorm.DB) (err error) {
if d.ID == uuid.Nil {
d.ID = uuid.NewRandom()
}
return
}
```
### Вариант 2 — Использовательность `type:uuid` без `default` в тегах, и генерация UUID в коде приложения перед сохранением.
Это более надежный способ управления идентификаторами в Go-приложениях с использованием GORM.
```
ID uuid.UUID `json:"id" gorm:"type:uuid;primaryKey"`
```
---
<sub><sup>Автоматическая проверка commit-keeper · модель `gemma4:31b` · `sha=b2218dd`</sup></sub>
Вопрос о выборе между UUID и BIGSERIAL — это классический компромисс между производительностью и распределенностью/безопасностью.
Почему BIGSERIAL (Integer) может быть лучше:
Производительность индексов: Целые числа занимают меньше места (8 байт против 16 байт у UUID). Это приводит к более компактным B-tree индексам, что ускоряет поиск и уменьшает нагрузку на память (RAM).
Скорость вставки: Последовательные ID минимизируют фрагментацию страниц данных в PostgreSQL, так как новые записи всегда добавляются в конец индекса.
Почему UUID (как сейчас в коде) может быть предпочтительнее:
Безопасность и непредсказуемость: UUID невозможно «угадать» или перебрать (enumeration attack). Если ID документа попадает в URL, пользователь не сможет просто прибавить единицу к ID, чтобы получить доступ к чужому документу.
Распределенные системы: UUID можно генерировать на стороне клиента или в разных микросервисах без необходимости координировать генерацию с центральной базой данных.
Миграции и слияния: При объединении данных из разных БД конфликты первичных ключей практически исключены.
Итог:
Если приложение предполагает публичные ссылки на документы и высокую степень безопасности, UUID — правильный выбор. Если же важна максимальная пропускная способность БД и ID используются только внутри системы, BIGSERIAL будет эффективнее.
Однако, в контексте вашего текущего кода, если вы решили оставить UUID, рекомендую обратить внимание на проблему с default:gen_random_uuid(), описанную в issue, чтобы избежать записи нулевых UUID в базу.
Вопрос о выборе между UUID и BIGSERIAL — это классический компромисс между производительностью и распределенностью/безопасностью.
**Почему BIGSERIAL (Integer) может быть лучше:**
1. **Производительность индексов:** Целые числа занимают меньше места (8 байт против 16 байт у UUID). Это приводит к более компактным B-tree индексам, что ускоряет поиск и уменьшает нагрузку на память (RAM).
2. **Скорость вставки:** Последовательные ID минимизируют фрагментацию страниц данных в PostgreSQL, так как новые записи всегда добавляются в конец индекса.
**Почему UUID (как сейчас в коде) может быть предпочтительнее:**
1. **Безопасность и непредсказуемость:** UUID невозможно «угадать» или перебрать (enumeration attack). Если ID документа попадает в URL, пользователь не сможет просто прибавить единицу к ID, чтобы получить доступ к чужому документу.
2. **Распределенные системы:** UUID можно генерировать на стороне клиента или в разных микросервисах без необходимости координировать генерацию с центральной базой данных.
3. **Миграции и слияния:** При объединении данных из разных БД конфликты первичных ключей практически исключены.
**Итог:**
Если приложение предполагает публичные ссылки на документы и высокую степень безопасности, UUID — правильный выбор. Если же важна максимальная пропускная способность БД и ID используются только внутри системы, BIGSERIAL будет эффективнее.
Однако, в контексте вашего текущего кода, если вы решили оставить UUID, рекомендую обратить внимание на проблему с `default:gen_random_uuid()`, описанную в issue, чтобы избежать записи нулевых UUID в базу.
<!-- commit-keeper -->
UUIDv7 является отличным компромиссом между UUIDv4 (случайными) и BIGSERIAL (последовательными).
Основные преимущества UUIDv7:
Сортируемость (Time-ordered): В отличие от UUIDv4, первые 48 бит UUIDv7 содержат метку времени (timestamp). Это делает их фактически последовательными.
Производительность БД: Поскольку они сортируемы, PostgreSQL будет вставлять новые записи в конец B-tree индекса, а не в случайные места. Это решает главную проблему производительности UUID, о которой мы говорили ранее (фрагментация страниц и высокая нагрузка на RAM).
Совместимость: UUIDv7 полностью совместим с форматом UUID (128 бит), поэтому вам не нужно менять тип колонки в базе данных с uuid на что-то другое.
Безопасность: Несмотря на наличие временной метки, оставшаяся часть UUID заполнена случайными данными, что делает их достаточно непредсказуемыми.
Итог:
Если вы хотите сохранить преимущества UUID (безопасность, распределенность), но при этом получить производительность индексов, близкую к BIGSERIAL, то UUIDv7 — это лучший выбор на сегодняшний день.
Для реализации в Go можно использовать библиотеки вроде github.com/google/uuid (в новых версиях) или специализированные пакеты для UUIDv7. При этом проблема с default:gen_random_uuid() в GORM всё равно остается актуальной: если вы перейдете на UUIDv7, генерацию лучше перенести на сторону приложения (в хук BeforeCreate), так как стандартные функции PostgreSQL (до версии 17) не генерируют UUIDv7 «из коробки» без дополнительных расширений.
UUIDv7 является отличным компромиссом между UUIDv4 (случайными) и BIGSERIAL (последовательными).
**Основные преимущества UUIDv7:**
1. **Сортируемость (Time-ordered):** В отличие от UUIDv4, первые 48 бит UUIDv7 содержат метку времени (timestamp). Это делает их фактически последовательными.
2. **Производительность БД:** Поскольку они сортируемы, PostgreSQL будет вставлять новые записи в конец B-tree индекса, а не в случайные места. Это решает главную проблему производительности UUID, о которой мы говорили ранее (фрагментация страниц и высокая нагрузка на RAM).
3. **Совместимость:** UUIDv7 полностью совместим с форматом UUID (128 бит), поэтому вам не нужно менять тип колонки в базе данных с `uuid` на что-то другое.
4. **Безопасность:** Несмотря на наличие временной метки, оставшаяся часть UUID заполнена случайными данными, что делает их достаточно непредсказуемыми.
**Итог:**
Если вы хотите сохранить преимущества UUID (безопасность, распределенность), но при этом получить производительность индексов, близкую к BIGSERIAL, то **UUIDv7 — это лучший выбор на сегодняшний день**.
Для реализации в Go можно использовать библиотеки вроде `github.com/google/uuid` (в новых версиях) или специализированные пакеты для UUIDv7. При этом проблема с `default:gen_random_uuid()` в GORM всё равно остается актуальной: если вы перейдете на UUIDv7, генерацию лучше перенести на сторону приложения (в хук `BeforeCreate`), так как стандартные функции PostgreSQL (до версии 17) не генерируют UUIDv7 «из коробки» без дополнительных расширений.
<!-- commit-keeper -->
UUIDv7 — это отличный современный стандарт, который решает главную проблему классических случайных UUID (v4) в базах данных.
В чем его основное преимущество?
Главная проблема UUIDv4 в том, что они абсолютно случайны. При использовании их в качестве первичного ключа в PostgreSQL (B-tree индекс), новые записи вставляются в случайные места индекса. Это приводит к «раздуванию» индекса (index bloat) и постоянным перечитываниями страниц с диска в RAM, что резко снижает производительность при росте таблицы.
UUIDv7 решает это за счет сортируемости:
Timestamp в начале: Первые 48 бит UUIDv7 содержат текущее время (Unix timestamp в миллисекундах).
Локальность данных: Благодаря этому новые записи всегда добавляются в «конец» индекса, как и в случае с обычным BIGSERIAL. Это минимизирует фрагментацию и делает вставку максимально эффективной.
Совместимость: Он полностью совместим с типом uuid в PostgreSQL и любым полем uuid.UUID в Go.
Как это применить в вашем случае:
Если вы перейдете на UUIDv7, вам будет максимально удобно реализовать один из вариантов исправления, которые я предлагал в начале issue:
Используйте хук BeforeCreate в GORM, чтобы генерировать UUIDv7 на стороне приложения. Это решит и проблему с производительностью индексов, и проблему с «нулевыми UUID», которые GORM может отправить в базу при использовании default:gen_random_uuid().
Пример реализации (с использованием библиотеки github.com/google/uuid):
Итог: UUIDv7 — это «золотая середина». Вы получаете безопасность и уникальность UUID, но при этом сохраняете производительность вставки и поиска, сравнимую с обычными целыми числами.
UUIDv7 — это отличный современный стандарт, который решает главную проблему классических случайных UUID (v4) в базах данных.
**В чем его основное преимущество?**
Главная проблема UUIDv4 в том, что они абсолютно случайны. При использовании их в качестве первичного ключа в PostgreSQL (B-tree индекс), новые записи вставляются в случайные места индекса. Это приводит к «раздуванию» индекса (index bloat) и постоянным перечитываниями страниц с диска в RAM, что резко снижает производительность при росте таблицы.
**UUIDv7 решает это за счет сортируемости:**
1. **Timestamp в начале:** Первые 48 бит UUIDv7 содержат текущее время (Unix timestamp в миллисекундах).
2. **Локальность данных:** Благодаря этому новые записи всегда добавляются в «конец» индекса, как и в случае с обычным `BIGSERIAL`. Это минимизирует фрагментацию и делает вставку максимально эффективной.
3. **Совместимость:** Он полностью совместим с типом `uuid` в PostgreSQL и любым полем `uuid.UUID` в Go.
**Как это применить в вашем случае:**
Если вы перейдете на UUIDv7, вам будет максимально удобно реализовать один из вариантов исправления, которые я предлагал в начале issue:
Используйте хук `BeforeCreate` в GORM, чтобы генерировать UUIDv7 на стороне приложения. Это решит и проблему с производительностью индексов, и проблему с «нулевыми UUID», которые GORM может отправить в базу при использовании `default:gen_random_uuid()`.
Пример реализации (с использованием библиотеки `github.com/google/uuid`):
```go
func (d *Document) BeforeCreate(tx *gorm.DB) (err error) {
// Генерируем UUIDv7
id, err := uuid.NewV7()
if err != nil {
return err
}
d.ID = id
return
}
```
**Итог:** UUIDv7 — это «золотая середина». Вы получаете безопасность и уникальность UUID, но при этом сохраняете производительность вставки и поиска, сравнимую с обычными целыми числами.
<!-- commit-keeper -->
Исходя из нашего обсуждения, мой итоговый совет зависит от того, какие приоритеты для вас сейчас важнее:
1. Самый современный и сбалансированный вариант (Рекомендую): UUIDv7
Если вы хотите сохранить преимущества UUID (безопасность, отсутствие перебора ID в URL, удобство распределенных систем), но при этом не терять в производительности БД.
Что сделать: Удалить default:gen_random_uuid() из тегов GORM и добавить хук BeforeCreate, который генерирует UUIDv7 (например, через uuid.NewV7() из библиотеки google/uuid).
Плюс: Высокая производительность индексов (как у BIGSERIAL) + безопасность UUID.
2. Самый простой и производительный вариант: BIGSERIAL (Integer)
Если приложение будет использоваться в закрытом контуре, или ID не будут видны пользователям в URL, и вам нужна максимальная скорость работы БД «из коробки».
Что сделать: Сменить тип поля с uuid.UUID на uint64 и использовать gorm:"primaryKey".
Плюс: Максимальная скорость, минимальный размер индекса.
3. Минимальное исправление текущего кода: UUIDv4 (через приложение)
Если вы хотите оставить всё как есть, но просто исправить баг с «нулевыми UUID».
Что сделать: Оставить тип uuid.UUID, но перенести генерацию из БД в Go-код (хук BeforeCreate с uuid.NewRandom()).
Плюс: Быстрое исправление без смены архитектуры.
Мой вердикт:
Я рекомендую UUIDv7. Это «золотая середина», которая решает проблему фрагментации индексов в PostgreSQL и одновременно избавляет вас от конфликта GORM с дефолтными значениями БД. Это решение на перспективу, которое избавит вас от необходимости мигрировать с UUID на Integer при росте базы данных.
Исходя из нашего обсуждения, мой итоговый совет зависит от того, какие приоритеты для вас сейчас важнее:
**1. Самый современный и сбалансированный вариант (Рекомендую): UUIDv7**
Если вы хотите сохранить преимущества UUID (безопасность, отсутствие перебора ID в URL, удобство распределенных систем), но при этом не терять в производительности БД.
- **Что сделать:** Удалить `default:gen_random_uuid()` из тегов GORM и добавить хук `BeforeCreate`, который генерирует UUIDv7 (например, через `uuid.NewV7()` из библиотеки `google/uuid`).
- **Плюс:** Высокая производительность индексов (как у BIGSERIAL) + безопасность UUID.
**2. Самый простой и производительный вариант: BIGSERIAL (Integer)**
Если приложение будет использоваться в закрытом контуре, или ID не будут видны пользователям в URL, и вам нужна максимальная скорость работы БД «из коробки».
- **Что сделать:** Сменить тип поля с `uuid.UUID` на `uint64` и использовать `gorm:"primaryKey"`.
- **Плюс:** Максимальная скорость, минимальный размер индекса.
**3. Минимальное исправление текущего кода: UUIDv4 (через приложение)**
Если вы хотите оставить всё как есть, но просто исправить баг с «нулевыми UUID».
- **Что сделать:** Оставить тип `uuid.UUID`, но перенести генерацию из БД в Go-код (хук `BeforeCreate` с `uuid.NewRandom()`).
- **Плюс:** Быстрое исправление без смены архитектуры.
**Мой вердикт:**
Я рекомендую **UUIDv7**. Это «золотая середина», которая решает проблему фрагментации индексов в PostgreSQL и одновременно избавляет вас от конфликта GORM с дефолтными значениями БД. Это решение на перспективу, которое избавит вас от необходимости мигрировать с UUID на Integer при росте базы данных.
<!-- commit-keeper -->
Для перехода на UUIDv7 и решения проблемы с нулевыми значениями в GORM, вам потребуется сделать следующее:
Обновить зависимости: Убедитесь, что используете актуальную версию библиотеки github.com/google/uuid (версия 1.6.0 и выше поддерживает UUIDv7).
Изменить модель: Удалите default:gen_random_uuid() из тегов GORM, так как генерация теперь будет происходить на стороне приложения.
Добавить хук BeforeCreate: Реализуйте метод для автоматической генерации ID перед сохранением записи.
Вот полный пример кода для models/document.go:
packagemodelsimport("github.com/google/uuid""gorm.io/gorm")typeDocumentstruct{// Удаляем default:gen_random_uuid(), оставляем только тип и primaryKeyIDuuid.UUID`json:"id" gorm:"type:uuid;primaryKey" example:"550e8400-e29b-41d4-a716-446655440000"`// ... остальные поля ...}// BeforeCreate — хук GORM, который срабатывает перед вставкой записи в БДfunc(d*Document)BeforeCreate(tx*gorm.DB)(errerror){// Генерируем UUIDv7 (сортируемый по времени)id,err:=uuid.NewV7()iferr!=nil{returnerr}d.ID=idreturnnil}
Почему это решает проблему:
Нет конфликта с GORM: Поскольку мы явно присваиваем значение d.ID в коде до того, как GORM отправит запрос в базу, GORM не будет отправлять «нулевой UUID» (0000...), и база данных примет сгенерированный нами UUIDv7.
Производительность: Благодаря структуре UUIDv7, PostgreSQL будет вставлять записи последовательно, что предотвратит фрагментацию индексов и обеспечит высокую скорость работы даже при больших объемах данных.
Для перехода на UUIDv7 и решения проблемы с нулевыми значениями в GORM, вам потребуется сделать следующее:
1. **Обновить зависимости**: Убедитесь, что используете актуальную версию библиотеки `github.com/google/uuid` (версия 1.6.0 и выше поддерживает UUIDv7).
2. **Изменить модель**: Удалите `default:gen_random_uuid()` из тегов GORM, так как генерация теперь будет происходить на стороне приложения.
3. **Добавить хук `BeforeCreate`**: Реализуйте метод для автоматической генерации ID перед сохранением записи.
Вот полный пример кода для `models/document.go`:
```go
package models
import (
"github.com/google/uuid"
"gorm.io/gorm"
)
type Document struct {
// Удаляем default:gen_random_uuid(), оставляем только тип и primaryKey
ID uuid.UUID `json:"id" gorm:"type:uuid;primaryKey" example:"550e8400-e29b-41d4-a716-446655440000"`
// ... остальные поля ...
}
// BeforeCreate — хук GORM, который срабатывает перед вставкой записи в БД
func (d *Document) BeforeCreate(tx *gorm.DB) (err error) {
// Генерируем UUIDv7 (сортируемый по времени)
id, err := uuid.NewV7()
if err != nil {
return err
}
d.ID = id
return nil
}
```
**Почему это решает проблему:**
- **Нет конфликта с GORM**: Поскольку мы явно присваиваем значение `d.ID` в коде до того, как GORM отправит запрос в базу, GORM не будет отправлять «нулевой UUID» (`0000...`), и база данных примет сгенерированный нами UUIDv7.
- **Производительность**: Благодаря структуре UUIDv7, PostgreSQL будет вставлять записи последовательно, что предотвратит фрагментацию индексов и обеспечит высокую скорость работы даже при больших объемах данных.
<!-- commit-keeper -->
Для перехода на UUIDv7 и полного решения проблемы с «нулевыми UUID» в GORM, вам нужно внести изменения в файл models/document.go.
Вот готовый код фикса:
packagemodelsimport("github.com/google/uuid""gorm.io/gorm")typeDocumentstruct{// Удаляем default:gen_random_uuid(), так как теперь генерируем ID в приложенииIDuuid.UUID`json:"id" gorm:"type:uuid;primaryKey" example:"550e8400-e29b-41d4-a716-446655440000"`// ... остальные поля структуры ...}// BeforeCreate — хук GORM, который гарантирует генерацию UUIDv7 перед сохранением в БД.// Это предотвращает отправку нулевого UUID (00000000-0000-0000-0000-000000000000),// который иначе перекрыл бы default-значение в PostgreSQL.func(d*Document)BeforeCreate(tx*gorm.DB)(errerror){id,err:=uuid.NewV7()iferr!=nil{returnerr}d.ID=idreturnnil}
Что именно было сделано:
Изменение тега GORM: Мы убрали default:gen_random_uuid(). Если оставить этот тег, но при этом поле в Go будет иметь значение по умолчанию (нулевой UUID), GORM может отправить этот ноль в базу, и база данных запишет именно его, проигнорировав функцию генерации.
Добавление хука BeforeCreate: Теперь идентификатор генерируется на стороне Go-приложения с помощью uuid.NewV7(). Это гарантирует, что в БД всегда уходит валидный, сортируемый по времени UUID.
Важное примечание по зависимостям:
Убедитесь, что в вашем go.mod используется версия github.com/google/uuid версии 1.6.0 или выше, так как поддержка UUIDv7 появилась именно в этих версиях. Если версия ниже, обновите её командой: go get github.com/google/uuid@latest
Для перехода на UUIDv7 и полного решения проблемы с «нулевыми UUID» в GORM, вам нужно внести изменения в файл `models/document.go`.
Вот готовый код фикса:
```go
package models
import (
"github.com/google/uuid"
"gorm.io/gorm"
)
type Document struct {
// Удаляем default:gen_random_uuid(), так как теперь генерируем ID в приложении
ID uuid.UUID `json:"id" gorm:"type:uuid;primaryKey" example:"550e8400-e29b-41d4-a716-446655440000"`
// ... остальные поля структуры ...
}
// BeforeCreate — хук GORM, который гарантирует генерацию UUIDv7 перед сохранением в БД.
// Это предотвращает отправку нулевого UUID (00000000-0000-0000-0000-000000000000),
// который иначе перекрыл бы default-значение в PostgreSQL.
func (d *Document) BeforeCreate(tx *gorm.DB) (err error) {
id, err := uuid.NewV7()
if err != nil {
return err
}
d.ID = id
return nil
}
```
**Что именно было сделано:**
1. **Изменение тега GORM**: Мы убрали `default:gen_random_uuid()`. Если оставить этот тег, но при этом поле в Go будет иметь значение по умолчанию (нулевой UUID), GORM может отправить этот ноль в базу, и база данных запишет именно его, проигнорировав функцию генерации.
2. **Добавление хука `BeforeCreate`**: Теперь идентификатор генерируется на стороне Go-приложения с помощью `uuid.NewV7()`. Это гарантирует, что в БД всегда уходит валидный, сортируемый по времени UUID.
**Важное примечание по зависимостям:**
Убедитесь, что в вашем `go.mod` используется версия `github.com/google/uuid` версии **1.6.0 или выше**, так как поддержка UUIDv7 появилась именно в этих версиях. Если версия ниже, обновите её командой:
`go get github.com/google/uuid@latest`
<!-- commit-keeper -->
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Severity: major
Commit:
b2218dd— char -> uuidЗатронутые места:
models/document.goстроки 14–14В чём проблема
Использование
default:gen_random_uuid()в тегах GORM для первичного ключа может привести к тому, что GORM будет игнорировать сгенерированные базой данных значения при создании записи, если в структуре Go Go-полеIDимеет нулевое значение (zero value) для типаuuid.UUID. В PostgreSQL, если поле определено какuuidс дефолтным значением, но GORM отправляет00000000-0000-0000-0000-000000000000(нулевой UUID), база данных примет этот нулевой UUID вместо того, чтобы сгенерировать новый черезgen_random_uuid().Цитата кода
Как исправить
Вариант 1 — Использовать
defaultв миграции, но оставить поле в Go как указатель или использовать хукBeforeCreateдля генерации UUID на стороне приложения, чтобы избежать отправки нулевого UUID в БД.Это гарантирует, что ID будет сгенерирован либо в приложении, либо в БД без конфликтов с нулевыми значениями.
Вариант 2 — Использовательность
type:uuidбезdefaultв тегах, и генерация UUID в коде приложения перед сохранением.Это более надежный способ управления идентификаторами в Go-приложениях с использованием GORM.
Автоматическая проверка commit-keeper · модель
gemma4:31b·sha=b2218ddА почему именно UUID, а не serial integer? Не лучше ли использовать BIGSERIAL для производительности индексов?
Вопрос о выборе между UUID и BIGSERIAL — это классический компромисс между производительностью и распределенностью/безопасностью.
Почему BIGSERIAL (Integer) может быть лучше:
Почему UUID (как сейчас в коде) может быть предпочтительнее:
Итог:
Если приложение предполагает публичные ссылки на документы и высокую степень безопасности, UUID — правильный выбор. Если же важна максимальная пропускная способность БД и ID используются только внутри системы, BIGSERIAL будет эффективнее.
Однако, в контексте вашего текущего кода, если вы решили оставить UUID, рекомендую обратить внимание на проблему с
default:gen_random_uuid(), описанную в issue, чтобы избежать записи нулевых UUID в базу.а что скажешь об UUID7?
UUIDv7 является отличным компромиссом между UUIDv4 (случайными) и BIGSERIAL (последовательными).
Основные преимущества UUIDv7:
uuidна что-то другое.Итог:
Если вы хотите сохранить преимущества UUID (безопасность, распределенность), но при этом получить производительность индексов, близкую к BIGSERIAL, то UUIDv7 — это лучший выбор на сегодняшний день.
Для реализации в Go можно использовать библиотеки вроде
github.com/google/uuid(в новых версиях) или специализированные пакеты для UUIDv7. При этом проблема сdefault:gen_random_uuid()в GORM всё равно остается актуальной: если вы перейдете на UUIDv7, генерацию лучше перенести на сторону приложения (в хукBeforeCreate), так как стандартные функции PostgreSQL (до версии 17) не генерируют UUIDv7 «из коробки» без дополнительных расширений.так что в итоге посоветуешь?
UUIDv7 — это отличный современный стандарт, который решает главную проблему классических случайных UUID (v4) в базах данных.
В чем его основное преимущество?
Главная проблема UUIDv4 в том, что они абсолютно случайны. При использовании их в качестве первичного ключа в PostgreSQL (B-tree индекс), новые записи вставляются в случайные места индекса. Это приводит к «раздуванию» индекса (index bloat) и постоянным перечитываниями страниц с диска в RAM, что резко снижает производительность при росте таблицы.
UUIDv7 решает это за счет сортируемости:
BIGSERIAL. Это минимизирует фрагментацию и делает вставку максимально эффективной.uuidв PostgreSQL и любым полемuuid.UUIDв Go.Как это применить в вашем случае:
Если вы перейдете на UUIDv7, вам будет максимально удобно реализовать один из вариантов исправления, которые я предлагал в начале issue:
Используйте хук
BeforeCreateв GORM, чтобы генерировать UUIDv7 на стороне приложения. Это решит и проблему с производительностью индексов, и проблему с «нулевыми UUID», которые GORM может отправить в базу при использованииdefault:gen_random_uuid().Пример реализации (с использованием библиотеки
github.com/google/uuid):Итог: UUIDv7 — это «золотая середина». Вы получаете безопасность и уникальность UUID, но при этом сохраняете производительность вставки и поиска, сравнимую с обычными целыми числами.
Исходя из нашего обсуждения, мой итоговый совет зависит от того, какие приоритеты для вас сейчас важнее:
1. Самый современный и сбалансированный вариант (Рекомендую): UUIDv7
Если вы хотите сохранить преимущества UUID (безопасность, отсутствие перебора ID в URL, удобство распределенных систем), но при этом не терять в производительности БД.
default:gen_random_uuid()из тегов GORM и добавить хукBeforeCreate, который генерирует UUIDv7 (например, черезuuid.NewV7()из библиотекиgoogle/uuid).2. Самый простой и производительный вариант: BIGSERIAL (Integer)
Если приложение будет использоваться в закрытом контуре, или ID не будут видны пользователям в URL, и вам нужна максимальная скорость работы БД «из коробки».
uuid.UUIDнаuint64и использоватьgorm:"primaryKey".3. Минимальное исправление текущего кода: UUIDv4 (через приложение)
Если вы хотите оставить всё как есть, но просто исправить баг с «нулевыми UUID».
uuid.UUID, но перенести генерацию из БД в Go-код (хукBeforeCreateсuuid.NewRandom()).Мой вердикт:
Я рекомендую UUIDv7. Это «золотая середина», которая решает проблему фрагментации индексов в PostgreSQL и одновременно избавляет вас от конфликта GORM с дефолтными значениями БД. Это решение на перспективу, которое избавит вас от необходимости мигрировать с UUID на Integer при росте базы данных.
тогда остановимся на UUID7 - дай код фикса нашей проблемы
Для перехода на UUIDv7 и решения проблемы с нулевыми значениями в GORM, вам потребуется сделать следующее:
Обновить зависимости: Убедитесь, что используете актуальную версию библиотеки
github.com/google/uuid(версия 1.6.0 и выше поддерживает UUIDv7).Изменить модель: Удалите
default:gen_random_uuid()из тегов GORM, так как генерация теперь будет происходить на стороне приложения.Добавить хук
BeforeCreate: Реализуйте метод для автоматической генерации ID перед сохранением записи.Вот полный пример кода для
models/document.go:Почему это решает проблему:
d.IDв коде до того, как GORM отправит запрос в базу, GORM не будет отправлять «нулевой UUID» (0000...), и база данных примет сгенерированный нами UUIDv7.Для перехода на UUIDv7 и полного решения проблемы с «нулевыми UUID» в GORM, вам нужно внести изменения в файл
models/document.go.Вот готовый код фикса:
Что именно было сделано:
default:gen_random_uuid(). Если оставить этот тег, но при этом поле в Go будет иметь значение по умолчанию (нулевой UUID), GORM может отправить этот ноль в базу, и база данных запишет именно его, проигнорировав функцию генерации.BeforeCreate: Теперь идентификатор генерируется на стороне Go-приложения с помощьюuuid.NewV7(). Это гарантирует, что в БД всегда уходит валидный, сортируемый по времени UUID.Важное примечание по зависимостям:
Убедитесь, что в вашем
go.modиспользуется версияgithub.com/google/uuidверсии 1.6.0 или выше, так как поддержка UUIDv7 появилась именно в этих версиях. Если версия ниже, обновите её командой:go get github.com/google/uuid@latest