feat(v2): make bit assignment for time/sequence/machine customizable … (#68)

* feat(v2): make bit assignment for time/sequence/machine customizable via Settings; update all logic and tests

* gofmt
This commit is contained in:
Yoshiyuki Mineo
2025-05-05 14:16:21 +09:00
committed by GitHub
parent 357b2ee5f0
commit 7ee8f154df
4 changed files with 150 additions and 70 deletions
+1 -1
View File
@@ -28,7 +28,7 @@ func handler(w http.ResponseWriter, r *http.Request) {
return
}
body, err := json.Marshal(sonyflake.Decompose(id))
body, err := json.Marshal(sf.Decompose(id))
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
+97 -50
View File
@@ -1,6 +1,6 @@
// Package sonyflake implements Sonyflake, a distributed unique ID generator inspired by Twitter's Snowflake.
//
// A Sonyflake ID is composed of
// By default, a Sonyflake ID is composed of
//
// 39 bits for time in units of 10 msec
// 8 bits for a sequence number
@@ -16,18 +16,19 @@ import (
"github.com/sony/sonyflake/v2/types"
)
// These constants are the bit lengths of Sonyflake ID parts.
const (
BitLenTime = 39 // bit length of time
BitLenSequence = 8 // bit length of sequence number
BitLenMachine = 63 - BitLenTime - BitLenSequence // bit length of machine id
)
// Settings configures Sonyflake:
//
// BitsSequence is the bit length of a sequence number.
// If BitsSequence is 0, the default bit length is used, which is 8.
// If BitsSequence is 31 or more, an error is returned.
//
// BitsMachineID is the bit length of a machine ID.
// If BitsMachineID is 0, the default bit length is used, which is 16.
// If BitsMachineID is 31 or more, an error is returned.
//
// TimeUnit is the time unit of Sonyflake.
// If TimeUnit is 0, the default time unit is used, which is 10 msec.
// TimeUnit must be equal to or greater than 1 msec.
// TimeUnit must be 1 msec or longer.
//
// StartTime is the time since which the Sonyflake time is defined as the elapsed time.
// If StartTime is 0, the start time of the Sonyflake instance is set to "2025-01-01 00:00:00 +0000 UTC".
@@ -40,7 +41,12 @@ const (
// CheckMachineID validates the uniqueness of a machine ID.
// If CheckMachineID returns false, the instance will not be created.
// If CheckMachineID is nil, no validation is done.
//
// The bit length of time is calculated by 63 - BitsSequence - BitsMachineID.
// If it is less than 32, an error is returned.
type Settings struct {
BitsSequence int
BitsMachineID int
TimeUnit time.Duration
StartTime time.Time
MachineID func() (int, error)
@@ -49,46 +55,88 @@ type Settings struct {
// Sonyflake is a distributed unique ID generator.
type Sonyflake struct {
mutex *sync.Mutex
mutex *sync.Mutex
bitsTime int
bitsSequence int
bitsMachine int
timeUnit int64
startTime int64
elapsedTime int64
sequence int
machine int
sequence int
machine int
}
var (
ErrStartTimeAhead = errors.New("start time is ahead of now")
ErrNoPrivateAddress = errors.New("no private ip address")
ErrOverTimeLimit = errors.New("over the time limit")
ErrInvalidMachineID = errors.New("invalid machine id")
ErrInvalidTimeUnit = errors.New("invalid time unit")
ErrInvalidBitsTime = errors.New("bit length for time must be 32 or more")
ErrInvalidBitsSequence = errors.New("invalid bit length for sequence number")
ErrInvalidBitsMachineID = errors.New("invalid bit length for machine id")
ErrInvalidTimeUnit = errors.New("invalid time unit")
ErrInvalidMachineID = errors.New("invalid machine id")
ErrStartTimeAhead = errors.New("start time is ahead of now")
ErrOverTimeLimit = errors.New("over the time limit")
ErrNoPrivateAddress = errors.New("no private ip address")
)
const defaultTimeUnit = 1e7 // nsec, i.e. 10 msec
const (
defaultTimeUnit = 1e7 // nsec, i.e. 10 msec
defaultBitsTime = 39
defaultBitsSequence = 8
defaultBitsMachine = 16
)
var defaultInterfaceAddrs = net.InterfaceAddrs
// New returns a new Sonyflake configured with the given Settings.
// New returns an error in the following cases:
// - Settings.BitsSequence is less than 0 or greater than 30.
// - Settings.BitsMachineID is less than 0 or greater than 30.
// - Settings.BitsSequence + Settings.BitsMachineID is 32 or more.
// - Settings.TimeUnit is less than 1 msec.
// - Settings.StartTime is ahead of the current time.
// - Settings.MachineID returns an error.
// - Settings.CheckMachineID returns false.
func New(st Settings) (*Sonyflake, error) {
if st.BitsSequence < 0 || st.BitsSequence > 30 {
return nil, ErrInvalidBitsSequence
}
if st.BitsMachineID < 0 || st.BitsMachineID > 30 {
return nil, ErrInvalidBitsMachineID
}
if st.TimeUnit < 0 || (st.TimeUnit > 0 && st.TimeUnit < time.Millisecond) {
return nil, ErrInvalidTimeUnit
}
if st.StartTime.After(time.Now()) {
return nil, ErrStartTimeAhead
}
sf := new(Sonyflake)
sf.mutex = new(sync.Mutex)
sf.sequence = 1<<BitLenSequence - 1
if st.BitsSequence == 0 {
sf.bitsSequence = defaultBitsSequence
} else {
sf.bitsSequence = st.BitsSequence
}
if st.BitsMachineID == 0 {
sf.bitsMachine = defaultBitsMachine
} else {
sf.bitsMachine = st.BitsMachineID
}
sf.bitsTime = 63 - sf.bitsSequence - sf.bitsMachine
if sf.bitsTime < 32 {
return nil, ErrInvalidBitsTime
}
if st.TimeUnit == 0 {
sf.timeUnit = defaultTimeUnit
} else if st.TimeUnit >= time.Millisecond {
sf.timeUnit = int64(st.TimeUnit)
} else {
return nil, ErrInvalidTimeUnit
sf.timeUnit = int64(st.TimeUnit)
}
if st.StartTime.IsZero() {
@@ -97,6 +145,8 @@ func New(st Settings) (*Sonyflake, error) {
sf.startTime = sf.toInternalTime(st.StartTime)
}
sf.sequence = 1<<sf.bitsSequence - 1
var err error
if st.MachineID == nil {
sf.machine, err = lower16BitPrivateIP(defaultInterfaceAddrs)
@@ -117,7 +167,7 @@ func New(st Settings) (*Sonyflake, error) {
// NextID generates a next unique ID as int64.
// After the Sonyflake time overflows, NextID returns an error.
func (sf *Sonyflake) NextID() (int64, error) {
const maskSequence = 1<<BitLenSequence - 1
maskSequence := 1<<sf.bitsSequence - 1
sf.mutex.Lock()
defer sf.mutex.Unlock()
@@ -153,12 +203,12 @@ func (sf *Sonyflake) sleep(overtime int64) {
}
func (sf *Sonyflake) toID() (int64, error) {
if sf.elapsedTime >= 1<<BitLenTime {
if sf.elapsedTime >= 1<<sf.bitsTime {
return 0, ErrOverTimeLimit
}
return sf.elapsedTime<<(BitLenSequence+BitLenMachine) |
int64(sf.sequence)<<BitLenMachine |
return sf.elapsedTime<<(sf.bitsSequence+sf.bitsMachine) |
int64(sf.sequence)<<sf.bitsMachine |
int64(sf.machine), nil
}
@@ -198,35 +248,32 @@ func lower16BitPrivateIP(interfaceAddrs types.InterfaceAddrs) (int, error) {
}
func (sf *Sonyflake) ToTime(id int64) time.Time {
return time.Unix(0, (sf.startTime+Time(id))*sf.timeUnit)
}
// Time returns the Sonyflake time when the given ID was generated.
func Time(id int64) int64 {
return id >> (BitLenSequence + BitLenMachine)
}
// SequenceNumber returns the sequence number of a Sonyflake ID.
func SequenceNumber(id int64) int {
const maskSequence = int64((1<<BitLenSequence - 1) << BitLenMachine)
return int((id & maskSequence) >> BitLenMachine)
}
// MachineID returns the machine ID of a Sonyflake ID.
func MachineID(id int64) int {
const maskMachine = int64(1<<BitLenMachine - 1)
return int(id & maskMachine)
return time.Unix(0, (sf.startTime+sf.timePart(id))*sf.timeUnit)
}
// Decompose returns a set of Sonyflake ID parts.
func Decompose(id int64) map[string]int64 {
time := Time(id)
sequence := SequenceNumber(id)
machine := MachineID(id)
func (sf *Sonyflake) Decompose(id int64) map[string]int64 {
time := sf.timePart(id)
sequence := sf.sequencePart(id)
machine := sf.machinePart(id)
return map[string]int64{
"id": id,
"time": time,
"sequence": int64(sequence),
"machine": int64(machine),
"sequence": sequence,
"machine": machine,
}
}
func (sf *Sonyflake) timePart(id int64) int64 {
return id >> (sf.bitsSequence + sf.bitsMachine)
}
func (sf *Sonyflake) sequencePart(id int64) int64 {
maskSequence := int64((1<<sf.bitsSequence - 1) << sf.bitsMachine)
return (id & maskSequence) >> sf.bitsMachine
}
func (sf *Sonyflake) machinePart(id int64) int64 {
maskMachine := int64(1<<sf.bitsMachine - 1)
return id & maskMachine
}
+29 -7
View File
@@ -20,6 +20,28 @@ func TestNew(t *testing.T) {
settings Settings
err error
}{
{
name: "invalid bit length for time",
settings: Settings{
BitsSequence: 16,
BitsMachineID: 16,
},
err: ErrInvalidBitsTime,
},
{
name: "invalid bit length for sequence number",
settings: Settings{
BitsSequence: -1,
},
err: ErrInvalidBitsSequence,
},
{
name: "invalid bit length for machine id",
settings: Settings{
BitsMachineID: 31,
},
err: ErrInvalidBitsMachineID,
},
{
name: "invalid time unit",
settings: Settings{
@@ -105,23 +127,23 @@ func TestNextID(t *testing.T) {
id := nextID(t, sf)
actualTime := Time(id)
actualTime := sf.timePart(id)
if actualTime < sleepTime || actualTime > sleepTime+1 {
t.Errorf("unexpected time: %d", actualTime)
}
actualSequence := SequenceNumber(id)
actualSequence := sf.sequencePart(id)
if actualSequence != 0 {
t.Errorf("unexpected sequence: %d", actualSequence)
}
actualMachine := MachineID(id)
if actualMachine != defaultMachineID(t) {
actualMachine := sf.machinePart(id)
if actualMachine != int64(defaultMachineID(t)) {
t.Errorf("unexpected machine: %d", actualMachine)
}
fmt.Println("sonyflake id:", id)
fmt.Println("decompose:", Decompose(id))
fmt.Println("decompose:", sf.Decompose(id))
}
func TestNextID_InSequence(t *testing.T) {
@@ -151,7 +173,7 @@ func TestNextID_InSequence(t *testing.T) {
}
lastID = id
parts := Decompose(id)
parts := sf.Decompose(id)
actualTime := parts["time"]
overtime := startTime + actualTime - currentTime
@@ -170,7 +192,7 @@ func TestNextID_InSequence(t *testing.T) {
}
}
if maxSeq != 1<<BitLenSequence-1 {
if maxSeq != 1<<sf.bitsSequence-1 {
t.Errorf("unexpected max sequence: %d", maxSeq)
}
fmt.Println("max sequence:", maxSeq)