dalli 2.7.4 → 3.2.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +5 -5
  2. data/{History.md → CHANGELOG.md} +219 -0
  3. data/Gemfile +14 -5
  4. data/LICENSE +1 -1
  5. data/README.md +33 -205
  6. data/lib/dalli/cas/client.rb +2 -57
  7. data/lib/dalli/client.rb +254 -253
  8. data/lib/dalli/compressor.rb +13 -2
  9. data/lib/dalli/key_manager.rb +121 -0
  10. data/lib/dalli/options.rb +7 -7
  11. data/lib/dalli/pid_cache.rb +40 -0
  12. data/lib/dalli/pipelined_getter.rb +177 -0
  13. data/lib/dalli/protocol/base.rb +250 -0
  14. data/lib/dalli/protocol/binary/request_formatter.rb +117 -0
  15. data/lib/dalli/protocol/binary/response_header.rb +36 -0
  16. data/lib/dalli/protocol/binary/response_processor.rb +239 -0
  17. data/lib/dalli/protocol/binary/sasl_authentication.rb +60 -0
  18. data/lib/dalli/protocol/binary.rb +173 -0
  19. data/lib/dalli/protocol/connection_manager.rb +255 -0
  20. data/lib/dalli/protocol/meta/key_regularizer.rb +31 -0
  21. data/lib/dalli/protocol/meta/request_formatter.rb +121 -0
  22. data/lib/dalli/protocol/meta/response_processor.rb +211 -0
  23. data/lib/dalli/protocol/meta.rb +178 -0
  24. data/lib/dalli/protocol/response_buffer.rb +54 -0
  25. data/lib/dalli/protocol/server_config_parser.rb +86 -0
  26. data/lib/dalli/protocol/ttl_sanitizer.rb +45 -0
  27. data/lib/dalli/protocol/value_compressor.rb +85 -0
  28. data/lib/dalli/protocol/value_marshaller.rb +59 -0
  29. data/lib/dalli/protocol/value_serializer.rb +91 -0
  30. data/lib/dalli/protocol.rb +8 -0
  31. data/lib/dalli/ring.rb +97 -86
  32. data/lib/dalli/server.rb +4 -719
  33. data/lib/dalli/servers_arg_normalizer.rb +54 -0
  34. data/lib/dalli/socket.rb +118 -120
  35. data/lib/dalli/version.rb +5 -1
  36. data/lib/dalli.rb +45 -14
  37. data/lib/rack/session/dalli.rb +162 -42
  38. metadata +40 -98
  39. data/Performance.md +0 -42
  40. data/Rakefile +0 -43
  41. data/dalli.gemspec +0 -29
  42. data/lib/action_dispatch/middleware/session/dalli_store.rb +0 -81
  43. data/lib/active_support/cache/dalli_store.rb +0 -372
  44. data/lib/dalli/railtie.rb +0 -7
  45. data/test/benchmark_test.rb +0 -243
  46. data/test/helper.rb +0 -56
  47. data/test/memcached_mock.rb +0 -201
  48. data/test/sasl/memcached.conf +0 -1
  49. data/test/sasl/sasldb +0 -1
  50. data/test/test_active_support.rb +0 -541
  51. data/test/test_cas_client.rb +0 -107
  52. data/test/test_compressor.rb +0 -52
  53. data/test/test_dalli.rb +0 -682
  54. data/test/test_encoding.rb +0 -32
  55. data/test/test_failover.rb +0 -137
  56. data/test/test_network.rb +0 -64
  57. data/test/test_rack_session.rb +0 -341
  58. data/test/test_ring.rb +0 -85
  59. data/test/test_sasl.rb +0 -105
  60. data/test/test_serializer.rb +0 -29
  61. data/test/test_server.rb +0 -110
data/lib/dalli/ring.rb CHANGED
@@ -1,59 +1,103 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'digest/sha1'
2
4
  require 'zlib'
