aerospike 2.13.0 → 2.14.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
  SHA256:
3
- metadata.gz: 7bef523a7e7500d8e39cb80f3b9ee6831a5ddc0bd6d782685181cae6a6c6ada5
4
- data.tar.gz: 760d626bffac6ed69bcdda048daf39b07a12a85264b67be0677ad5d552c5473c
3
+ metadata.gz: d0452d4d35ee6d5ce56586cc99b3a68c7e1b4a6c4f6334c6794414a483e2ef01
4
+ data.tar.gz: 328a5672510bb68e044a92c39fc1f04059daa53b4332eaa5a64dda04f5b3de78
5
5
  SHA512:
6
- metadata.gz: df19a0c2900463a35b73ba52bfae3bdb2c5f6cbdaba3ab17172219e96ad3de3878bbc1ff57403fbbffe0fb48cc147ee642297e2fe47c829d768e76b1e4dee0fa
7
- data.tar.gz: 528ade6f9abfb9b9e266439fefa6606f2623aec2503ec6c5466a5c3f9ffd9656debea49ff434f715f042c3ae319546e80352aa36cdc925ed351cab2633c75c4b
6
+ metadata.gz: 44c883336f3993d532c7989e1c6bf737deca2773cb444a4cfa6021bca35311c9d4fe5c74ddb73fa1274661435a127892f9a368bd1d94120e6965bee38b870216
7
+ data.tar.gz: ebac442b5b2ddc345b7d7713b0e8d7f0aaaba9da24aabe811e1a10a26555c2a85ba1d2bad0a0266674fdcc1c3deb573ed1839ffe98294dc979ccc69e42eab187
@@ -2,6 +2,15 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [2.14.0] - 2019-08-06
6
+
7
+ * **New Features**
8
+ * Adds support for rake-aware reads.
9
+ * Adds support for client-server compression.
10
+
11
+ * **Improvements**
12
+ * Adds support for `truncate-namespace` command.
13
+
5
14
  ## [2.13.0] - 2019-07-17
6
15
 
7
16
  * **New Features**
@@ -22,6 +22,7 @@ require "timeout"
22
22
  require 'resolv'
23
23
  require 'msgpack'
24
24
  require 'bcrypt'
25
+ require 'zlib'
25
26
 
26
27
  require 'aerospike/atomic/atomic'
27
28
 
@@ -105,17 +106,21 @@ require 'aerospike/cluster/find_nodes_to_remove'
105
106
  require 'aerospike/cluster/find_node'
106
107
  require 'aerospike/cluster/partition'
107
108
  require 'aerospike/cluster/partition_parser'
109
+ require 'aerospike/cluster/rack_parser'
108
110
  require 'aerospike/node'
109
111
  require 'aerospike/node/generation'
112
+ require 'aerospike/node/rebalance'
110
113
  require 'aerospike/node/refresh/failed'
111
114
  require 'aerospike/node/refresh/friends'
112
115
  require 'aerospike/node/refresh/info'
113
116
  require 'aerospike/node/refresh/partitions'
117
+ require 'aerospike/node/refresh/racks'
114
118
  require 'aerospike/node/refresh/peers'
115
119
  require 'aerospike/node/refresh/reset'
116
120
  require 'aerospike/node/verify/cluster_name'
117
121
  require 'aerospike/node/verify/name'
118
122
  require 'aerospike/node/verify/partition_generation'
123
+ require 'aerospike/node/verify/rebalance_generation'
119
124
  require 'aerospike/node/verify/peers_generation'
120
125
  require 'aerospike/node_validator'
121
126
  require 'aerospike/peer'
@@ -44,6 +44,7 @@ module Aerospike
44
44
  attr_accessor :default_read_policy
45
45
  attr_accessor :default_scan_policy
46
46
  attr_accessor :default_write_policy
47
+ attr_accessor :cluster
47
48
 
48
49
  def initialize(hosts = nil, policy: ClientPolicy.new, connect: true)
49
50
 
@@ -224,8 +225,19 @@ module Aerospike
224
225
  def truncate(namespace, set_name = nil, before_last_update = nil, options = {})
225
226
  policy = create_policy(options, Policy, default_info_policy)
226
227
 
227
- str_cmd = "truncate:namespace=#{namespace}"
228
- str_cmd << ";set=#{set_name}" unless set_name.to_s.strip.empty?
228
+ node = @cluster.random_node
229
+ conn = node.get_connection(policy.timeout)
230
+
231
+ if set_name && !set_name.to_s.strip.empty?
232
+ str_cmd = "truncate:namespace=#{namespace}"
233
+ str_cmd << ";set=#{set_name}" unless set_name.to_s.strip.empty?
234
+ else
235
+ if node.supports_feature(Aerospike::Features::TRUNCATE_NAMESPACE)
236
+ str_cmd = "truncate-namespace:namespace=#{namespace}"
237
+ else
238
+ str_cmd = "truncate:namespace=#{namespace}"
239
+ end
240
+ end
229
241
 
230
242
  if before_last_update
231
243
  lut_nanos = (before_last_update.to_f * 1_000_000_000.0).round
@@ -235,8 +247,7 @@ module Aerospike
235
247
  str_cmd << ";lut=now"
236
248
  end
237
249
 
