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

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)
[![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.
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
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,
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
```
Usage
-----
## Usage
The function New creates a new Sonyflake instance.
@@ -43,6 +43,8 @@ You can configure Sonyflake by the struct Settings:
```go
type Settings struct {
BitsSequence int
BitsMachineID int
TimeUnit time.Duration
StartTime time.Time
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.
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".
@@ -66,6 +76,9 @@ type Settings struct {
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.
In order to get a new unique ID, you just have to call the method NextID.
```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.
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 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.
License
-------
## License
The MIT License (MIT)

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

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
}

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)