3
5
 
4
6
  module Dalli
7
+ ##
8
+ # An implementation of a consistent hash ring, designed to minimize
9
+ # the cache miss impact of adding or removing servers from the ring.
10
+ # That is, adding or removing a server from the ring should impact
11
+ # the key -> server mapping of ~ 1/N of the stored keys where N is the
12
+ # number of servers in the ring. This is done by creating a large
13
+ # number of "points" per server, distributed over the space
14
+ # 0x00000000 - 0xFFFFFFFF. For a given key, we calculate the CRC32
15
+ # hash, and find the nearest "point" that is less than or equal to the
16
+ # the key's hash. In this implemetation, each "point" is represented
17
+ # by a Dalli::Ring::Entry.
18
+ ##
5
19
  class Ring
20
+ # The number of entries on the continuum created per server
21
+ # in an equally weighted scenario.
6
22
  POINTS_PER_SERVER = 160 # this is the default in libmemcached
7
23
 
8
24
  attr_accessor :servers, :continuum
9
25
 
10
- def initialize(servers, options)
11
- @servers = servers
12
- @continuum = nil
13
- if servers.size > 1
14
- total_weight = servers.inject(0) { |memo, srv| memo + srv.weight }
15
- continuum = []
16
- servers.each do |server|
17
- entry_count_for(server, servers.size, total_weight).times do |idx|
18
- hash = Digest::SHA1.hexdigest("#{server.name}:#{idx}")
19
- value = Integer("0x#{hash[0..7]}")
20
- continuum << Dalli::Ring::Entry.new(value, server)
21
- end
22
- end
23
- @continuum = continuum.sort { |a, b| a.value <=> b.value }
26
+ def initialize(servers_arg, protocol_implementation, options)
27
+ @servers = servers_arg.map do |s|
28
+ protocol_implementation.new(s, options)
24
29
  end
30
+ @continuum = nil
31
+ @continuum = build_continuum(servers) if servers.size > 1
25
32
 
26
33
  threadsafe! unless options[:threadsafe] == false
27
34
  @failover = options[:failover] != false
28
35
  end
29
36
 
30
37
  def server_for_key(key)
31
- if @continuum
32
- hkey = hash_for(key)
33
- 20.times do |try|
34
- entryidx = binary_search(@continuum, hkey)
35
- server = @continuum[entryidx].server
36
- return server if server.alive?
37
- break unless @failover
38
- hkey = hash_for("#{try}#{key}")
39
- end
40
- else
41
- server = @servers.first
42
- return server if server && server.alive?
38
+ server = if @continuum
39
+ server_from_continuum(key)
40
+ else
41
+ @servers.first
42
+ end
43
+
44
+ # Note that the call to alive? has the side effect of initializing
45
+ # the socket
46
+ return server if server&.alive?
47
+
48
+ raise Dalli::RingError, 'No server available'
49
+ end
50
+
51
+ def server_from_continuum(key)
52
+ hkey = hash_for(key)
53
+ 20.times do |try|
54
+ server = server_for_hash_key(hkey)
55
+
56
+ # Note that the call to alive? has the side effect of initializing
57
+ # the socket
58
+ return server if server.alive?
59
+ break unless @failover
60
+
61
+ hkey = hash_for("#{try}#{key}")
43
62
  end
63
+ nil
64
+ end
44
65
 
45
- raise Dalli::RingError, "No server available"
66
+ def keys_grouped_by_server(key_arr)
67
+ key_arr.group_by do |key|
68
+ server_for_key(key)
69
+ rescue Dalli::RingError
70
+ Dalli.logger.debug { "unable to get key #{key}" }
71
+ nil
72
+ end
46
73
  end
47
74
 
48
75
  def lock
49
- @servers.each { |s| s.lock! }
76
+ @servers.each(&:lock!)
50
77
  begin
51
- return yield
78
+ yield
52
79
  ensure