238
- # Send index command to one node. That node will distribute the command to other nodes.
239
- response = send_info_command(policy, str_cmd).upcase
250
+ response = send_info_command(policy, str_cmd, node).upcase
240
251
  return if response == 'OK'
241
252
  raise Aerospike::Exceptions::Aerospike.new(Aerospike::ResultCode::SERVER_ERROR, "Truncate failed: #{response}")
242
253
  end
@@ -818,9 +829,13 @@ module Aerospike
818
829
  self.default_write_policy = create_policy(policies[:write], WritePolicy)
819
830
  end
820
831
 
821
- def send_info_command(policy, command)
832
+ def send_info_command(policy, command, node = nil)
822
833
  Aerospike.logger.debug { "Sending info command: #{command}" }
823
- _, response = @cluster.request_info(policy, command).first
834
+ if node
835
+ _, response = @cluster.request_node_info(node, policy, command).first
836
+ else
837
+ _, response = @cluster.request_info(policy, command).first
838
+ end
824
839
  response.to_s
825
840
  end
826
841
 
@@ -28,6 +28,7 @@ module Aerospike
28
28
  attr_reader :features, :tls_options
29
29
  attr_reader :cluster_id, :aliases
30
30
  attr_reader :cluster_name
31
+ attr_accessor :rack_aware, :rack_id
31
32
 
32
33
  def initialize(policy, hosts)
33
34
  @cluster_seeds = hosts
@@ -37,6 +38,8 @@ module Aerospike
37
38
  @tend_interval = policy.tend_interval
38
39
  @cluster_name = policy.cluster_name
39
40
  @tls_options = policy.tls
41
+ @rack_aware = policy.rack_aware
42
+ @rack_id = policy.rack_id
40
43
 
41
44
  @replica_index = Atomic.new(0)
42
45
 
@@ -111,6 +114,8 @@ module Aerospike
111
114
  return master_node(partition)
112
115
  when Aerospike::Replica::MASTER_PROLES
113
116
  return master_proles_node(partition)
117
+ when Aerospike::Replica::PREFER_RACK
118
+ return rack_node(partition, seq)
114
119
  when Aerospike::Replica::RANDOM
115
120
  return random_node
116
121
  else
@@ -125,6 +130,8 @@ module Aerospike
125
130
  return master_node(partition)
126
131
  when Aerospike::Replica::MASTER_PROLES
127
132
  return master_proles_node(partition)
133
+ when Aerospike::Replica::PREFER_RACK
134
+ return rack_node(partition, seq)
128
135
  when Aerospike::Replica::SEQUENCE
129
136
  return sequence_node(partition, seq)
130
137
  when Aerospike::Replica::RANDOM
@@ -149,6 +156,38 @@ module Aerospike
149
156
  node
150
157
  end
151
158
 
159
+ # Returns a node on the cluster
160
+ def rack_node(partition, seq)
161
+ partition_map = partitions
162
+ replica_array = partition_map[partition.namespace]
163
+ raise Aerospike::Exceptions::InvalidNamespace("namespace not found in the partition map") if !replica_array
164
+
165
+ replica_array = replica_array.get
166
+
167
+ is_retry = seq.value > -1
168
+
169
+ node = nil
170
+ fallback = nil
171
+ for i in 1..replica_array.length
172
+ idx = (seq.update{|v| v.succ} % replica_array.size).abs
173
+ node = (replica_array[idx].get)[partition.partition_id]
174
+
175
+ next if !node
176
+
177
+ fallback = node
178
+
179
+ # If fallback exists, do not retry on node where command failed,
180
+ # even if fallback is not on the same rack.
181
+ return fallback if is_retry && fallback && i == replica_array.length
182
+
183
+ return node if node && node.active? && node.has_rack(partition.namespace, @rack_id)
184
+ end
185
+
186
+ return fallback if fallback
187
+
188
+ raise Aerospike::Exceptions::InvalidNode
189
+ end
190
+
152
191
  # Returns a node on the cluster for read operations
153
192
  def master_proles_node(partition)
154
193
  partition_map = partitions
@@ -256,6 +295,13 @@ module Aerospike
256
295
  end
257
296
  end
258
297
 
298
+ def request_node_info(node, policy, *commands)
299
+ conn = node.get_connection(policy.timeout)
300
+ Info.request(conn, *commands).tap do
301
+ node.put_connection(conn)
302
+ end
303
+ end
304
+
259
305
  def supports_feature?(feature)
260
306
  @features.get.include?(feature.to_s)
261
307
  end
@@ -352,6 +398,7 @@ module Aerospike
352
398
 
353
399
  nodes.each do |node|
354
400
  node.refresh_partitions(peers) if node.partition_generation.changed?
401
+ node.refresh_racks if node.rebalance_generation.changed?
355
402
  end
356
403
 
