snowflaked 0.1.4 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/ext/snowflaked/Cargo.toml +2 -1
- data/ext/snowflaked/src/lib.rs +59 -40
- data/lib/snowflaked/model_extensions.rb +5 -9
- data/lib/snowflaked/version.rb +1 -1
- data/lib/snowflaked.rb +9 -11
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8614c6c1857513375925fcc710cae86379a5f3937e1128a76e7c0c9316e717d7
|
|
4
|
+
data.tar.gz: 2a229cc5321964620d03261fcaa7b757e2a46ae31519ba9d03a0bb18a4b37f90
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d54e7b37a266cfa15f98d07e9723db9d218aff9301b7c1c0939d82f750ea91cbbfde65a7470b2b29a34d08b23b8a8a969e88e07af13b7f1af16f6eb6322b5648
|
|
7
|
+
data.tar.gz: a032245eec1c604d91bd3c51fefb3d5345cf693e8ff50c34dab29bd022d5c2c7fc77cabe32084e7a5955c25caaa7ffb903abc90d1d7d61b233fd6b0040f90a65
|
data/README.md
CHANGED
|
@@ -145,7 +145,7 @@ Snowflaked.machine_id(id)
|
|
|
145
145
|
|
|
146
146
|
## Benchmarks
|
|
147
147
|
|
|
148
|
-
See [BENCHMARKS.md](benchmarks/
|
|
148
|
+
See [BENCHMARKS.md](benchmarks/README.md) for more details.
|
|
149
149
|
|
|
150
150
|
tl;dr: Snowflake IDs have a negligible performance impact compared to database-backed IDs.
|
|
151
151
|
|
data/ext/snowflaked/Cargo.toml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "snowflaked"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.2.0"
|
|
4
4
|
edition = "2021"
|
|
5
5
|
publish = false
|
|
6
6
|
|
|
@@ -10,3 +10,4 @@ crate-type = ["cdylib"]
|
|
|
10
10
|
[dependencies]
|
|
11
11
|
magnus = { version = "0.8" }
|
|
12
12
|
snowflaked = { version = "1.0.3", features = ["sync"] }
|
|
13
|
+
arc-swap = "1.8.2"
|
data/ext/snowflaked/src/lib.rs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
use arc_swap::ArcSwapOption;
|
|
1
2
|
use magnus::{function, prelude::*, Error, RHash, Ruby};
|
|
2
3
|
use snowflaked::sync::Generator;
|
|
3
4
|
use snowflaked::{Builder, Snowflake};
|
|
4
|
-
use std::sync::
|
|
5
|
+
use std::sync::Arc;
|
|
5
6
|
use std::time::UNIX_EPOCH;
|
|
6
7
|
|
|
7
8
|
struct GeneratorState {
|
|
@@ -11,68 +12,88 @@ struct GeneratorState {
|
|
|
11
12
|
init_pid: u32,
|
|
12
13
|
}
|
|
13
14
|
|
|
14
|
-
static STATE:
|
|
15
|
+
static STATE: ArcSwapOption<GeneratorState> = ArcSwapOption::const_empty();
|
|
15
16
|
|
|
16
|
-
fn
|
|
17
|
-
let
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
let mut state = STATE.write().unwrap();
|
|
17
|
+
fn build_generator(machine_id: u16, epoch_offset: u64) -> Generator {
|
|
18
|
+
let epoch = UNIX_EPOCH + std::time::Duration::from_millis(epoch_offset);
|
|
19
|
+
Builder::new().instance(machine_id).epoch(epoch).build()
|
|
20
|
+
}
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
fn ensure_state(machine_id: u16, epoch_offset: u64, current_pid: u32) -> (Arc<GeneratorState>, bool) {
|
|
23
|
+
if let Some(s) = &*STATE.load() {
|
|
24
|
+
if s.init_pid == current_pid {
|
|
25
|
+
return (Arc::clone(s), false);
|
|
26
|
+
}
|
|
24
27
|
}
|
|
25
28
|
|
|
26
|
-
let
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
*state = Some(GeneratorState {
|
|
30
|
-
generator,
|
|
29
|
+
let new_state = Arc::new(GeneratorState {
|
|
30
|
+
generator: build_generator(machine_id, epoch_offset),
|
|
31
31
|
epoch_offset,
|
|
32
32
|
machine_id,
|
|
33
33
|
init_pid: current_pid,
|
|
34
34
|
});
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
let prev_state = STATE.rcu(|current| {
|
|
37
|
+
if let Some(c) = current {
|
|
38
|
+
if c.init_pid == current_pid {
|
|
39
|
+
return Arc::clone(c);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
Arc::clone(&new_state)
|
|
43
|
+
});
|
|
38
44
|
|
|
39
|
-
|
|
40
|
-
|
|
45
|
+
match prev_state.as_ref() {
|
|
46
|
+
Some(s) if s.init_pid == current_pid => (Arc::clone(s), false),
|
|
47
|
+
_ => (new_state, true),
|
|
48
|
+
}
|
|
49
|
+
}
|
|
41
50
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
)
|
|
47
|
-
})?;
|
|
51
|
+
fn init_generator(machine_id: u16, epoch_ms: Option<u64>) -> bool {
|
|
52
|
+
let (_, swapped) = ensure_state(machine_id, epoch_ms.unwrap_or(0), std::process::id());
|
|
53
|
+
swapped
|
|
54
|
+
}
|
|
48
55
|
|
|
49
|
-
|
|
56
|
+
fn validate_config(ruby: &Ruby, s: &GeneratorState, machine_id: u16, epoch_offset: u64) -> Result<(), Error> {
|
|
57
|
+
if s.machine_id != machine_id || s.epoch_offset != epoch_offset {
|
|
50
58
|
return Err(Error::new(
|
|
51
59
|
ruby.exception_runtime_error(),
|
|
52
|
-
"
|
|
60
|
+
"Generator already initialized with a different machine_id or epoch for this process",
|
|
53
61
|
));
|
|
54
62
|
}
|
|
63
|
+
Ok(())
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
fn generate(ruby: &Ruby, machine_id: u16, epoch_ms: Option<u64>) -> Result<u64, Error> {
|
|
67
|
+
let epoch_offset = epoch_ms.unwrap_or(0);
|
|
68
|
+
let (state, _) = ensure_state(machine_id, epoch_offset, std::process::id());
|
|
55
69
|
|
|
56
|
-
|
|
70
|
+
validate_config(ruby, &state, machine_id, epoch_offset)?;
|
|
71
|
+
Ok(state.generator.generate())
|
|
57
72
|
}
|
|
58
73
|
|
|
59
|
-
fn
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
74
|
+
fn epoch_offset(ruby: &Ruby) -> Result<u64, Error> {
|
|
75
|
+
STATE
|
|
76
|
+
.load()
|
|
77
|
+
.as_ref()
|
|
78
|
+
.map(|s| s.epoch_offset)
|
|
79
|
+
.ok_or_else(|| Error::new(ruby.exception_runtime_error(), "Generator not initialized"))
|
|
64
80
|
}
|
|
65
81
|
|
|
66
82
|
fn parse(ruby: &Ruby, id: u64) -> Result<RHash, Error> {
|
|
83
|
+
let offset = epoch_offset(ruby)?;
|
|
67
84
|
let hash = ruby.hash_new();
|
|
68
85
|
|
|
69
|
-
hash.aset(ruby.to_symbol("timestamp_ms"),
|
|
70
|
-
hash.aset(ruby.to_symbol("machine_id"),
|
|
71
|
-
hash.aset(ruby.to_symbol("sequence"), sequence(
|
|
86
|
+
hash.aset(ruby.to_symbol("timestamp_ms"), id.timestamp().saturating_add(offset))?;
|
|
87
|
+
hash.aset(ruby.to_symbol("machine_id"), id.instance())?;
|
|
88
|
+
hash.aset(ruby.to_symbol("sequence"), id.sequence())?;
|
|
72
89
|
|
|
73
90
|
Ok(hash)
|
|
74
91
|
}
|
|
75
92
|
|
|
93
|
+
fn timestamp_ms(ruby: &Ruby, id: u64) -> Result<u64, Error> {
|
|
94
|
+
epoch_offset(ruby).map(|offset| id.timestamp().saturating_add(offset))
|
|
95
|
+
}
|
|
96
|
+
|
|
76
97
|
fn machine_id_from_id(id: u64) -> u64 {
|
|
77
98
|
id.instance()
|
|
78
99
|
}
|
|
@@ -82,13 +103,11 @@ fn sequence(id: u64) -> u64 {
|
|
|
82
103
|
}
|
|
83
104
|
|
|
84
105
|
fn is_initialized() -> bool {
|
|
85
|
-
|
|
86
|
-
state.as_ref().is_some_and(|s| s.init_pid == std::process::id())
|
|
106
|
+
STATE.load().as_ref().is_some_and(|s| s.init_pid == std::process::id())
|
|
87
107
|
}
|
|
88
108
|
|
|
89
109
|
fn configured_machine_id() -> Option<u16> {
|
|
90
|
-
|
|
91
|
-
state.as_ref().and_then(|s| if s.init_pid == std::process::id() { Some(s.machine_id) } else { None })
|
|
110
|
+
STATE.load().as_ref().filter(|s| s.init_pid == std::process::id()).map(|s| s.machine_id)
|
|
92
111
|
}
|
|
93
112
|
|
|
94
113
|
#[magnus::init]
|
|
@@ -97,7 +116,7 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
|
97
116
|
let internal = module.define_module("Native")?;
|
|
98
117
|
|
|
99
118
|
internal.define_singleton_method("init_generator", function!(init_generator, 2))?;
|
|
100
|
-
internal.define_singleton_method("generate", function!(generate,
|
|
119
|
+
internal.define_singleton_method("generate", function!(generate, 2))?;
|
|
101
120
|
internal.define_singleton_method("parse", function!(parse, 1))?;
|
|
102
121
|
internal.define_singleton_method("timestamp_ms", function!(timestamp_ms, 1))?;
|
|
103
122
|
internal.define_singleton_method("machine_id", function!(machine_id_from_id, 1))?;
|
|
@@ -6,7 +6,7 @@ module Snowflaked
|
|
|
6
6
|
|
|
7
7
|
included do
|
|
8
8
|
class_attribute :_snowflake_attributes, instance_writer: false, default: [:id]
|
|
9
|
-
|
|
9
|
+
before_validation :_generate_snowflake_ids, on: :create
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
class_methods do
|
|
@@ -20,11 +20,7 @@ module Snowflaked
|
|
|
20
20
|
def _snowflake_columns_from_comments
|
|
21
21
|
return @_snowflake_columns_from_comments if defined?(@_snowflake_columns_from_comments)
|
|
22
22
|
|
|
23
|
-
@_snowflake_columns_from_comments = if
|
|
24
|
-
columns.filter_map { |col| col.name.to_sym if col.comment == Snowflaked::SchemaDefinitions::COMMENT }
|
|
25
|
-
else
|
|
26
|
-
[]
|
|
27
|
-
end
|
|
23
|
+
@_snowflake_columns_from_comments = table_exists? ? columns.filter_map { |col| col.name.to_sym if col.comment == Snowflaked::SchemaDefinitions::COMMENT } : []
|
|
28
24
|
end
|
|
29
25
|
|
|
30
26
|
def _snowflake_attributes_with_columns
|
|
@@ -35,10 +31,10 @@ module Snowflaked
|
|
|
35
31
|
private
|
|
36
32
|
|
|
37
33
|
def _generate_snowflake_ids
|
|
38
|
-
|
|
39
|
-
return if
|
|
34
|
+
attributes = self.class._snowflake_attributes_with_columns
|
|
35
|
+
return if attributes.empty?
|
|
40
36
|
|
|
41
|
-
|
|
37
|
+
attributes.each do |attribute|
|
|
42
38
|
next if self[attribute].present?
|
|
43
39
|
|
|
44
40
|
self[attribute] = Snowflaked.id
|
data/lib/snowflaked/version.rb
CHANGED
data/lib/snowflaked.rb
CHANGED
|
@@ -29,8 +29,7 @@ module Snowflaked
|
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
def machine_id_value
|
|
32
|
-
|
|
33
|
-
id % (MAX_MACHINE_ID + 1)
|
|
32
|
+
(@machine_id || default_machine_id) % (MAX_MACHINE_ID + 1)
|
|
34
33
|
end
|
|
35
34
|
|
|
36
35
|
def epoch_ms
|
|
@@ -46,8 +45,7 @@ module Snowflaked
|
|
|
46
45
|
end
|
|
47
46
|
|
|
48
47
|
def env_machine_id
|
|
49
|
-
|
|
50
|
-
id&.to_i
|
|
48
|
+
(ENV["SNOWFLAKED_MACHINE_ID"] || ENV.fetch("MACHINE_ID", nil))&.to_i
|
|
51
49
|
end
|
|
52
50
|
|
|
53
51
|
def hostname_pid_hash
|
|
@@ -68,8 +66,8 @@ module Snowflaked
|
|
|
68
66
|
end
|
|
69
67
|
|
|
70
68
|
def id
|
|
71
|
-
|
|
72
|
-
Native.generate
|
|
69
|
+
config = configuration
|
|
70
|
+
Native.generate(config.machine_id_value, config.epoch_ms)
|
|
73
71
|
end
|
|
74
72
|
|
|
75
73
|
def parse(id)
|
|
@@ -78,16 +76,17 @@ module Snowflaked
|
|
|
78
76
|
end
|
|
79
77
|
|
|
80
78
|
def timestamp(id)
|
|
81
|
-
|
|
82
|
-
|
|
79
|
+
ensure_initialized!
|
|
80
|
+
seconds, milliseconds = Native.timestamp_ms(id).divmod(1000)
|
|
81
|
+
Time.at(seconds, milliseconds * 1000, :usec)
|
|
83
82
|
end
|
|
84
83
|
|
|
85
84
|
def machine_id(id)
|
|
86
|
-
ensure_initialized!
|
|
87
85
|
Native.machine_id(id)
|
|
88
86
|
end
|
|
89
87
|
|
|
90
88
|
def timestamp_ms(id)
|
|
89
|
+
ensure_initialized!
|
|
91
90
|
Native.timestamp_ms(id)
|
|
92
91
|
end
|
|
93
92
|
|
|
@@ -100,8 +99,7 @@ module Snowflaked
|
|
|
100
99
|
def ensure_initialized!
|
|
101
100
|
return if Native.initialized?
|
|
102
101
|
|
|
103
|
-
|
|
104
|
-
Native.init_generator(config.machine_id_value, config.epoch_ms)
|
|
102
|
+
Native.init_generator(configuration.machine_id_value, configuration.epoch_ms)
|
|
105
103
|
end
|
|
106
104
|
end
|
|
107
105
|
end
|