-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathhyperflake.go
More file actions
167 lines (143 loc) · 4.94 KB
/
hyperflake.go
File metadata and controls
167 lines (143 loc) · 4.94 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
package hyperflake
import (
"fmt"
"github.com/chirag3003/hyperflake-go/internal"
"sync"
"time"
)
// Bit layout of a 64-bit Hyperflake ID:
//
// [63] = sign bit (1 bit)
// [62:22] = timestamp (41 bits) — milliseconds since epoch
// [21:17] = datacenter ID (5 bits)
// [16:12] = machine ID (5 bits)
// [11:0] = sequence no. (12 bits)
const (
timestampShift = 22
datacenterIDShift = 17
machineIDShift = 12
timestampMask = 0x1FFFFFFFFFF // 41 bits
datacenterIDMask = 0x1F // 5 bits
machineIDMask = 0x1F // 5 bits
sequenceMask = 0xFFF // 12 bits
)
// Config holds the configuration for generating Hyperflake IDs.
type Config struct {
mu sync.Mutex // Protects sequenceNumber and lastTimestamp
epoch int64 // Epoch timestamp in milliseconds
datacenterID int // Datacenter ID (0–31)
machineID int // Machine ID (0–31)
sequenceNumber int // Per-millisecond sequence counter
signBit int // Sign bit (almost always 0)
lastTimestamp int64
}
// HyperFlakeID represents a decoded Hyperflake ID.
type HyperFlakeID struct {
ID int64 // Original ID
Signbit int // Sign bit
DatacenterID int // Datacenter ID
MachineID int // Machine ID
SequenceNumber int // Sequence number
TimestampSinceEpoch int64 // Milliseconds since custom epoch
Timestamp int64 // Unix timestamp in milliseconds
}
var DefaultEpoch = time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC)
var defaultEpochMilli = DefaultEpoch.UnixMilli()
/*
NewHyperflakeConfig creates a new HyperflakeConfig with the given parameters.
Parameters:
- datacenterID: Datacenter ID (0–31).
- machineID: Machine ID (0–31).
- signBit: Optional sign bit (default is 0).
Returns:
- A pointer to the newly created Config.
*/
func NewHyperflakeConfig(datacenterID int, machineID int, signBit ...int) *Config {
sBit := 0
if len(signBit) > 0 {
sBit = signBit[0]
}
return &Config{
epoch: defaultEpochMilli,
datacenterID: datacenterID,
machineID: machineID,
signBit: sBit,
}
}
/*
NewHyperflakeConfigWithEpoch creates a new HyperflakeConfig with a custom epoch.
Parameters:
- datacenterID: Datacenter ID (0–31).
- machineID: Machine ID (0–31).
- epoch: Custom epoch timestamp in milliseconds.
- signBit: Optional sign bit (default is 0).
Returns:
- A pointer to the newly created Config.
*/
func NewHyperflakeConfigWithEpoch(datacenterID int, machineID int, epoch int64, signBit ...int) *Config {
config := NewHyperflakeConfig(datacenterID, machineID, signBit...)
config.epoch = epoch
return config
}
// GenerateHyperflakeID generates a new Hyperflake ID based on the current configuration.
// It is safe for concurrent use by multiple goroutines.
func (config *Config) GenerateHyperflakeID() (int64, error) {
config.mu.Lock()
defer config.mu.Unlock()
timestamp := internal.GetCurrentTimestampSinceEpoch(config.epoch)
if timestamp < config.lastTimestamp {
return 0, fmt.Errorf("clock is moving backwards")
}
if timestamp == config.lastTimestamp {
config.sequenceNumber++
if config.sequenceNumber > sequenceMask {
// Sequence exhausted for this millisecond; spin until the clock ticks.
for timestamp == config.lastTimestamp {
timestamp = internal.GetCurrentTimestampSinceEpoch(config.epoch)
}
config.sequenceNumber = 0
}
} else {
config.sequenceNumber = 0
}
config.lastTimestamp = timestamp
id := (int64(config.signBit) << 63) |
(timestamp << timestampShift) |
(int64(config.datacenterID) << datacenterIDShift) |
(int64(config.machineID) << machineIDShift) |
int64(config.sequenceNumber)
return id, nil
}
// DecodeID decodes a given Hyperflake ID into its components.
func (config *Config) DecodeID(id int64) (*HyperFlakeID, error) {
signBit := int((id >> 63) & 0x1)
timestamp := (id >> timestampShift) & timestampMask
datacenterID := int((id >> datacenterIDShift) & datacenterIDMask)
machineID := int((id >> machineIDShift) & machineIDMask)
sequenceNumber := int(id & sequenceMask)
return &HyperFlakeID{
ID: id,
Signbit: signBit,
TimestampSinceEpoch: timestamp,
Timestamp: timestamp + config.epoch,
DatacenterID: datacenterID,
MachineID: machineID,
SequenceNumber: sequenceNumber,
}, nil
}
// SetMachineID sets the machine ID in the configuration.
func (config *Config) SetMachineID(machineID int) {
config.machineID = machineID
}
// SetDatacenterID sets the datacenter ID in the configuration.
func (config *Config) SetDatacenterID(datacenterID int) {
config.datacenterID = datacenterID
}
// GetMachineID returns the machine ID from the configuration.
func (config *Config) GetMachineID() int {
return config.machineID
}
// GetDatacenterID returns the datacenter ID from the configuration.
func (config *Config) GetDatacenterID() int {
return config.datacenterID
}