splitclient-rb 3.3.0 → 4.0.0.pre.rc1

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: 63050dc6d8209d6fdd050462bbafabc650e74fc7
4
- data.tar.gz: cbb40be59c858289ae351c6115e147a7062362d1
3
+ metadata.gz: c6868ad86ef9ff027cff7dbdb58cfa973c7d02d2
4
+ data.tar.gz: 254804356acd6e57dedd0f01fcec6549374651e7
5
5
  SHA512:
6
- metadata.gz: 3e9be02c6bb231fce90773edac53d39af03c634012a39dd96ac6480009836c9e34102b8651b8b55f6449a967a0a06292b89b4186ada8e62ac81b3c158956d1de
7
- data.tar.gz: a1875dd25ac3602f5660224dd5a890a86c058820c08a716e32c9c29bbdedc35ea26fce9e47977fa5224a382f93cea3140973e0bf8b21be99b4f9f5d6c82ff7be
6
+ metadata.gz: 2d6f2d5097557c0eda262290c9d80a6a031e1c2b7f4acab2dc3fb163113136241435ccbbe137cd5d5e63f37d46dcea2c9514d0269a792f3ff7f4ea9359db8bc3
7
+ data.tar.gz: 7a164d6e280b8223108d418392d7051ef2b9f7b3d78389aea46d772180b35af68264cdc3e0943f2634c1ccac0a4b1cfa9fbaf243befc3058a39426193d8b0f0d
data/CHANGES.txt CHANGED
@@ -1,3 +1,7 @@
1
+ 4.0.0
2
+ - Add support for murmur3 algo
3
+ - Optimize memory usage
4
+
1
5
  3.3.0
2
6
  - Add support for traffic allocation
3
7
 
data/Detailed-README.md CHANGED
@@ -467,6 +467,14 @@ This will generate a file gemspec with the right version, then:
467
467
  gem push splitclient-rb-<VERSION>.gem