357
404
  if peers.generation_changed? || !peers.use_peers?
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2014-2020 Aerospike, Inc.
4
+ #
5
+ # Portions may be licensed to Aerospike, Inc. under one or more contributor
6
+ # license agreements.
7
+ #
8
+ # Licensed under the Apache License, Version 2.0 (the "License"); you may not
9
+ # use this file except in compliance with the License. You may obtain a copy of
10
+ # the License at http:#www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15
+ # License for the specific language governing permissions and limitations under
16
+ # the License.
17
+
18
+ require 'base64'
19
+
20
+ module Aerospike
21
+
22
+ class RackParser #:nodoc:
23
+
24
+ attr_accessor :rebalance_generation, :racks
25
+
26
+ REBALANCE_GENERATION = "rebalance-generation"
27
+ RACK_IDS = "rack-ids"
28
+
29
+ def initialize(node, conn)
30
+ @node = node
31
+ @conn = conn
32
+ @racks = nil
33
+ end
34
+
35
+ def update_racks
36
+ # Use low-level info methods and parse byte array directly for maximum performance.
37
+ # Receive format: rack-ids\t
38
+ # <ns1>:<rack-id>...;
39
+ # <ns2>:<rack-id>...; ...
40
+ info_map = Info.request(@conn, REBALANCE_GENERATION, RACK_IDS)
41
+
42
+ @rebalance_generation = info_map[REBALANCE_GENERATION].to_i
43
+
44
+ info = info_map[RACK_IDS]
45
+ if !info || info.length == 0
46
+ raise Aerospike::Exceptions::Connection.new("#{RACK_IDS} response for node #{@node.name} is empty")
47
+ end
48
+
49
+ @buffer = info
50
+ @length = info.length
51
+ @offset = 0
52
+
53
+ while @offset < @length && @buffer[@offset] != '\n'
54
+ namespace = parse_name
55
+ rack_id = parse_rack_id
56
+
57
+ @racks = {} if !@racks
58
+ @racks[namespace] = rack_id
59
+ end
60
+
61
+ @racks
62
+ end
63
+
64
+ private
65
+
66
+ def parse_name
67
+ beginning = @offset
68
+ while @offset < @length
69
+ break if @buffer[@offset] == ':'
70
+ @offset+=1
71
+ end
72
+
73
+ # Parse namespace.
74
+ namespace = @buffer[beginning...@offset].strip
75
+
76
+ if namespace.length <= 0 || namespace.length >= 32
77
+ response = get_truncated_response
78
+ raise Aerospike::Exceptions::Parse.new(
79
+ "Invalid rack namespace #{namespace}. Response=#{response}"
80
+ )
81
+ end
82
+
83
+ @offset+=1
84
+ namespace
85
+ end
86
+
87
+ def parse_rack_id
88
+ beginning = @offset
89
+ while @offset < @length
90
+ break if @buffer[@offset] == ';'
91
+ @offset+=1
92
+ end
93
+
94
+ # Parse rack_id
95
+ rack_id = @buffer[beginning...@offset].strip.to_i
96
+
97
+ if rack_id < 0
98
+ response = get_truncated_response
99
+ raise Aerospike::Exceptions::Parse.new(
100
+ "Invalid rack_id #{rack_id}. Response=#{response}"
101
+ )
102
+ end
103
+
104
+ @offset+=1
105
+ rack_id
106
+ end
107
+
108
+ def get_truncated_response
109
+ max = @length
110
+ @length = max if @length > 200
111
+ @buffer[0...max]
112
+ end
113
+
114
+
115
+ end # class
116
+
117
+ end # module
@@ -75,6 +75,7 @@ module Aerospike
75
75
  end
76
76
 
77
77
  end_cmd
78
+ mark_compressed(@policy)
78
79
  end
79
80
 
80
81
  # Parse all results in the batch. Add records to shared list.
@@ -98,6 +98,7 @@ module Aerospike
98
98
  end
99
99
  end
100
100
  end_cmd
101
+ mark_compressed(@policy)
101
102
  end
102
103
 
103
104
  # Parse all results in the batch. Add records to shared list.
@@ -16,6 +16,7 @@
16
16
  # the License.
17
17
 
18
18
  require 'time'
19
+ require 'zlib'
19
20
 
20
21
  require 'msgpack'
21
22
  require 'aerospike/result_code'
@@ -42,6 +43,9 @@ module Aerospike
42
43
  # Involve all replicas in read operation.
43
44
  INFO1_CONSISTENCY_ALL = Integer(1 << 6)
44
45
 
46
+ # Tell server to compress it's response.
47
+ INFO1_COMPRESS_RESPONSE = (1 << 7)
48
+
45
49
  # Create or update record
46
50
  INFO2_WRITE = Integer(1 << 0)
47
51
  # Fling a record into the belly of Moloch.
@@ -72,8 +76,10 @@ module Aerospike
72
76
  OPERATION_HEADER_SIZE = 8
73
77
  MSG_REMAINING_HEADER_SIZE = 22
74
78
  DIGEST_SIZE = 20
79
+ COMPRESS_THRESHOLD = 128
75
80
  CL_MSG_VERSION = 2
76
81
  AS_MSG_TYPE = 3
82
+ AS_MSG_TYPE_COMPRESSED = 4
77
83
 
78
84
  class Command #:nodoc:
79
85
 
@@ -83,6 +89,8 @@ module Aerospike
83
89
 
84
90
  @node = node
85
91
 
92
+ @compress = false
93
+
86
94
  # will add before use
87
95
  @sequence = Atomic.new(-1)
88
96
 
@@ -118,6 +126,7 @@ module Aerospike
118
126
  end
119
127
 
120
128
  end_cmd
129
+ mark_compressed(policy)
121
130
  end
122
131
 
