dogtag 0.1.0 → 1.0.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/lib/dogtag.rb +38 -12
- data/lib/dogtag/generator.rb +20 -3
- data/lib/dogtag/id.rb +5 -0
- data/lib/dogtag/mixins/redis.rb +1 -1
- data/lib/dogtag/request.rb +8 -11
- data/lib/dogtag/version.rb +1 -1
- data/lua/id-generation.lua +38 -10
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3873a444c41218413a933994fd93c9683588e6f5
|
4
|
+
data.tar.gz: 561a47c6e821efc9d602d04b985ae1e05e1f1809
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0ddf7a7f873c0c3b6a31c80b189cde967aea42569afbec71aeb95ae3361049176d43a525d4270da2ad4dc701fdab2f64b2f9c9aa69da67cbfbeda5f10a79e9db
|
7
|
+
data.tar.gz: dd72cbd167d3c91f5fd530108a17f1ab4ecf6fe50fd4efca81aaee763bbd5c69a8904d98ab947f2dd7e070a12b828b9bedbec78e543330013a7e544bf319cdb8
|
data/lib/dogtag.rb
CHANGED
@@ -6,32 +6,58 @@ module Dogtag
|
|
6
6
|
|
7
7
|
CUSTOM_EPOCH = 1483228800000 # in milliseconds
|
8
8
|
|
9
|
-
TIMESTAMP_BITS =
|
10
|
-
LOGICAL_SHARD_ID_BITS =
|
11
|
-
|
9
|
+
TIMESTAMP_BITS = 40
|
10
|
+
LOGICAL_SHARD_ID_BITS = 5
|
11
|
+
DATA_TYPE_BITS = 8
|
12
|
+
SEQUENCE_BITS = 10
|
13
|
+
|
14
|
+
MIN_LOGICAL_SHARD_ID = 0
|
15
|
+
MAX_LOGICAL_SHARD_ID = ~(-1 << LOGICAL_SHARD_ID_BITS)
|
16
|
+
LOGICAL_SHARD_ID_ALLOWED_RANGE = (MIN_LOGICAL_SHARD_ID..MAX_LOGICAL_SHARD_ID).freeze
|
17
|
+
|
18
|
+
MIN_DATA_TYPE = 0
|
19
|
+
MAX_DATA_TYPE = ~(-1 << DATA_TYPE_BITS)
|
20
|
+
DATA_TYPE_ALLOWED_RANGE = (MIN_DATA_TYPE..MAX_DATA_TYPE).freeze
|
21
|
+
|
22
|
+
MAX_SEQUENCE = ~(-1 << SEQUENCE_BITS)
|
12
23
|
|
13
24
|
SEQUENCE_SHIFT = 0
|
14
|
-
|
15
|
-
|
25
|
+
DATA_TYPE_SHIFT = SEQUENCE_BITS
|
26
|
+
LOGICAL_SHARD_ID_SHIFT = SEQUENCE_BITS + DATA_TYPE_BITS
|
27
|
+
TIMESTAMP_SHIFT = SEQUENCE_BITS + DATA_TYPE_BITS + LOGICAL_SHARD_ID_BITS
|
28
|
+
|
29
|
+
LOGICAL_SHARD_ID_RANGE_KEY = 'dogtag-generator-logical-shard-id-range'.freeze
|
16
30
|
|
17
|
-
|
31
|
+
def self.logical_shard_id_range=(logical_shard_id_range)
|
32
|
+
unless logical_shard_id_range.is_a? Range
|
33
|
+
raise ArgumentError, 'logical_shard_id_range must be a range'
|
34
|
+
end
|
35
|
+
|
36
|
+
unless (Dogtag::LOGICAL_SHARD_ID_ALLOWED_RANGE.to_a & logical_shard_id_range.to_a) == logical_shard_id_range.to_a
|
37
|
+
raise ArgumentError, "logical_shard_id_range is outside the allowed range of #{Dogtag::LOGICAL_SHARD_ID_ALLOWED_RANGE}"
|
38
|
+
end
|
18
39
|
|
19
|
-
|
20
|
-
|
40
|
+
if redis.exists LOGICAL_SHARD_ID_RANGE_KEY
|
41
|
+
redis.lset LOGICAL_SHARD_ID_RANGE_KEY, 0, logical_shard_id_range.min
|
42
|
+
redis.lset LOGICAL_SHARD_ID_RANGE_KEY, 1, logical_shard_id_range.max
|
43
|
+
else
|
44
|
+
redis.rpush LOGICAL_SHARD_ID_RANGE_KEY, logical_shard_id_range.min
|
45
|
+
redis.rpush LOGICAL_SHARD_ID_RANGE_KEY, logical_shard_id_range.max
|
46
|
+
end
|
21
47
|
end
|
22
48
|
|
23
|
-
def self.generate_id
|
24
|
-
Generator.new(1).ids.first
|
49
|
+
def self.generate_id(data_type)
|
50
|
+
Generator.new(data_type, 1).ids.first
|
25
51
|
end
|
26
52
|
|
27
|
-
def self.generate_ids(count)
|
53
|
+
def self.generate_ids(data_type, count)
|
28
54
|
ids = []
|
29
55
|
|
30
56
|
# The Lua script can't always return as many IDs as you may want. So we loop
|
31
57
|
# until we have the exact amount.
|
32
58
|
while ids.length < count
|
33
59
|
initial_id_count = ids.length
|
34
|
-
ids += Generator.new(count - ids.length).ids
|
60
|
+
ids += Generator.new(data_type, count - ids.length).ids
|
35
61
|
|
36
62
|
# Ensure the ids array keeps growing as infinite loop insurance
|
37
63
|
return ids unless ids.length > initial_id_count
|
data/lib/dogtag/generator.rb
CHANGED
@@ -1,6 +1,18 @@
|
|
1
1
|
module Dogtag
|
2
2
|
class Generator
|
3
|
-
def initialize(count = 1)
|
3
|
+
def initialize(data_type, count = 1)
|
4
|
+
unless data_type.is_a? Integer
|
5
|
+
raise ArgumentError, 'data_type must be an integer'
|
6
|
+
end
|
7
|
+
|
8
|
+
unless Dogtag::DATA_TYPE_ALLOWED_RANGE.include? data_type
|
9
|
+
raise ArgumentError, "data_type is outside the allowed range of #{Dogtag::DATA_TYPE_ALLOWED_RANGE}"
|
10
|
+
end
|
11
|
+
|
12
|
+
raise ArgumentError, 'count must be an integer' unless count.is_a? Integer
|
13
|
+
raise ArgumentError, 'count must be a positive number' if count < 1
|
14
|
+
|
15
|
+
@data_type = data_type
|
4
16
|
@count = count
|
5
17
|
end
|
6
18
|
|
@@ -9,6 +21,7 @@ module Dogtag
|
|
9
21
|
(
|
10
22
|
shifted_timestamp |
|
11
23
|
shifted_logical_shard_id |
|
24
|
+
shifted_data_type |
|
12
25
|
(sequence << Dogtag::SEQUENCE_SHIFT)
|
13
26
|
)
|
14
27
|
end
|
@@ -16,19 +29,23 @@ module Dogtag
|
|
16
29
|
|
17
30
|
private
|
18
31
|
|
19
|
-
attr_reader :count
|
32
|
+
attr_reader :data_type, :count
|
20
33
|
|
21
34
|
def shifted_timestamp
|
22
35
|
timestamp = Timestamp.from_redis(response.seconds, response.microseconds_part)
|
23
36
|
timestamp.with_epoch(Dogtag::CUSTOM_EPOCH).milliseconds << Dogtag::TIMESTAMP_SHIFT
|
24
37
|
end
|
25
38
|
|
39
|
+
def shifted_data_type
|
40
|
+
data_type << Dogtag::DATA_TYPE_SHIFT
|
41
|
+
end
|
42
|
+
|
26
43
|
def shifted_logical_shard_id
|
27
44
|
response.logical_shard_id << Dogtag::LOGICAL_SHARD_ID_SHIFT
|
28
45
|
end
|
29
46
|
|
30
47
|
def response
|
31
|
-
@response ||= Request.new(count).response
|
48
|
+
@response ||= Request.new(data_type, count).response
|
32
49
|
end
|
33
50
|
end
|
34
51
|
end
|
data/lib/dogtag/id.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
module Dogtag
|
2
2
|
class Id
|
3
3
|
SEQUENCE_MAP = ~(-1 << Dogtag::SEQUENCE_BITS) << Dogtag::SEQUENCE_SHIFT
|
4
|
+
DATA_TYPE_MAP = ~(-1 << Dogtag::DATA_TYPE_BITS) << Dogtag::DATA_TYPE_SHIFT
|
4
5
|
LOGICAL_SHARD_ID_MAP = (~(-1 << Dogtag::LOGICAL_SHARD_ID_BITS)) << Dogtag::LOGICAL_SHARD_ID_SHIFT
|
5
6
|
TIMESTAMP_MAP = ~(-1 << Dogtag::TIMESTAMP_BITS) << Dogtag::TIMESTAMP_SHIFT
|
6
7
|
|
@@ -22,6 +23,10 @@ module Dogtag
|
|
22
23
|
(id & LOGICAL_SHARD_ID_MAP) >> Dogtag::LOGICAL_SHARD_ID_SHIFT
|
23
24
|
end
|
24
25
|
|
26
|
+
def data_type
|
27
|
+
(id & DATA_TYPE_MAP) >> Dogtag::DATA_TYPE_SHIFT
|
28
|
+
end
|
29
|
+
|
25
30
|
def sequence
|
26
31
|
(id & SEQUENCE_MAP) >> Dogtag::SEQUENCE_SHIFT
|
27
32
|
end
|
data/lib/dogtag/mixins/redis.rb
CHANGED
data/lib/dogtag/request.rb
CHANGED
@@ -2,17 +2,19 @@ module Dogtag
|
|
2
2
|
class Request
|
3
3
|
include Dogtag::Mixins::Redis
|
4
4
|
|
5
|
-
MAX_SEQUENCE = ~(-1 << Dogtag::SEQUENCE_BITS)
|
6
|
-
MIN_LOGICAL_SHARD_ID = 1
|
7
|
-
MAX_LOGICAL_SHARD_ID = ~(-1 << Dogtag::LOGICAL_SHARD_ID_BITS)
|
8
5
|
LUA_SCRIPT_PATH = 'lua/id-generation.lua'.freeze
|
9
6
|
MAX_TRIES = 5
|
10
7
|
|
11
|
-
def initialize(count = 1)
|
8
|
+
def initialize(data_type, count = 1)
|
9
|
+
raise ArgumentError, 'data_type must be a number' unless data_type.is_a? Numeric
|
10
|
+
unless Dogtag::DATA_TYPE_ALLOWED_RANGE.include? data_type
|
11
|
+
raise ArgumentError, "data_type is outside the allowed range of #{Dogtag::DATA_TYPE_ALLOWED_RANGE}"
|
12
|
+
end
|
12
13
|
raise ArgumentError, 'count must be a number' unless count.is_a? Numeric
|
13
14
|
raise ArgumentError, 'count must be greater than zero' unless count > 0
|
14
15
|
|
15
16
|
@tries = 0
|
17
|
+
@data_type = data_type
|
16
18
|
@count = count
|
17
19
|
end
|
18
20
|
|
@@ -22,7 +24,7 @@ module Dogtag
|
|
22
24
|
|
23
25
|
private
|
24
26
|
|
25
|
-
attr_reader :count
|
27
|
+
attr_reader :data_type, :count
|
26
28
|
|
27
29
|
def lua_script
|
28
30
|
File.read(
|
@@ -31,12 +33,7 @@ module Dogtag
|
|
31
33
|
end
|
32
34
|
|
33
35
|
def lua_args
|
34
|
-
lua_args = [
|
35
|
-
MAX_SEQUENCE,
|
36
|
-
MIN_LOGICAL_SHARD_ID,
|
37
|
-
MAX_LOGICAL_SHARD_ID,
|
38
|
-
count
|
39
|
-
]
|
36
|
+
lua_args = [ MAX_SEQUENCE, data_type, count ]
|
40
37
|
end
|
41
38
|
|
42
39
|
# NOTE: If too many requests come in inside of a millisecond the Lua script
|
data/lib/dogtag/version.rb
CHANGED
data/lua/id-generation.lua
CHANGED
@@ -1,23 +1,51 @@
|
|
1
|
-
local
|
2
|
-
local
|
3
|
-
local logical_shard_id_key = 'dogtag-generator-logical-shard-id'
|
1
|
+
local logical_shard_id_range_key = 'dogtag-generator-logical-shard-id-range'
|
2
|
+
local last_logical_shard_id_key = 'dogtag-generator-last-logical-shard-id'
|
4
3
|
|
5
4
|
local max_sequence = tonumber(KEYS[1])
|
6
|
-
local
|
7
|
-
local
|
8
|
-
|
5
|
+
local data_type = tonumber(KEYS[2])
|
6
|
+
local num_ids = tonumber(KEYS[3])
|
7
|
+
|
8
|
+
-- Allow one server to acts as multiple shards
|
9
|
+
local logical_shard_id_range = redis.call('SORT', logical_shard_id_range_key)
|
10
|
+
if next(logical_shard_id_range) == nil then
|
11
|
+
redis.log(redis.LOG_WARNING, 'Dogtag: ' .. logical_shard_id_range_key .. ' has not been set.')
|
12
|
+
return redis.error_reply('Dogtag: ' .. logical_shard_id_range_key .. ' has not been set.')
|
13
|
+
end
|
14
|
+
local logical_shard_id_min = tonumber(logical_shard_id_range[1])
|
15
|
+
local logical_shard_id_max = tonumber(logical_shard_id_range[2])
|
16
|
+
|
17
|
+
local logical_shard_id = nil
|
18
|
+
if redis.call('EXISTS', last_logical_shard_id_key) == 0 then
|
19
|
+
logical_shard_id = logical_shard_id_min
|
20
|
+
else
|
21
|
+
local last_shard_id = tonumber(redis.call('GET', last_logical_shard_id_key))
|
22
|
+
|
23
|
+
if last_shard_id >= logical_shard_id_max or last_shard_id < logical_shard_id_min then
|
24
|
+
logical_shard_id = logical_shard_id_min
|
25
|
+
else
|
26
|
+
logical_shard_id = last_shard_id + 1
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
redis.call('SET', last_logical_shard_id_key, logical_shard_id)
|
31
|
+
|
32
|
+
--[[
|
33
|
+
Scope lock and sequence keys to the specific data_type being requested.
|
34
|
+
Ideally, we'd also use the logical_shard_id in the keys so that any per-millisecond limitations would only be per-shard,
|
35
|
+
but unfortunately the whole "pure function" limitation keeps us from using a random shard_id here. The best solution may
|
36
|
+
be to round robin the shard ID by incrementing a Redis key on each call.
|
37
|
+
]]--
|
38
|
+
local lock_key = 'dogtag-generator-lock-' .. logical_shard_id .. '-' .. data_type
|
39
|
+
local sequence_key = 'dogtag-generator-sequence-' .. logical_shard_id .. '-' .. data_type
|
9
40
|
|
10
41
|
if redis.call('EXISTS', lock_key) == 1 then
|
11
42
|
redis.log(redis.LOG_NOTICE, 'Dogtag: Cannot generate ID, waiting for lock to expire.')
|
12
43
|
return redis.error_reply('Dogtag: Cannot generate ID, waiting for lock to expire.')
|
13
44
|
end
|
14
45
|
|
15
|
-
--
|
16
|
-
Increment by a set number, this can
|
17
|
-
--]]
|
46
|
+
-- Increment by a set number
|
18
47
|
local end_sequence = redis.call('INCRBY', sequence_key, num_ids)
|
19
48
|
local start_sequence = end_sequence - num_ids + 1
|
20
|
-
local logical_shard_id = tonumber(redis.call('GET', logical_shard_id_key)) or -1
|
21
49
|
|
22
50
|
if end_sequence >= max_sequence then
|
23
51
|
--[[
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dogtag
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Adam Crownoble
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-05-
|
11
|
+
date: 2017-05-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: redis
|