53
- @servers.each { |s| s.unlock! }
80
+ @servers.each(&:unlock!)
54
81
  end
55
82
  end
56
83
 
84
+ def pipeline_consume_and_ignore_responses
85
+ @servers.each do |s|
86
+ s.request(:noop)
87
+ rescue Dalli::NetworkError
88
+ # Ignore this error, as it indicates the socket is unavailable
89
+ # and there's no need to flush
90
+ end
91
+ end
92
+
93
+ def socket_timeout
94
+ @servers.first.socket_timeout
95
+ end
96
+
97
+ def close
98
+ @servers.each(&:close)
99
+ end
100
+
57
101
  private
58
102
 
59
103
  def threadsafe!
@@ -70,73 +114,40 @@ module Dalli
70
114
  ((total_servers * POINTS_PER_SERVER * server.weight) / Float(total_weight)).floor
71
115
  end
72
116
 
73
- # Native extension to perform the binary search within the continuum
74
- # space. Fallback to a pure Ruby version if the compilation doesn't work.
75
- # optional for performance and only necessary if you are using multiple
76
- # memcached servers.
77
- begin
78
- require 'inline'
79
- inline do |builder|
80
- builder.c <<-EOM
81
- int binary_search(VALUE ary, unsigned int r) {
82
- long upper = RARRAY_LEN(ary) - 1;
83
- long lower = 0;
84
- long idx = 0;
85
- ID value = rb_intern("value");
86
- VALUE continuumValue;
87
- unsigned int l;
88
-
89
- while (lower <= upper) {
90
- idx = (lower + upper) / 2;
91
-
92
- continuumValue = rb_funcall(RARRAY_PTR(ary)[idx], value, 0);
93
- l = NUM2UINT(continuumValue);
94
- if (l == r) {
95
- return idx;
96
- }
97
- else if (l > r) {
98
- upper = idx - 1;
99
- }
100
- else {
101
- lower = idx + 1;
102
- }
103
- }
104
- return upper;
105
- }
106
- EOM
107
- end
108
- rescue LoadError
117
+ def server_for_hash_key(hash_key)
109
118
  # Find the closest index in the Ring with value <= the given value
110
- def binary_search(ary, value)
111
- upper = ary.size - 1
112
- lower = 0
113
- idx = 0
114
-
115
- while (lower <= upper) do
116
- idx = (lower + upper) / 2
117
- comp = ary[idx].value <=> value
118
-
119
- if comp == 0
120
- return idx
121
- elsif comp > 0
122
- upper = idx - 1
123
- else
124
- lower = idx + 1
125
- end
119
+ entryidx = @continuum.bsearch_index { |entry| entry.value > hash_key }
120
+ if entryidx.nil?
121
+ entryidx = @continuum.size - 1
122
+ else
123
+ entryidx -= 1
124
+ end
125
+ @continuum[entryidx].server
126
+ end
127
+
128
+ def build_continuum(servers)
129
+ continuum = []
130
+ total_weight = servers.inject(0) { |memo, srv| memo + srv.weight }
131
+ servers.each do |server|
132
+ entry_count_for(server, servers.size, total_weight).times do |idx|
133
+ hash = Digest::SHA1.hexdigest("#{server.name}:#{idx}")
134
+ value = Integer("0x#{hash[0..7]}")
135
+ continuum << Dalli::Ring::Entry.new(value, server)
126
136
  end
127
- return upper
128
137
  end
138
+ continuum.sort_by(&:value)
129
139
  end
130
140
 
141
+ ##
142
+ # Represents a point in the consistent hash ring implementation.
143
+ ##
131
144
  class Entry
132
- attr_reader :value
133
- attr_reader :server
145
+ attr_reader :value, :server
134
146
 
135
147
  def initialize(val, srv)
136
148
  @value = val
137
149
  @server = srv
138
150
  end
139
151
  end
140
-
141
152
  end
142
153
  end