123
132
  # Writes the command for delete operations
@@ -288,6 +297,7 @@ module Aerospike
288
297
  write_operation_for_bin(nil, Aerospike::Operation::READ) if read_header
289
298
 
290
299
  end_cmd
300
+ mark_compressed(policy)
291
301
  end
292
302
 
293
303
  def set_udf(policy, key, package_name, function_name, args)
@@ -310,6 +320,7 @@ module Aerospike
310
320
  write_field_bytes(arg_bytes, Aerospike::FieldType::UDF_ARGLIST)
311
321
 
312
322
  end_cmd
323
+ mark_compressed(policy)
313
324
  end
314
325
 
315
326
  def set_scan(policy, namespace, set_name, bin_names)
@@ -579,6 +590,7 @@ module Aerospike
579
590
  # Generic header write.
580
591
  def write_header(policy, read_attr, write_attr, field_count, operation_count)
581
592
  read_attr |= INFO1_CONSISTENCY_ALL if policy.consistency_level == Aerospike::ConsistencyLevel::CONSISTENCY_ALL
593
+ read_attr |= INFO1_COMPRESS_RESPONSE if policy.use_compression
582
594
 
583
595
  # Write all header data except total size which must be written last.
584
596
  @data_buffer.write_byte(MSG_REMAINING_HEADER_SIZE, 8) # Message heade.length.
@@ -628,6 +640,7 @@ module Aerospike
628
640
  info_attr |= INFO3_COMMIT_MASTER if policy.commit_level == Aerospike::CommitLevel::COMMIT_MASTER
629
641
  read_attr |= INFO1_CONSISTENCY_ALL if policy.consistency_level == Aerospike::ConsistencyLevel::CONSISTENCY_ALL
630
642
  write_attr |= INFO2_DURABLE_DELETE if policy.durable_delete
643
+ read_attr |= INFO1_COMPRESS_RESPONSE if policy.use_compression
631
644
 
632
645
  # Write all header data except total size which must be written last.
633
646
  @data_buffer.write_byte(MSG_REMAINING_HEADER_SIZE, 8) # Message heade.length.
@@ -810,6 +823,48 @@ module Aerospike
810
823
  @data_buffer.write_int64(size, 0)
811
824
  end
812
825
 
826
+ def use_compression?
827
+ @compress
828
+ end
829
+
830
+ def compress_buffer
831
+ if @data_offset > COMPRESS_THRESHOLD
832
+ compressed = Zlib::deflate(@data_buffer.buf, Zlib::DEFAULT_COMPRESSION)
833
+
834
+ # write original size as header
835
+ proto_s = "%08d" % 0
836
+ proto_s[0, 8] = [@data_offset].pack('q>')
837
+ compressed.prepend(proto_s)
838
+
839
+ # write proto
840
+ proto = (compressed.size+8) | Integer(CL_MSG_VERSION << 56) | Integer(AS_MSG_TYPE << 48)
841
+ proto_s = "%08d" % 0
842
+ proto_s[0, 8] = [proto].pack('q>')
843
+ compressed.prepend(proto_s)
844
+
845
+ @data_buffer = Buffer.new(-1, compressed)
846
+ @data_offset = @data_buffer.size
847
+ end
848
+ end
849
+
850
+ # isCompressed returns the length of the compressed buffer.
851
+ # If the buffer is not compressed, the result will be -1
852
+ def compressed_size
853
+ # A number of these are commented out because we just don't care enough to read
854
+ # that section of the header. If we do care, uncomment and check!
855
+ proto = @data_buffer.read_int64(0)
856
+ size = proto & 0xFFFFFFFFFFFF
857
+ msg_type = (proto >> 48) & 0xFF
858
+
859
+ return nil if msg_type != AS_MSG_TYPE_COMPRESSED
860
+
861
+ size
862
+ end
863
+
864
+ def mark_compressed(policy)
865
+ @compress = policy.use_compression
866
+ end
867
+
813
868
  end # class
814
869
 
815
870
  end # module
@@ -29,6 +29,9 @@ module Aerospike
29
29
  @valid = true
30
30
  @mutex = Mutex.new
31
31
 
32
+ @compressed_data_buffer = nil
33
+ @compressed_data_offset = nil
34
+
32
35
  self
33
36
  end
34
37
 
@@ -42,12 +45,41 @@ module Aerospike
42
45
  status = true
43
46
 
44
47
  while status
48
+ @data_offset = 0
49
+ @compressed_data_buffer = nil
50
+
45
51
  # Read header.
46
52
  read_bytes(8)
47
53
 
48
54
  size = @data_buffer.read_int64(0)
49
55
  receive_size = size & 0xFFFFFFFFFFFF
50
56
 
57
+ # inflate if compressed
58
+ compressed_sz = compressed_size
59
+ if compressed_sz
60
+ begin
61
+ # read compressed msg header
62
+ @conn.read(@data_buffer, 8)
63
+
64
+ # read compressed message
65
+ @conn.read(@data_buffer, compressed_sz - 8)
66
+
67
+ # inflate the results
68
+ # TODO: reuse the current buffer
69
+ uncompressed = Zlib::inflate(@data_buffer.buf)
70
+ receive_size = uncompressed.size - 8
71
+
72
+ @compressed_data_buffer = Buffer.new(-1, uncompressed)
73
+ @compressed_data_offset = 0
74
+
75
+ # waste the first 8 header bytes
76
+ @compressed_data_buffer.eat!(8)
77
+ rescue => e
78
+ Aerospike.logger.error("parse result error: #{e}")
79
+ raise e
80
+ end
81
+ end
82
+
51
83
  if receive_size > 0
