dogtag 0.1.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 040cf6345d43641af35bd1982b190cde7143c481
4
- data.tar.gz: d2f72279c3599a6fc07bee47683b0a3d431bc425
3
+ metadata.gz: 3873a444c41218413a933994fd93c9683588e6f5
4
+ data.tar.gz: 561a47c6e821efc9d602d04b985ae1e05e1f1809
5
5
  SHA512:
6
- metadata.gz: a0d87a8746ea3e7c9fb47a49ebb68e326810d871c59046119a319c2847cf149b7f14c2cd9b0f094ff6d5cb9dce39ba8d9ce6cbb3e571db942c50b27823536e39
7
- data.tar.gz: ee27ededce69e8de0f1e6c1f859ef05e9c693f98d8616c60d574001843a334946893d9182101a26bb89d6ea6705dd6bc70774aca2d2f8867312c2430a70ba8fc
6
+ metadata.gz: 0ddf7a7f873c0c3b6a31c80b189cde967aea42569afbec71aeb95ae3361049176d43a525d4270da2ad4dc701fdab2f64b2f9c9aa69da67cbfbeda5f10a79e9db
7
+ data.tar.gz: dd72cbd167d3c91f5fd530108a17f1ab4ecf6fe50fd4efca81aaee763bbd5c69a8904d98ab947f2dd7e070a12b828b9bedbec78e543330013a7e544bf319cdb8
@@ -6,32 +6,58 @@ module Dogtag
6
6
 
7
7
  CUSTOM_EPOCH = 1483228800000 # in milliseconds
8
8
 
9
- TIMESTAMP_BITS = 41
10
- LOGICAL_SHARD_ID_BITS = 10
11
- SEQUENCE_BITS = 12
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
- LOGICAL_SHARD_ID_SHIFT = SEQUENCE_BITS
15
- TIMESTAMP_SHIFT = SEQUENCE_BITS + LOGICAL_SHARD_ID_BITS
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
- LOGICAL_SHARD_ID_KEY = 'dogtag-generator-logical-shard-id'.freeze
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
- def self.logical_shard_id=(logical_shard_id)
20
- redis.set LOGICAL_SHARD_ID_KEY, logical_shard_id
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
@@ -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
@@ -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
@@ -2,7 +2,7 @@ module Dogtag
2
2
  module Mixins
3
3
  module Redis
4
4
  def redis
5
- # TODO: Redis config
5
+ # TODO: Redis config for multiple servers
6
6
  @redis ||= ::Redis.new
7
7
  end
8
8
  end
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Dogtag
2
- VERSION = '0.1.0'.freeze
2
+ VERSION = '1.0.0'.freeze
3
3
  end
@@ -1,23 +1,51 @@
1
- local lock_key = 'dogtag-generator-lock'
2
- local sequence_key = 'dogtag-generator-sequence'
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 min_logical_shard_id = tonumber(KEYS[2])
7
- local max_logical_shard_id = tonumber(KEYS[3])
8
- local num_ids = tonumber(KEYS[4])
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: 0.1.0
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-09 00:00:00.000000000 Z
11
+ date: 2017-05-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis