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 authored and GitHub committed 2025-05-05 14:16:21 +09:00
1 parent 357b2ee5f0
commit 7ee8f154df
4 files changed
+150 -70

No files matched your search

+23 -12
View File
@@ -1,5 +1,4 @@
Sonyflake # Sonyflake
=========
[![GoDoc](https://godoc.org/github.com/sony/sonyflake?status.svg)](http://godoc.org/github.com/sony/sonyflake) [![GoDoc](https://godoc.org/github.com/sony/sonyflake?status.svg)](http://godoc.org/github.com/sony/sonyflake)
[![Go Report Card](https://goreportcard.com/badge/github.com/sony/sonyflake)](https://goreportcard.com/report/github.com/sony/sonyflake) [![Go Report Card](https://goreportcard.com/badge/github.com/sony/sonyflake)](https://goreportcard.com/report/github.com/sony/sonyflake)
@@ -8,7 +7,7 @@ Sonyflake is a distributed unique ID generator inspired by [Twitter's Snowflake]
Sonyflake focuses on lifetime and performance on many host/core environment. Sonyflake focuses on lifetime and performance on many host/core environment.
So it has a different bit assignment from Snowflake. So it has a different bit assignment from Snowflake.
A Sonyflake ID is composed of By default, a Sonyflake ID is composed of
39 bits for time in units of 10 msec 39 bits for time in units of 10 msec
8 bits for a sequence number 8 bits for a sequence number
@@ -23,15 +22,16 @@ As a result, Sonyflake has the following advantages and disadvantages:
However, if you want more generation rate in a single host, However, if you want more generation rate in a single host,
you can easily run multiple Sonyflake instances parallelly using goroutines. you can easily run multiple Sonyflake instances parallelly using goroutines.
Installation In addition, you can adjust the lifetime and generation rate of Sonyflake
------------ by customizing the bit assignment and the time unit.
## Installation
``` ```
go get github.com/sony/sonyflake/v2 go get github.com/sony/sonyflake/v2
``` ```
Usage ## Usage
-----
The function New creates a new Sonyflake instance. The function New creates a new Sonyflake instance.
@@ -43,6 +43,8 @@ You can configure Sonyflake by the struct Settings:
```go ```go
type Settings struct { type Settings struct {
BitsSequence int
BitsMachineID int
TimeUnit time.Duration TimeUnit time.Duration
StartTime time.Time StartTime time.Time
MachineID func() (int, error) MachineID func() (int, error)
@@ -50,9 +52,17 @@ type Settings struct {
} }
``` ```
- 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. - TimeUnit is the time unit of Sonyflake.
If TimeUnit is 0, the default time unit is used, which is 10 msec. 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. - 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". If StartTime is 0, the start time of the Sonyflake instance is set to "2025-01-01 00:00:00 +0000 UTC".
@@ -66,6 +76,9 @@ type Settings struct {
If CheckMachineID returns false, the instance will not be created. If CheckMachineID returns false, the instance will not be created.
If CheckMachineID is nil, no validation is done. 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.
In order to get a new unique ID, you just have to call the method NextID. In order to get a new unique ID, you just have to call the method NextID.
```go ```go
@@ -75,8 +88,7 @@ func (sf *Sonyflake) NextID() (int64, error)
NextID can continue to generate IDs for about 174 years from StartTime by default. NextID can continue to generate IDs for about 174 years from StartTime by default.
But after the Sonyflake time is over the limit, NextID returns an error. But after the Sonyflake time is over the limit, NextID returns an error.
AWS VPC and Docker ## AWS VPC and Docker
------------------
The [awsutil](https://github.com/sony/sonyflake/blob/master/v2/awsutil) package provides The [awsutil](https://github.com/sony/sonyflake/blob/master/v2/awsutil) package provides
the function AmazonEC2MachineID that returns the lower 16-bit private IP address of the Amazon EC2 instance. the function AmazonEC2MachineID that returns the lower 16-bit private IP address of the Amazon EC2 instance.
@@ -91,8 +103,7 @@ In this common case, you can use AmazonEC2MachineID as Settings.MachineID.
See [example](https://github.com/sony/sonyflake/blob/master/v2/example) that runs Sonyflake on AWS Elastic Beanstalk. See [example](https://github.com/sony/sonyflake/blob/master/v2/example) that runs Sonyflake on AWS Elastic Beanstalk.
License ## License
-------
The MIT License (MIT) The MIT License (MIT)
+1 -1
View File
@@ -28,7 +28,7 @@ func handler(w http.ResponseWriter, r *http.Request) {
return return
} }
body, err := json.Marshal(sonyflake.Decompose(id)) body, err := json.Marshal(sf.Decompose(id))
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
+97 -50
View File
@@ -1,6 +1,6 @@
// Package sonyflake implements Sonyflake, a distributed unique ID generator inspired by Twitter's Snowflake. // 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 // 39 bits for time in units of 10 msec
// 8 bits for a sequence number // 8 bits for a sequence number
@@ -16,18 +16,19 @@ import (
"github.com/sony/sonyflake/v2/types" "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: // 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. // TimeUnit is the time unit of Sonyflake.
// If TimeUnit is 0, the default time unit is used, which is 10 msec. // 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. // 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". // 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. // CheckMachineID validates the uniqueness of a machine ID.
// If CheckMachineID returns false, the instance will not be created. // If CheckMachineID returns false, the instance will not be created.
// If CheckMachineID is nil, no validation is done. // 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 { type Settings struct {
BitsSequence int
BitsMachineID int
TimeUnit time.Duration TimeUnit time.Duration
StartTime time.Time StartTime time.Time
MachineID func() (int, error) MachineID func() (int, error)
@@ -49,46 +55,88 @@ type Settings struct {
// Sonyflake is a distributed unique ID generator. // Sonyflake is a distributed unique ID generator.
type Sonyflake struct { type Sonyflake struct {
mutex *sync.Mutex mutex *sync.Mutex
bitsTime int
bitsSequence int
bitsMachine int
timeUnit int64 timeUnit int64
startTime int64 startTime int64
elapsedTime int64 elapsedTime int64
sequence int
machine int sequence int
machine int
} }
var ( var (
ErrStartTimeAhead = errors.New("start time is ahead of now") ErrInvalidBitsTime = errors.New("bit length for time must be 32 or more")
ErrNoPrivateAddress = errors.New("no private ip address") ErrInvalidBitsSequence = errors.New("invalid bit length for sequence number")
ErrOverTimeLimit = errors.New("over the time limit") ErrInvalidBitsMachineID = errors.New("invalid bit length for machine id")
ErrInvalidMachineID = errors.New("invalid machine id") ErrInvalidTimeUnit = errors.New("invalid time unit")
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 var defaultInterfaceAddrs = net.InterfaceAddrs
// New returns a new Sonyflake configured with the given Settings. // New returns a new Sonyflake configured with the given Settings.
// New returns an error in the following cases: // 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.StartTime is ahead of the current time.
// - Settings.MachineID returns an error. // - Settings.MachineID returns an error.
// - Settings.CheckMachineID returns false. // - Settings.CheckMachineID returns false.
func New(st Settings) (*Sonyflake, error) { 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()) { if st.StartTime.After(time.Now()) {
return nil, ErrStartTimeAhead return nil, ErrStartTimeAhead
} }
sf := new(Sonyflake) sf := new(Sonyflake)
sf.mutex = new(sync.Mutex) 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 { if st.TimeUnit == 0 {
sf.timeUnit = defaultTimeUnit sf.timeUnit = defaultTimeUnit
} else if st.TimeUnit >= time.Millisecond {
sf.timeUnit = int64(st.TimeUnit)
} else { } else {
return nil, ErrInvalidTimeUnit sf.timeUnit = int64(st.TimeUnit)
} }
if st.StartTime.IsZero() { if st.StartTime.IsZero() {
@@ -97,6 +145,8 @@ func New(st Settings) (*Sonyflake, error) {
sf.startTime = sf.toInternalTime(st.StartTime) sf.startTime = sf.toInternalTime(st.StartTime)
} }
sf.sequence = 1<<sf.bitsSequence - 1
var err error var err error
if st.MachineID == nil { if st.MachineID == nil {
sf.machine, err = lower16BitPrivateIP(defaultInterfaceAddrs) sf.machine, err = lower16BitPrivateIP(defaultInterfaceAddrs)
@@ -117,7 +167,7 @@ func New(st Settings) (*Sonyflake, error) {
// NextID generates a next unique ID as int64. // NextID generates a next unique ID as int64.
// After the Sonyflake time overflows, NextID returns an error. // After the Sonyflake time overflows, NextID returns an error.
func (sf *Sonyflake) NextID() (int64, error) { func (sf *Sonyflake) NextID() (int64, error) {
const maskSequence = 1<<BitLenSequence - 1 maskSequence := 1<<sf.bitsSequence - 1
sf.mutex.Lock() sf.mutex.Lock()
defer sf.mutex.Unlock() defer sf.mutex.Unlock()
@@ -153,12 +203,12 @@ func (sf *Sonyflake) sleep(overtime int64) {
} }
func (sf *Sonyflake) toID() (int64, error) { func (sf *Sonyflake) toID() (int64, error) {
if sf.elapsedTime >= 1<<BitLenTime { if sf.elapsedTime >= 1<<sf.bitsTime {
return 0, ErrOverTimeLimit return 0, ErrOverTimeLimit
} }
return sf.elapsedTime<<(BitLenSequence+BitLenMachine) | return sf.elapsedTime<<(sf.bitsSequence+sf.bitsMachine) |
int64(sf.sequence)<<BitLenMachine | int64(sf.sequence)<<sf.bitsMachine |
int64(sf.machine), nil int64(sf.machine), nil
} }
@@ -198,35 +248,32 @@ func lower16BitPrivateIP(interfaceAddrs types.InterfaceAddrs) (int, error) {
} }
func (sf *Sonyflake) ToTime(id int64) time.Time { func (sf *Sonyflake) ToTime(id int64) time.Time {
return time.Unix(0, (sf.startTime+Time(id))*sf.timeUnit) return time.Unix(0, (sf.startTime+sf.timePart(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)
} }
// Decompose returns a set of Sonyflake ID parts. // Decompose returns a set of Sonyflake ID parts.
func Decompose(id int64) map[string]int64 { func (sf *Sonyflake) Decompose(id int64) map[string]int64 {
time := Time(id) time := sf.timePart(id)
sequence := SequenceNumber(id) sequence := sf.sequencePart(id)
machine := MachineID(id) machine := sf.machinePart(id)
return map[string]int64{ return map[string]int64{
"id": id, "id": id,
"time": time, "time": time,
"sequence": int64(sequence), "sequence": sequence,
"machine": int64(machine), "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 settings Settings
err error 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", name: "invalid time unit",
settings: Settings{ settings: Settings{
@@ -105,23 +127,23 @@ func TestNextID(t *testing.T) {
id := nextID(t, sf) id := nextID(t, sf)
actualTime := Time(id) actualTime := sf.timePart(id)
if actualTime < sleepTime || actualTime > sleepTime+1 { if actualTime < sleepTime || actualTime > sleepTime+1 {
t.Errorf("unexpected time: %d", actualTime) t.Errorf("unexpected time: %d", actualTime)
} }
actualSequence := SequenceNumber(id) actualSequence := sf.sequencePart(id)
if actualSequence != 0 { if actualSequence != 0 {
t.Errorf("unexpected sequence: %d", actualSequence) t.Errorf("unexpected sequence: %d", actualSequence)
} }
actualMachine := MachineID(id) actualMachine := sf.machinePart(id)
if actualMachine != defaultMachineID(t) { if actualMachine != int64(defaultMachineID(t)) {
t.Errorf("unexpected machine: %d", actualMachine) t.Errorf("unexpected machine: %d", actualMachine)
} }
fmt.Println("sonyflake id:", id) fmt.Println("sonyflake id:", id)
fmt.Println("decompose:", Decompose(id)) fmt.Println("decompose:", sf.Decompose(id))
} }
func TestNextID_InSequence(t *testing.T) { func TestNextID_InSequence(t *testing.T) {
@@ -151,7 +173,7 @@ func TestNextID_InSequence(t *testing.T) {
} }
lastID = id lastID = id
parts := Decompose(id) parts := sf.Decompose(id)
actualTime := parts["time"] actualTime := parts["time"]
overtime := startTime + actualTime - currentTime 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) t.Errorf("unexpected max sequence: %d", maxSeq)
} }
fmt.Println("max sequence:", maxSeq) fmt.Println("max sequence:", maxSeq)