52
84
  status = parse_group(receive_size)
53
85
  else
@@ -157,7 +189,13 @@ module Aerospike
157
189
  @data_buffer = Buffer.new(length)
158
190
  end
159
191
 
160
- @conn.read(@data_buffer, length)
192
+ if compressed?
193
+ @data_buffer.write_binary(@compressed_data_buffer.buf[@compressed_data_offset...@compressed_data_offset+length], 0)
194
+ @compressed_data_offset += length
195
+ else
196
+ @conn.read(@data_buffer, length)
197
+ end
198
+
161
199
  @data_offset += length
162
200
  end
163
201
 
@@ -176,6 +214,11 @@ module Aerospike
176
214
  res
177
215
  end
178
216
 
217
+ def compressed?
218
+ @compressed_data_buffer != nil
219
+ end
220
+
221
+
179
222
  end # class
180
223
 
181
224
  end # module
@@ -15,6 +15,8 @@
15
15
  # License for the specific language governing permissions and limitations under
16
16
  # the License.
17
17
 
18
+ require 'zlib'
19
+
18
20
  require 'aerospike/record'
19
21
 
20
22
  require 'aerospike/command/single_command'
@@ -50,12 +52,40 @@ module Aerospike
50
52
  def parse_result
51
53
  # Read header.
52
54
  begin
53
- @conn.read(@data_buffer, MSG_TOTAL_HEADER_SIZE)
55
+ @conn.read(@data_buffer, 8)
54
56
  rescue => e
55
57
  Aerospike.logger.error("parse result error: #{e}")
56
58
  raise e
57
59
  end
58
60
 
61
+ # inflate if compressed
62
+ compressed_sz = compressed_size
63
+ if compressed_sz
64
+ begin
65
+ # waste 8 size bytes
66
+ @conn.read(@data_buffer, 8)
67
+
68
+ # read compressed message
69
+ @conn.read(@data_buffer, compressed_sz - 8)
70
+
71
+ # inflate the results
72
+ # TODO: reuse the current buffer
73
+ uncompressed = Zlib::inflate(@data_buffer.buf)
74
+
75
+ @data_buffer = Buffer.new(-1, uncompressed)
76
+ rescue => e
77
+ Aerospike.logger.error("parse result error: #{e}")
78
+ raise e
79
+ end
80
+ else
81
+ begin
82
+ bytes_read = @conn.read(@data_buffer, MSG_TOTAL_HEADER_SIZE - 8, 8)
83
+ rescue => e
84
+ Aerospike.logger.error("parse result error: #{e}")
85
+ raise e
86
+ end
87
+ end
88
+
59
89
  # A number of these are commented out because we just don't care enough to read
60
90
  # that section of the header. If we do care, uncomment and check!
61
91
  sz = @data_buffer.read_int64(0)
@@ -68,7 +98,9 @@ module Aerospike
68
98
  receive_size = (sz & 0xFFFFFFFFFFFF) - header_length
69
99
 
70
100
  # Read remaining message bytes.
71
- if receive_size > 0
101
+ if compressed_sz
102
+ @data_buffer.eat!(MSG_TOTAL_HEADER_SIZE)
103
+ elsif receive_size > 0
72
104
  size_buffer_sz(receive_size)
73
105
 
74
106
  begin
@@ -40,7 +40,40 @@ module Aerospike
40
40
 
41
41
  def parse_result
42
42
  # Read header.
43
- @conn.read(@data_buffer, MSG_TOTAL_HEADER_SIZE)
43
+ begin
44
+ @conn.read(@data_buffer, 8)
45
+ rescue => e
46
+ Aerospike.logger.error("parse result error: #{e}")
47
+ raise e
48
+ end
49
+
50
+ # inflate if compressed
51
+ compressed_sz = compressed_size
52
+ if compressed_sz
53
+ begin
54
+ #waste 8 size bytes
55
+ @conn.read(@data_buffer, 8)
56
+
57
+ # read compressed message
58
+ @conn.read(@data_buffer, sz - 8)
59
+
60
+ # inflate the results
61
+ # TODO: reuse the current buffer
62
+ uncompressed = Zlib::inflate(@data_buffer.buf)
63
+
64
+ @data_buffer = Buffer.new(-1, uncompressed)
65
+ rescue => e
66
+ Aerospike.logger.error("parse result error: #{e}")
67
+ raise e
68
+ end
69
+ else
70
+ begin
71
+ bytes_read = @conn.read(@data_buffer, MSG_TOTAL_HEADER_SIZE - 8, 8)
72
+ rescue => e
73
+ Aerospike.logger.error("parse result error: #{e}")
74
+ raise e
75
+ end
76
+ end
44
77
 
45
78
  result_code = @data_buffer.read(13).ord & 0xFF
46
79
 
@@ -40,5 +40,7 @@ module Aerospike
40
40
  # Server supports the new "peers" protocol for automatic node discovery
41
41
  PEERS = :peers
