68 lines
2.0 KiB
Go
68 lines
2.0 KiB
Go
// Package ulid provides thread-safe ULID (Universally Unique Lexicographically Sortable Identifier) generation.
|
|
//
|
|
// ULIDs are 128-bit identifiers that are lexicographically sortable and contain
|
|
// a timestamp component. This package uses a pool-based approach with
|
|
// cryptographically secure random seeding and monotonic ordering for optimal
|
|
// performance in concurrent environments.
|
|
package ulid
|
|
|
|
import (
|
|
cryptorand "crypto/rand"
|
|
"encoding/binary"
|
|
"io"
|
|
mathrand "math/rand"
|
|
"os"
|
|
"sync"
|
|
"time"
|
|
|
|
oklid "github.com/oklog/ulid/v2"
|
|
"go.ntppool.org/common/logger"
|
|
)
|
|
|
|
// monotonicPool is a pool of monotonic ULID readers for performance optimization.
|
|
// Each reader is initialized with a cryptographically secure random seed
|
|
// and random increment value to ensure uniqueness across concurrent usage.
|
|
var monotonicPool = sync.Pool{
|
|
New: func() any {
|
|
log := logger.Setup()
|
|
|
|
var seed int64
|
|
err := binary.Read(cryptorand.Reader, binary.BigEndian, &seed)
|
|
if err != nil {
|
|
log.Error("crypto/rand error", "err", err)
|
|
os.Exit(10)
|
|
}
|
|
|
|
rand := mathrand.New(mathrand.NewSource(seed))
|
|
|
|
inc := uint64(mathrand.Int63())
|
|
|
|
// log.Printf("seed: %d", seed)
|
|
// log.Printf("inc: %d", inc)
|
|
|
|
// inc = inc & ^uint64(1<<63) // only want 63 bits
|
|
mono := oklid.Monotonic(rand, inc)
|
|
return mono
|
|
},
|
|
}
|
|
|
|
// MakeULID generates a new ULID with the specified timestamp using a pooled monotonic reader.
|
|
// The function is thread-safe and optimized for high-concurrency environments.
|
|
//
|
|
// Each call retrieves a monotonic reader from the pool, generates a ULID with the
|
|
// given timestamp, and returns it. The reader is not returned to the pool as it
|
|
// maintains internal state for monotonic ordering.
|
|
//
|
|
// Returns a pointer to the generated ULID or an error if generation fails.
|
|
// Generation should only fail under extreme circumstances (entropy exhaustion).
|
|
func MakeULID(t time.Time) (*oklid.ULID, error) {
|
|
mono := monotonicPool.Get().(io.Reader)
|
|
|
|
id, err := oklid.New(oklid.Timestamp(t), mono)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &id, nil
|
|
}
|