aerospike 2.13.0 → 2.14.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 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