42
42
 
43
+ # Server supports the "truncate-namespace" command
44
+ TRUNCATE_NAMESPACE = :"truncate-namespace"
43
45
  end
44
46
  end
@@ -22,7 +22,7 @@ require 'aerospike/atomic/atomic'
22
22
  module Aerospike
23
23
  class Node
24
24
 
25
- attr_reader :reference_count, :responded, :name, :features, :cluster_name, :partition_generation, :peers_generation, :failures, :cluster, :peers_count, :host
25
+ attr_reader :reference_count, :responded, :name, :features, :cluster_name, :partition_generation, :rebalance_generation, :peers_generation, :failures, :cluster, :peers_count, :host
26
26
 
27
27
  PARTITIONS = 4096
28
28
  FULL_HEALTH = 100
@@ -46,16 +46,29 @@ module Aerospike
46
46
  @peers_count = Atomic.new(0)
47
47
  @peers_generation = ::Aerospike::Node::Generation.new
48
48
  @partition_generation = ::Aerospike::Node::Generation.new
49
+ @rebalance_generation = ::Aerospike::Node::Rebalance.new
49
50
  @reference_count = Atomic.new(0)
50
51
  @responded = Atomic.new(false)
51
52
  @active = Atomic.new(true)
52
53
  @failures = Atomic.new(0)
53
54
 
54
55
  @replica_index = Atomic.new(0)
56
+ @racks = Atomic.new(nil)
55
57
 
56
58
  @connections = ::Aerospike::ConnectionPool.new(cluster, host)
57
59
  end
58
60
 
61
+ def update_racks(parser)
62
+ new_racks = parser.update_racks
63
+ @racks.value = new_racks if new_racks
64
+ end
65
+
66
+ def has_rack(ns, rack_id)
67
+ racks = @racks.value
68
+ return false if !racks
69
+ racks[ns] == rack_id
70
+ end
71
+
59
72
  # Get a connection to the node. If no cached connection is not available,
60
73
  # a new connection will be created
61
74
  def get_connection(timeout)
@@ -199,6 +212,10 @@ module Aerospike
199
212
  Node::Refresh::Partitions.(self, peers)
200
213
  end
201
214
 
215
+ def refresh_racks()
216
+ Node::Refresh::Racks.(self)
217
+ end
218
+
202
219
  def refresh_peers(peers)
203
220
  Node::Refresh::Peers.(self, peers)
204
221
  end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2018 Aerospike, Inc.
4
+ #
5
+ # Portions may be licensed to Aerospike, Inc. under one or more contributor
6
+ # license agreements.
7
+ #
8
+ # Licensed under the Apache License, Version 2.0 (the "License"); you may not
9
+ # use this file except in compliance with the License. You may obtain a copy of
10
+ # the License at
11
+ #
12
+ # http://www.apache.org/licenses/LICENSE-2.0
13
+ #
14
+ # Unless required by applicable law or agreed to in writing, software
15
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
17
+ # License for the specific language governing permissions and limitations under
18
+ # the License.
19
+
20
+ module Aerospike
21
+ class Node
22
+ # generic class for representing changes in eg. peer and partition generation
23
+ class Rebalance
24
+ attr_reader :generation
25
+
26
+ def initialize(generation = -1)
27
+ @generation = ::Aerospike::Atomic.new(generation)
28
+ @changed = ::Aerospike::Atomic.new(false)
29
+ end
30
+
31
+ def changed?
32
+ @changed.value == true
33
+ end
34
+
35
+ def eql?(generation)
36
+ @generation.value == generation
37
+ end
38
+
39
+ def reset_changed!
40
+ @changed.value = false
41
+ end
42
+
43
+ def update(new_generation)
44
+ return if @generation.value == new_generation
45
+ @generation.value = new_generation
46
+ @changed.value = true
47
+ end
48
+ end
49
+ end
50
+ end
@@ -24,14 +24,17 @@ module Aerospike
24
24
  CMDS_BASE = %w[node partition-generation cluster-name].freeze
25
25
  CMDS_PEERS = (CMDS_BASE + ['peers-generation']).freeze
26
26
  CMDS_SERVICES = (CMDS_BASE + ['services']).freeze
27
+ CMDS_REBALANCE = (CMDS_PEERS + ['rebalance-generation']).freeze
27
28
 
28
29
  class << self
29
30
  def call(node, peers)
30
31
  conn = node.tend_connection
31
32
  if peers.use_peers?
32
- info_map = ::Aerospike::Info.request(conn, *CMDS_PEERS)
33
+ cmds = node.cluster.rack_aware ? CMDS_REBALANCE : CMDS_PEERS
34
+ info_map = ::Aerospike::Info.request(conn, *cmds)
33
35
  Verify::PeersGeneration.(node, info_map, peers)
34
36
  Verify::PartitionGeneration.(node, info_map)
37
+ Verify::RebalanceGeneration.(node, info_map) if node.cluster.rack_aware
35
38
  Verify::Name.(node, info_map)
36
39
  Verify::ClusterName.(node, info_map)