468
468
  ```
469
469
 
470
+ ## Benchmarking
471
+
472
+ To benchmark hashing algorithms (currently we're using MurmurHash) you'll need to run:
473
+
474
+ ```bash
475
+ bundle exec rake benchmark_hashing_algorithm
476
+ ```
477
+
470
478
  ## Contributing
471
479
 
472
480
  Bug reports and pull requests are welcome on GitHub at https://github.com/splitio/ruby-client.
data/NEWS CHANGED
@@ -1,3 +1,8 @@
1
+ 4.0.0
2
+
3
+ Add support for murmur3 hashing algorithm
4
+ Optimize gem memory usage
5
+
1
6
  3.3.0
2
7
 
3
8
  Add support for traffic allocation
@@ -6,7 +6,7 @@ module SplitIoClient
6
6
  module MemoryAdapters
7
7
  # Memory adapter implementation, which stores everything inside thread-safe Map
8
8
  class MapAdapter
9
- def initialize(_ = nil)
9
+ def initialize
10
10
  @map = Concurrent::Map.new
11
11
  end
12
12
 
@@ -1,110 +1,118 @@
1
+ require 'digest/murmurhash'
2
+
1
3
  module SplitIoClient
2
4
  # Misc class in charge of providing hash functions and
3
5
  # determination of treatment based on concept of buckets
4
6
  # based on provided key
5
7
  #
6
8
  class Splitter < NoMethodError
7
-
8
- #
9
- # Checks if the partiotion size is 100%
10
- #
11
- # @param partitions [object] array of partitions
12
- #
13
- # @return [boolean] true if partition is 100% false otherwise
14
- def self.hundred_percent_one_treatment?(partitions)
15
- if partitions.size != 1
16
- return false
9
+ class << self
10
+ #
11
+ # Checks if the partiotion size is 100%
12
+ #
13
+ # @param partitions [object] array of partitions
14
+ #
15
+ # @return [boolean] true if partition is 100% false otherwise
16
+ def hundred_percent_one_treatment?(partitions)
17
+ (partitions.size != 1) ? false : (partitions.first.size == 100)
17
18
  end
18
- return (partitions.first).size == 100
19
- end
20
19
 
20
+ #
21
+ # gets the appropriate treatment based on id, seed and partition value
22
+ #
23
+ # @param id [string] user key
24
+ # @param seed [number] seed for the user key
25
+ # @param partitions [object] array of partitions
26
+ #
27
+ # @return traetment [object] treatment value
28
+ def get_treatment(id, seed, partitions, legacy_algo)
29
+ legacy = (legacy_algo == 1 || legacy_algo == nil) ? true : false
21
30
 
22
- #
23
- # gets the appropriate treatment based on id, seed and partition value
24
- #
25
- # @param id [string] user key
26
- # @param seed [number] seed for the user key
27
- # @param partitions [object] array of partitions
28
- #
29
- # @return traetment [object] treatment value
30
- def self.get_treatment(id, seed, partitions)
31
- if partitions.empty?
32
- return Treatments::CONTROL
33
- end
31
+ if partitions.empty?
32
+ return Treatments::CONTROL
33
+ end
34
34
 
35
- if hundred_percent_one_treatment?(partitions)
36
- return (partitions.first).treatment
37
- end
35
+ if hundred_percent_one_treatment?(partitions)
36
+ return (partitions.first).treatment
37
+ end
38
38
 
39
- return get_treatment_for_key(bucket(hash(id, seed)), partitions)
40
- end
39
+ return get_treatment_for_key(bucket(count_hash(id, seed, legacy_algo)), partitions)
40
+ end
41
41
 
42
- #
43
- # returns a hash value for the give key, sedd pair
44
- #
45
- # @param key [string] user key
46
- # @param seed [number] seed for the user key
47
- #
48
- # @return hash [string] hash value
49
- def self.hash(key, seed)
50
- h = 0
51
- for i in 0..key.length-1
52
- h = to_int32(31 * h + key[i].ord)
42
+ # returns a hash value for the give key, seed pair
43
+ #
44
+ # @param key [String] user key
45
+ # @param seed [Fixnum] seed for the user key
46
+ #
47
+ # @return hash [String] hash value
48
+ def count_hash(key, seed, legacy)
49
+ legacy ? legacy_hash(key, seed) : murmur_hash(key, seed)
53
50
  end
54
- h^seed
55
- end
56
51
 
57
- #
58
- # misc method to convert ruby number to int 32 since overflow is handled different to java
59
- #
60
- # @param number [number] ruby number value
61
- #
62
- # @return [int] returns the int 32 value of the provided number
63
- def self.to_int32(number)
64
- begin
65
- sign = number < 0 ? -1 : 1
66
- abs = number.abs
67
- return 0 if abs == 0 || abs == Float::INFINITY
68
- rescue
69
- return 0
52
+ def murmur_hash(key, seed)
53
+ Digest::MurmurHash3_x86_32.rawdigest(key, [seed].pack('L'))
70
54
  end
71
55
 
72
- pos_int = sign * abs.floor
73
- int_32bit = pos_int % 2**32
56
+ def legacy_hash(key, seed)
57
+ h = 0
74
58
 
75
- return int_32bit - 2**32 if int_32bit >= 2**31
76
- int_32bit
77
- end
59
+ for i in 0..key.length-1
60
+ h = to_int32(31 * h + key[i].ord)
61
+ end
78
62
 
79
- #
80
- # returns the treatment for a bucket given the partitions
81
- #
82
- # @param bucket [number] bucket value
83
- # @param parittions [object] array of partitions
84
- #
85
- # @return treatment [treatment] treatment value for this bucket and partitions
86
- def self.get_treatment_for_key(bucket, partitions)
87
- buckets_covered_thus_far = 0
88
- partitions.each do |p|
89
- unless p.is_empty?
90
- buckets_covered_thus_far += p.size
91
- return p.treatment if buckets_covered_thus_far >= bucket
63
+ h^seed
64
+ end
65
+
66
+ #
67
+ # misc method to convert ruby number to int 32 since overflow is handled different to java
68
+ #
69
+ # @param number [number] ruby number value
70
+ #
71
+ # @return [int] returns the int 32 value of the provided number
72
+ def to_int32(number)
73
+ begin
74
+ sign = number < 0 ? -1 : 1
75
+ abs = number.abs
76
+ return 0 if abs == 0 || abs == Float::INFINITY
77
+ rescue
78
+ return 0
92
79
  end
80
+
81
+ pos_int = sign * abs.floor
82
+ int_32bit = pos_int % 2**32
83
+
84
+ return int_32bit - 2**32 if int_32bit >= 2**31
85
+ int_32bit
93
86
  end
94
87
 
95
- return Treatments::CONTROL
96
- end
88
+ #
89
+ # returns the treatment for a bucket given the partitions
90
+ #
91
+ # @param bucket [number] bucket value
92
+ # @param parittions [object] array of partitions
93
+ #
94
+ # @return treatment [treatment] treatment value for this bucket and partitions
95
+ def get_treatment_for_key(bucket, partitions)
96
+ buckets_covered_thus_far = 0
97
+ partitions.each do |p|
98
+ unless p.is_empty?
99
+ buckets_covered_thus_far += p.size
100
+ return p.treatment if buckets_covered_thus_far >= bucket
101
+ end
102
+ end
97
103
 
98
- #
99
- # returns bucket value for the given hash value
100
- #
101
- # @param hash_value [string] hash value
102
- #
103
- # @return bucket [number] bucket number
104
- def self.bucket(hash_value)
105
- (hash_value.abs % 100) + 1
106
- end
104
+ return Treatments::CONTROL
105
+ end
107
106
 
107
+ #
108
+ # returns bucket value for the given hash value
109
+ #
110
+ # @param hash_value [string] hash value
111
+ #
112
+ # @return bucket [number] bucket number
113
+ def bucket(hash_value)
114
+ (hash_value.abs % 100) + 1
115
+ end
116
+ end
108
117
  end
109
-
110
118
  end
@@ -23,6 +23,7 @@ module SplitIoClient
23
23
  def match(split, keys, attributes)
24
24
  in_rollout = false
25
25
  key = keys[:bucketing_key] ? keys[:bucketing_key] : keys[:matching_key]
26
+ legacy_algo = (split[:algo] == 1 || split[:algo] == nil) ? true : false
26
27
 
27
28
  split[:conditions].each do |c|
28
29
  condition = SplitIoClient::Condition.new(c)
@@ -31,7 +32,7 @@ module SplitIoClient
31
32
 
32
33
  if !in_rollout && condition.type == SplitIoClient::Condition::TYPE_ROLLOUT
33
34
  if split[:trafficAllocation] < 100
34
- bucket = Splitter.bucket(Splitter.hash(key, split[:trafficAllocationSeed].to_i))
35
+ bucket = Splitter.bucket(Splitter.count_hash(key, split[:trafficAllocationSeed].to_i, legacy_algo))
35
36
 
36
37
  if bucket >= split[:trafficAllocation]
37
38
  return treatment(Models::Label::NOT_IN_SPLIT, @default_treatment, split[:changeNumber])
@@ -42,7 +43,8 @@ module SplitIoClient
42
43
  end
43
44
 
44
45
  if matcher_type(condition).match?(keys[:matching_key], attributes)
45
- result = Splitter.get_treatment(key, split[:seed], condition.partitions)
46
+ key = keys[:bucketing_key] ? keys[:bucketing_key] : keys[:matching_key]
47
+ result = Splitter.get_treatment(key, split[:seed], condition.partitions, split[:algo])
46
48
 
47
49
  if result.nil?
48
50
  return treatment(Models::Label::NO_RULE_MATCHED, @default_treatment, split[:changeNumber])
@@ -1,6 +1,5 @@
1
1
  module SplitIoClient
2
2
  class SplitClient
3
-
4
3
  #
5
4
  # Creates a new split client instance that connects to split.io API.
6
5
  #
@@ -79,7 +78,7 @@ module SplitIoClient
79
78
 
80
79
  store_impression(
81
80
  split_name, matching_key, bucketing_key,
82
- { treatment: SplitIoClient::Treatments::CONTROL, label: Models::Label::EXCEPTION },
81
+ { treatment: SplitIoClient::Treatments::CONTROL, label: Engine::Models::Label::EXCEPTION },
83
82
  store_impressions
84
83
  )
85
84
 
@@ -98,7 +97,7 @@ module SplitIoClient
98
97
 
99
98
  store_impression(
100
99
  split_name, matching_key, bucketing_key,
101
- { treatment: SplitIoClient::Treatments::CONTROL, label: Models::Label::EXCEPTION },
100
+ { treatment: SplitIoClient::Treatments::CONTROL, label: Engine::Models::Label::EXCEPTION },
102
101
  store_impressions
103
102
  )
104
103
 
@@ -141,5 +140,11 @@ module SplitIoClient
141
140
  treatment_label_change_number[:treatment]
142
141
  end
143
142
  end
143
+
144
+ private
145
+
146
+ def split_treatment
147
+ @split_treatment ||= SplitIoClient::Engine::Parser::SplitTreatment.new(@segments_repository)
148
+ end
144
149
  end
145
150
  end
@@ -185,12 +185,7 @@ module SplitIoClient
185
185
  def self.init_cache_adapter(adapter, data_structure, redis_url = nil, impressions_queue_size = nil)
186
186
  case adapter
187
187
  when :memory
188
- # takes :memory_adapter (symbol) and returns MemoryAdapter (string)
189
- adapter = SplitIoClient::Cache::Adapters::MemoryAdapters.const_get(
190
- data_structure.to_s.split('_').collect(&:capitalize).join
191
- ).new(impressions_queue_size)
192
-
193
- SplitIoClient::Cache::Adapters::MemoryAdapter.new(adapter)
188
+ SplitIoClient::Cache::Adapters::MemoryAdapter.new(map_memory_adapter(data_structure, impressions_queue_size))
194
189
  when :redis
195
190
  begin
196
191
  require 'redis'
@@ -202,6 +197,15 @@ module SplitIoClient
202
197
  end
203
198
  end
204
199
 
200
+ def self.map_memory_adapter(name, queue_size)
201
+ case name
202
+ when :map_adapter
203
+ SplitIoClient::Cache::Adapters::MemoryAdapters::MapAdapter.new
204
+ when :queue_adapter
205
+ SplitIoClient::Cache::Adapters::MemoryAdapters::QueueAdapter.new(queue_size)
206
+ end
207
+ end
208
+
205
209
  def self.default_mode
206
210
  :standalone
207
211
  end
@@ -1,3 +1,3 @@
1
1
  module SplitIoClient
2
- VERSION = '3.3.0'
2
+ VERSION = '4.0.0-rc1'
3
3
  end
@@ -27,6 +27,7 @@ Gem::Specification.new do |spec|
27
27
  spec.add_development_dependency "pry"
28
28
  spec.add_development_dependency "pry-byebug"
29
29
  spec.add_development_dependency "simplecov"
30
+ spec.add_development_dependency "allocation_stats"
30
31
 
31
32
  spec.add_runtime_dependency "json", "~> 1.8"
32
33
  spec.add_runtime_dependency "thread_safe"
@@ -36,4 +37,5 @@ Gem::Specification.new do |spec|
36
37
  spec.add_runtime_dependency "faraday_middleware"
37
38
  spec.add_runtime_dependency "net-http-persistent", "<= 2.9.4"
38
39
  spec.add_runtime_dependency "redis"
40
+ spec.add_runtime_dependency "digest-murmurhash"
39
41
  end
@@ -0,0 +1,51 @@
1
+ require 'benchmark'
2
+ require 'digest/murmurhash'
3
+
4
+ desc 'Benchmark murmur32 hashing algorithm'
5
+
6
+ task :benchmark_hashing_algorithm do
7
+ iterations = 200_000
8
+ key = SecureRandom.uuid
9
+
10
+ Benchmark.bmbm do |x|
11
+ x.report('MurmurHash1') do
12
+ iterations.times { Digest::MurmurHash1.rawdigest(key) }
13
+ end
14
+
15
+ x.report('MurmurHash2') do
16
+ iterations.times { Digest::MurmurHash2.rawdigest(key) }
17
+ end
18
+
19
+ x.report('MurmurHash2A') do
20
+ iterations.times { Digest::MurmurHash2A.rawdigest(key) }
21
+ end
22
+
23
+ x.report('LegacyHash') do
24
+ iterations.times { legacy_hash(key, 123) }
25
+ end
26
+ end
27
+ end
28
+
29
+ def legacy_hash(key, seed)
30
+ h = 0
31
+ for i in 0..key.length-1
32
+ h = to_int32(31 * h + key[i].ord)
33
+ end
34
+ h^seed
35
+ end
36
+
37
+ def to_int32(number)
38
+ begin
39
+ sign = number < 0 ? -1 : 1
40
+ abs = number.abs
41
+ return 0 if abs == 0 || abs == Float::INFINITY
42
+ rescue
43
+ return 0
44
+ end
45
+
46
+ pos_int = sign * abs.floor
47
+ int_32bit = pos_int % 2**32
48
+
49
+ return int_32bit - 2**32 if int_32bit >= 2**31
50
+ int_32bit
51
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: splitclient-rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.3.0
4
+ version: 4.0.0.pre.rc1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Split Software
@@ -122,6 +122,20 @@ dependencies:
122
122
  - - ">="
123
123
  - !ruby/object:Gem::Version
124
124
  version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: allocation_stats
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
125
139
  - !ruby/object:Gem::Dependency
126
140
  name: json
127
141
  requirement: !ruby/object:Gem::Requirement
@@ -234,6 +248,20 @@ dependencies:
234
248
  - - ">="
235
249
  - !ruby/object:Gem::Version
236
250
  version: '0'
251
+ - !ruby/object:Gem::Dependency
252
+ name: digest-murmurhash
253
+ requirement: !ruby/object:Gem::Requirement
254
+ requirements:
255
+ - - ">="
256
+ - !ruby/object:Gem::Version
257
+ version: '0'
258
+ type: :runtime
259
+ prerelease: false
260
+ version_requirements: !ruby/object:Gem::Requirement
261
+ requirements:
262
+ - - ">="
263
+ - !ruby/object:Gem::Version
264
+ version: '0'
237
265
  description: Ruby client for using split SDK.
238
266
  email:
239
267
  - pato@split.io
@@ -313,6 +341,7 @@ files:
313
341
  - splitclient-rb.gemspec
314
342
  - splitio.yml.example
315
343
  - tasks/benchmark_get_treatment.rake
344
+ - tasks/benchmark_hashing_algorithm.rake
316
345
  - tasks/console.rake
317
346
  - tasks/rspec.rake
318
347
  homepage: https://github.com/splitio/ruby-client
@@ -330,9 +359,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
330
359
  version: '0'
331
360
  required_rubygems_version: !ruby/object:Gem::Requirement
332
361
  requirements:
333
- - - ">="
362
+ - - ">"
334
363
  - !ruby/object:Gem::Version
335
- version: '0'
364
+ version: 1.3.1
336
365
  requirements: []
337
366
  rubyforge_project:
338
367
  rubygems_version: 2.5.2