37
40
  else
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2018-2020 Aerospike, Inc.
4
+ #
5
+ # Portions may be licensed to Aerospike, Inc. under one or more contributor
6
+ # license agreements.
7
+ #
8
+ # Licensed under the Apache License, Version 2.0 (the "License"); you may not
9
+ # use this file except in compliance with the License. You may obtain a copy of
10
+ # the License at
11
+ #
12
+ # http://www.apache.org/licenses/LICENSE-2.0
13
+ #
14
+ # Unless required by applicable law or agreed to in writing, software
15
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
17
+ # License for the specific language governing permissions and limitations under
18
+ # the License.
19
+
20
+ module Aerospike
21
+ class Node
22
+ module Refresh
23
+ module Racks
24
+ class << self
25
+ def call(node)
26
+ return unless should_refresh?(node)
27
+
28
+ Aerospike.logger.info("Updating racks for node #{node.name}")
29
+ conn = node.tend_connection
30
+ parser = RackParser.new(node, conn)
31
+ node.update_racks(parser)
32
+ rescue ::Aerospike::Exceptions::Aerospike => e
33
+ conn.close
34
+ Refresh::Failed.(node, e)
35
+ end
36
+
37
+ # Do not refresh racks when node connection has already failed
38
+ # during this cluster tend iteration.
39
+ def should_refresh?(node)
40
+ return false if node.failed? || !node.active?
41
+ true
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -28,6 +28,7 @@ module Aerospike
28
28
  node.reset_reference_count!
29
29
  node.reset_responded!
30
30
  node.partition_generation.reset_changed!
31
+ node.rebalance_generation.reset_changed!
31
32
  end
32
33
  end
33
34
  end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2018 Aerospike, Inc.
4
+ #
5
+ # Portions may be licensed to Aerospike, Inc. under one or more contributor
6
+ # license agreements.
7
+ #
8
+ # Licensed under the Apache License, Version 2.0 (the "License"); you may not
9
+ # use this file except in compliance with the License. You may obtain a copy of
10
+ # the License at
11
+ #
12
+ # http://www.apache.org/licenses/LICENSE-2.0
13
+ #
14
+ # Unless required by applicable law or agreed to in writing, software
15
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
17
+ # License for the specific language governing permissions and limitations under
18
+ # the License.
19
+
20
+ module Aerospike
21
+ class Node
22
+ module Verify
23
+ # Fetch and set rebalance generation. If racks needs to be refreshed
24
+ # this will be indicated in node.rebalance_changed
25
+ module RebalanceGeneration
26
+ class << self
27
+ def call(node, info_map)
28
+ gen_string = info_map.fetch('rebalance-generation', nil)
29
+
30
+ raise Aerospike::Exceptions::Parse.new('rebalance-generation is empty') if gen_string.to_s.empty?
31
+
32
+ generation = gen_string.to_i
33
+
34
+ node.rebalance_generation.update(generation)
35
+
36
+ return unless node.rebalance_generation.changed?
37
+ Aerospike.logger.info("Node #{node.name} rebalance generation #{generation} changed")
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -27,6 +27,7 @@ module Aerospike
27
27
  attr_accessor :cluster_name
28
28
  attr_accessor :tls
29
29
  attr_accessor :policies
30
+ attr_accessor :rack_aware, :rack_id
30
31
 
31
32
  def initialize(opt={})
32
33
  # Initial host connection timeout in seconds. The timeout when opening a connection
@@ -56,6 +57,20 @@ module Aerospike
56
57
 
57
58
  # Default Policies
58
59
  @policies = opt.fetch(:policies) { Hash.new }
60
+
61
+ # Track server rack data. This field is useful when directing read commands to the server node
62
+ # that contains the key and exists on the same rack as the client. This serves to lower cloud
63
+ # provider costs when nodes are distributed across different racks/data centers.
64
+ #
65
+ # ClientPolicy#rack_id, Replica#PREFER_RACK and server rack
66
+ # configuration must also be set to enable this functionality.
67
+ @rack_aware = opt[:rack_aware] || false
68
+
69
+ # Rack where this client instance resides.
70
+ #
71
+ # ClientPolicy#rack_aware, Replica#PREFER_RACK and server rack
72
+ # configuration must also be set to enable this functionality.
73
+ @rack_id = opt[:rack_id] || 0
59
74
  end
60
75
 
61
76
  def requires_authentication
@@ -24,7 +24,7 @@ module Aerospike
24
24
  class Policy
25
25
 
26
26
  attr_accessor :priority, :timeout, :max_retries, :sleep_between_retries, :consistency_level,
27
- :predexp, :fail_on_filtered_out, :replica
27
+ :predexp, :fail_on_filtered_out, :replica, :use_compression
28
28
 
29
29
  def initialize(opt={})
30
30
  # Container object for transaction policy attributes used in all database
@@ -89,9 +89,17 @@ module Aerospike
89
89
  # to the node containing the key's master partition.
90
90
  #
91
91
  # Default to sending read commands to the node containing the key's master partition.
92
- @replica = opt[:replica] || Aerospike::Replica::MASTER;
92
+ @replica = opt[:replica] || Aerospike::Replica::MASTER
93
93
 
94
- # Transaction timeout.
94
+ # Use zlib compression on write or batch read commands when the command buffer size is greater
95
+ # than 128 bytes. In addition, tell the server to compress its response on read commands.
96
+ # The server response compression threshold is also 128 bytes.
97
+ #
98
+ # This option will increase cpu and memory usage (for extra compressed buffers), but
99
+ # decrease the size of data sent over the network.
100
+ @use_compression = opt[:use_compression] || false
101
+
102
+ # Transaction timeout.
95
103
  # This timeout is used to set the socket timeout and is also sent to the
96
104
  # server along with the transaction in the wire protocol.
97
105
  # Default to no timeout (0).
@@ -28,6 +28,13 @@ module Aerospike
28
28
  # Policy#retryOnTimeout is true, try nodes containing prole partition.
29
29
  SEQUENCE = 2
30
30
 
31
+ # Try node on the same rack as the client first. If there are no nodes on the
32
+ # same rack, use SEQUENCE instead.
33
+ #
34
+ # ClientPolicy#rack_aware}, ClientPolicy#rack_id, and server rack
35
+ # configuration must also be set to enable this functionality.
36
+ PREFER_RACK = 3
37
+
31
38
  # Distribute reads across all nodes in cluster in round-robin fashion.
32
39
  # This option is useful when the replication factor equals the number
33
40
  # of nodes in the cluster and the overhead of requesting proles is not desired.
@@ -25,13 +25,14 @@ module Aerospike
25
25
  @timeout = nil
26
26
  end
27
27
 
28
- def read(buffer, length)
28
+ def read(buffer, length, offset = 0)
29
29
  bytes_read = 0
30
30
  until bytes_read >= length
31
31
  result = read_from_socket(length - bytes_read)
32
- buffer.write_binary(result, bytes_read)
32
+ buffer.write_binary(result, offset + bytes_read)
33
33
  bytes_read += result.bytesize
34
34
  end
35
+ bytes_read
35
36
  end
36
37
 
37
38
  def read_from_socket(length)
@@ -42,9 +42,9 @@ module Aerospike
42
42
  DEFAULT_BUFFER_SIZE = 16 * 1024
43
43
  MAX_BUFFER_SIZE = 10 * 1024 * 1024
44
44
 
45
- def initialize(size=DEFAULT_BUFFER_SIZE)
46
- @buf = "%0#{size}d" % 0
47
-
45
+ def initialize(size=DEFAULT_BUFFER_SIZE, buf = nil)
46
+ @buf = (buf ? buf : ("%0#{size}d" % 0))
47
+ @buf.force_encoding('binary')
48
48
  @slice_end = @buf.bytesize
49
49
  end
50
50
 
@@ -61,6 +61,10 @@ module Aerospike
61
61
  end
62
62
  alias_method :length, :size
63
63
 
64
+ def eat!(n)
65
+ @buf.replace(@buf[n..-1])
66
+ end
67
+
64
68
  def resize(length)
65
69
  if @buf.bytesize < length
66
70
  @buf.concat("%0#{length - @buf.bytesize}d" % 0)
@@ -156,6 +160,12 @@ module Aerospike
156
160
  @buf[0..@slice_end-1]
157
161
  end
158
162
 
163
+ def reset
164
+ for i in 0..@buf.size-1
165
+ @buf[i] = ' '
166
+ end
167
+ end
168
+
159
169
  def dump(from=nil, to=nil)
160
170
  from ||= 0
161
171
  to ||= @slice_end - 1
@@ -1,4 +1,4 @@
1
1
  # encoding: utf-8
2
2
  module Aerospike
3
- VERSION = "2.13.0"
3
+ VERSION = "2.14.0"
4
4
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aerospike
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.13.0
4
+ version: 2.14.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Khosrow Afroozeh
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2020-07-17 00:00:00.000000000 Z
12
+ date: 2020-08-06 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: msgpack
@@ -74,6 +74,7 @@ files:
74
74
  - lib/aerospike/cluster/find_nodes_to_remove.rb
75
75
  - lib/aerospike/cluster/partition.rb
76
76
  - lib/aerospike/cluster/partition_parser.rb
77
+ - lib/aerospike/cluster/rack_parser.rb
77
78
  - lib/aerospike/command/admin_command.rb
78
79
  - lib/aerospike/command/batch_direct_command.rb
79
80
  - lib/aerospike/command/batch_direct_exists_command.rb
@@ -108,16 +109,19 @@ files:
108
109
  - lib/aerospike/loggable.rb
109
110
  - lib/aerospike/node.rb
110
111
  - lib/aerospike/node/generation.rb
112
+ - lib/aerospike/node/rebalance.rb
111
113
  - lib/aerospike/node/refresh/failed.rb
112
114
  - lib/aerospike/node/refresh/friends.rb
113
115
  - lib/aerospike/node/refresh/info.rb
114
116
  - lib/aerospike/node/refresh/partitions.rb
115
117
  - lib/aerospike/node/refresh/peers.rb
118
+ - lib/aerospike/node/refresh/racks.rb
116
119
  - lib/aerospike/node/refresh/reset.rb
117
120
  - lib/aerospike/node/verify/cluster_name.rb
118
121
  - lib/aerospike/node/verify/name.rb
119
122
  - lib/aerospike/node/verify/partition_generation.rb
120
123
  - lib/aerospike/node/verify/peers_generation.rb
124
+ - lib/aerospike/node/verify/rebalance_generation.rb
121
125
  - lib/aerospike/node_validator.rb
122
126
  - lib/aerospike/operation.rb
123
127
  - lib/aerospike/peer.rb