aerospike 2.6.0 → 2.7.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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +9 -0
  3. data/README.md +3 -1
  4. data/lib/aerospike.rb +9 -5
  5. data/lib/aerospike/client.rb +101 -83
  6. data/lib/aerospike/cluster.rb +11 -50
  7. data/lib/aerospike/cluster/create_connection.rb +1 -1
  8. data/lib/aerospike/cluster/find_nodes_to_remove.rb +66 -0
  9. data/lib/aerospike/cluster/partition.rb +5 -2
  10. data/lib/aerospike/command/batch_direct_command.rb +104 -0
  11. data/lib/aerospike/command/batch_direct_exists_command.rb +51 -0
  12. data/lib/aerospike/command/batch_direct_node.rb +40 -0
  13. data/lib/aerospike/command/batch_index_command.rb +119 -0
  14. data/lib/aerospike/command/batch_index_exists_command.rb +45 -0
  15. data/lib/aerospike/command/batch_index_node.rb +52 -0
  16. data/lib/aerospike/command/batch_item.rb +18 -47
  17. data/lib/aerospike/command/command.rb +6 -65
  18. data/lib/aerospike/command/field_type.rb +13 -10
  19. data/lib/aerospike/command/{batch_command.rb → multi_command.rb} +29 -9
  20. data/lib/aerospike/command/read_command.rb +4 -2
  21. data/lib/aerospike/command/single_command.rb +6 -9
  22. data/lib/aerospike/connection/create.rb +3 -3
  23. data/lib/aerospike/host/parse.rb +28 -2
  24. data/lib/aerospike/node.rb +6 -2
  25. data/lib/aerospike/node/refresh/friends.rb +1 -1
  26. data/lib/aerospike/node/refresh/peers.rb +1 -1
  27. data/lib/aerospike/node_validator.rb +3 -3
  28. data/lib/aerospike/peers.rb +4 -0
  29. data/lib/aerospike/peers/parse.rb +26 -6
  30. data/lib/aerospike/policy/batch_policy.rb +25 -15
  31. data/lib/aerospike/policy/client_policy.rb +2 -2
  32. data/lib/aerospike/policy/query_policy.rb +25 -12
  33. data/lib/aerospike/policy/scan_policy.rb +39 -16
  34. data/lib/aerospike/query/stream_command.rb +6 -5
  35. data/lib/aerospike/record.rb +4 -3
  36. data/lib/aerospike/socket/ssl.rb +13 -13
  37. data/lib/aerospike/socket/tcp.rb +8 -1
  38. data/lib/aerospike/utils/string_parser.rb +7 -3
  39. data/lib/aerospike/version.rb +1 -1
  40. metadata +11 -7
  41. data/lib/aerospike/command/batch_command_exists.rb +0 -93
  42. data/lib/aerospike/command/batch_command_get.rb +0 -84
  43. data/lib/aerospike/command/batch_node.rb +0 -82
@@ -25,7 +25,7 @@ require 'aerospike/atomic/atomic'
25
25
  module Aerospike
26
26
  class Cluster
27
27
  attr_reader :connection_timeout, :connection_queue_size, :user, :password
28
- attr_reader :features, :ssl_options
28
+ attr_reader :features, :tls_options
29
29
  attr_reader :cluster_id, :aliases
30
30
  attr_reader :cluster_name
31
31
 
@@ -36,7 +36,7 @@ module Aerospike
36
36
  @connection_timeout = policy.timeout
37
37
  @tend_interval = policy.tend_interval
38
38
  @cluster_name = policy.cluster_name
39
- @ssl_options = policy.ssl_options
39
+ @tls_options = policy.tls
40
40
 
41
41
  @aliases = {}
42
42
  @cluster_nodes = []
@@ -75,7 +75,7 @@ module Aerospike
75
75
  end
76
76
 
77
77
  def tls_enabled?
78
- !ssl_options.nil? && ssl_options[:enable] != false
78
+ !tls_options.nil? && tls_options[:enable] != false
79
79
  end
80
80
 
81
81
  def initialize_tls_host_names(hosts)
@@ -102,7 +102,9 @@ module Aerospike
102
102
  (node_array.length > 0) && !@closed.value
103
103
  end
104
104
 
105
- def get_node(partition)
105
+ def get_node_for_key(key)
106
+ partition = Partition.new_by_key(key)
107
+
106
108
  # Must copy hashmap reference for copy on write semantics to work.
107
109
  nmap = partitions
108
110
  if node_array = nmap[partition.namespace]
@@ -268,6 +270,8 @@ module Aerospike
268
270
  if peers.generation_changed?
269
271
  # Refresh peers for all nodes that responded the first time even if only
270
272
  # one node's peers changed.
273
+ peers.reset_refresh_count!
274
+
271
275
  nodes.each do |node|
272
276
  node.refresh_peers(peers)
273
277
  end
@@ -371,7 +375,7 @@ module Aerospike
371
375
 
372
376
  seed_array.each do |seed|
373
377
  begin
374
- seed_node_validator = NodeValidator.new(self, seed, @connection_timeout, @cluster_name, ssl_options)
378
+ seed_node_validator = NodeValidator.new(self, seed, @connection_timeout, @cluster_name, tls_options)
375
379
  rescue => e
376
380
  Aerospike.logger.error("Seed #{seed} failed: #{e}\n#{e.backtrace.join("\n")}")
377
381
  next
@@ -384,7 +388,7 @@ module Aerospike
384
388
  nv = seed_node_validator
385
389
  else
386
390
  begin
387
- nv = NodeValidator.new(self, aliass, @connection_timeout, @cluster_name, ssl_options)
391
+ nv = NodeValidator.new(self, aliass, @connection_timeout, @cluster_name, tls_options)
388
392
  rescue => e
389
393
  Aerospike.logger.error("Seed #{seed} failed: #{e}")
390
394
  next
@@ -427,50 +431,7 @@ module Aerospike
427
431
  end
428
432
 
429
433
  def find_nodes_to_remove(refresh_count)
430
- node_list = nodes
431
-
432
- remove_list = []
433
-
434
- node_list.each do |node|
435
- if !node.active?
436
- # Inactive nodes must be removed.
437
- remove_list << node
438
- next
439
- end
440
-
441
- case node_list.length
442
- when 1
443
- # Single node clusters rely solely on node health.
444
- remove_list << node if node.unhealthy?
445
-
446
- when 2
447
- # Two node clusters require at least one successful refresh before removing.
448
- if refresh_count == 2 && node.reference_count.value == 0 && !node.responded?
449
- # Node is not referenced nor did it respond.
450
- remove_list << node
451
- end
452
-
453
- else
454
- # Multi-node clusters require two successful node refreshes before removing.
455
- if refresh_count >= 2 && node.reference_count.value == 0
456
- # Node is not referenced by other nodes.
457
- # Check if node responded to info request.
458
- if node.responded?
459
- # Node is alive, but not referenced by other nodes. Check if mapped.
460
- unless find_node_in_partition_map(node)
461
- # Node doesn't have any partitions mapped to it.
462
- # There is not point in keeping it in the cluster.
463
- remove_list << node
464
- end
465
- else
466
- # Node not responding. Remove it.
467
- remove_list << node
468
- end
469
- end
470
- end
471
- end
472
-
473
- remove_list
434
+ FindNodesToRemove.(self, refresh_count)
474
435
  end
475
436
 
476
437
  def find_node_in_partition_map(filter)
@@ -28,7 +28,7 @@ module Aerospike
28
28
  host.port,
29
29
  tls_name: host.tls_name,
30
30
  timeout: cluster.connection_timeout,
31
- ssl_options: cluster.ssl_options
31
+ tls_options: cluster.tls_options
32
32
  ).tap do |conn|
33
33
  if cluster.credentials_given?
34
34
  # Authenticate will raise and close connection if invalid credentials
@@ -0,0 +1,66 @@
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 Cluster
22
+ # Calculates which nodes that should be removed from the cluster
23
+ module FindNodesToRemove
24
+ class << self
25
+ def call(cluster, refresh_count)
26
+ node_list = cluster.nodes
27
+
28
+ remove_list = []
29
+
30
+ node_list.each do |node|
31
+ unless node.active?
32
+ # Inactive nodes must be removed.
33
+ remove_list << node
34
+ next
35
+ end
36
+
37
+ if refresh_count.zero? && node.failed?(5) # 5 or more failures counts as failed
38
+ # All node info requests failed and this node had 5 consecutive failures.
39
+ # Remove node. If no nodes are left, seeds will be tried in next cluster
40
+ # tend iteration.
41
+ remove_list << node
42
+ next
43
+ end
44
+
45
+ if node_list.size > 1 && refresh_count >= 1 && !node.referenced?
46
+ # Node is not referenced by other nodes.
47
+ # Check if node responded to info request.
48
+ if node.failed?
49
+ remove_list << node
50
+ else
51
+ # Node is alive, but not referenced by other nodes. Check if mapped.
52
+ unless cluster.find_node_in_partition_map(node)
53
+ # Node doesn't have any partitions mapped to it.
54
+ # There is no point in keeping it in the cluster.
55
+ remove_list << node
56
+ end
57
+ end
58
+ end
59
+ end
60
+
61
+ remove_list
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -1,4 +1,5 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
+
2
3
  # Copyright 2014-2017 Aerospike, Inc.
3
4
  #
4
5
  # Portions may be licensed to Aerospike, Inc. under one or more contributor
@@ -19,6 +20,8 @@ module Aerospike
19
20
  private
20
21
 
21
22
  class Partition # :nodoc:
23
+ UNPACK_FORMAT = 'l<'
24
+
22
25
  attr_reader :namespace, :partition_id
23
26
 
24
27
  def initialize(namespace, partition_id)
@@ -31,7 +34,7 @@ module Aerospike
31
34
  def self.new_by_key(key)
32
35
  Partition.new(
33
36
  key.namespace,
34
- (key.digest[0..3].unpack('l<')[0] & 0xFFFF) % Node::PARTITIONS
37
+ (key.digest[0..3].unpack(UNPACK_FORMAT)[0] & 0xFFFF) % Node::PARTITIONS
35
38
  )
36
39
  end
37
40
 
@@ -0,0 +1,104 @@
1
+ # Copyright 2014-2018 Aerospike, Inc.
2
+ #
3
+ # Portions may be licensed to Aerospike, Inc. under one or more contributor
4
+ # license agreements.
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License"); you may not
7
+ # use this file except in compliance with the License. You may obtain a copy of
8
+ # the License at
9
+ #
10
+ # 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 'aerospike/command/multi_command'
19
+
20
+ module Aerospike
21
+
22
+ class BatchDirectCommand < MultiCommand #:nodoc:
23
+
24
+ attr_accessor :batch
25
+ attr_accessor :policy
26
+ attr_accessor :key_map
27
+ attr_accessor :bin_names
28
+ attr_accessor :results
29
+ attr_accessor :read_attr
30
+
31
+ def initialize(node, batch, policy, key_map, bin_names, results, read_attr)
32
+ super(node)
33
+
34
+ @batch = batch
35
+ @policy = policy
36
+ @key_map = key_map
37
+ @bin_names = bin_names
38
+ @results = results
39
+ @read_attr = read_attr
40
+ end
41
+
42
+ def write_buffer
43
+ # Estimate buffer size
44
+ begin_cmd
45
+ byte_size = batch.keys.length * DIGEST_SIZE
46
+
47
+ @data_offset += batch.namespace.bytesize +
48
+ FIELD_HEADER_SIZE + byte_size + FIELD_HEADER_SIZE
49
+
50
+ if bin_names
51
+ bin_names.each do |bin_name|
52
+ estimate_operation_size_for_bin_name(bin_name)
53
+ end
54
+ end
55
+
56
+ size_buffer
57
+
58
+ operation_count = 0
59
+ if bin_names
60
+ operation_count = bin_names.length
61
+ end
62
+
63
+ write_header(policy, read_attr, 0, 2, operation_count)
64
+ write_field_string(batch.namespace, Aerospike::FieldType::NAMESPACE)
65
+ write_field_header(byte_size, Aerospike::FieldType::DIGEST_RIPE_ARRAY)
66
+
67
+ batch.keys.each do |key|
68
+ @data_offset += @data_buffer.write_binary(key.digest, @data_offset)
69
+ end
70
+
71
+ if bin_names
72
+ bin_names.each do |bin_name|
73
+ write_operation_for_bin_name(bin_name, Aerospike::Operation::READ)
74
+ end
75
+ end
76
+
77
+ end_cmd
78
+ end
79
+
80
+ # Parse all results in the batch. Add records to shared list.
81
+ # If the record was not found, the bins will be nil.
82
+ def parse_row(result_code)
83
+ generation = @data_buffer.read_int32(6)
84
+ expiration = @data_buffer.read_int32(10)
85
+ field_count = @data_buffer.read_int16(18)
86
+ op_count = @data_buffer.read_int16(20)
87
+
88
+ key = parse_key(field_count)
89
+
90
+ item = key_map[key.digest]
91
+ if item
92
+ if result_code == 0
93
+ index = item.index
94
+ key = item.key
95
+ results[index] = parse_record(key, op_count, generation, expiration)
96
+ end
97
+ else
98
+ Aerospike.logger.warn("Unexpected batch key returned: #{key}")
99
+ end
100
+ end
101
+
102
+ end # class
103
+
104
+ end # module
@@ -0,0 +1,51 @@
1
+ # Copyright 2014-2018 Aerospike, Inc.
2
+ #
3
+ # Portions may be licensed to Aerospike, Inc. under one or more contributor
4
+ # license agreements.
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License"); you may not
7
+ # use this file except in compliance with the License. You may obtain a copy of
8
+ # the License at
9
+ #
10
+ # 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 'aerospike/command/batch_direct_command'
19
+
20
+ module Aerospike
21
+
22
+ class BatchDirectExistsCommand < BatchDirectCommand #:nodoc:
23
+
24
+ def initialize(node, batch, policy, key_map, results)
25
+ super(node, batch, policy, key_map, nil, results, INFO1_READ | INFO1_NOBINDATA)
26
+ end
27
+
28
+ # Parse all results in the batch. Add records to shared list.
29
+ # If the record was not found, the bins will be nil.
30
+ def parse_row(result_code)
31
+ field_count = @data_buffer.read_int16(18)
32
+ op_count = @data_buffer.read_int16(20)
33
+
34
+ if op_count > 0
35
+ raise Aerospike::Exceptions::Parse.new('Received bins that were not requested!')
36
+ end
37
+
38
+ key = parse_key(field_count)
39
+ item = key_map[key.digest]
40
+
41
+ if item
42
+ index = item.index
43
+ results[index] = (result_code == 0)
44
+ else
45
+ Aerospike::logger.debug("Unexpected batch key returned: #{key.namespace}, #{key.digest}")
46
+ end
47
+ end
48
+
49
+ end # class
50
+
51
+ end # module
@@ -0,0 +1,40 @@
1
+ # Copyright 2014-2018 Aerospike, Inc.
2
+ #
3
+ # Portions may be licensed to Aerospike, Inc. under one or more contributor
4
+ # license agreements.
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License"); you may not
7
+ # use this file except in compliance with the License. You may obtain a copy of
8
+ # the License at
9
+ #
10
+ # 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
+ module Aerospike
19
+
20
+ BatchNamespace = Struct.new :namespace, :keys
21
+
22
+ class BatchDirectNode #:nodoc:
23
+
24
+ attr_accessor :node
25
+ attr_accessor :batch_namespaces
26
+
27
+ def self.generate_list(cluster, keys)
28
+ keys.group_by { |key| cluster.get_node_for_key(key) }
29
+ .map { |node, keys_for_node| BatchDirectNode.new(node, keys_for_node) }
30
+ end
31
+
32
+ def initialize(node, keys)
33
+ @node = node
34
+ @batch_namespaces = keys.group_by(&:namespace)
35
+ .map { |ns, keys_for_ns| BatchNamespace.new(ns, keys_for_ns) }
36
+ end
37
+
38
+ end
39
+
40
+ end
@@ -0,0 +1,119 @@
1
+ # Copyright 2018 Aerospike, Inc.
2
+ #
3
+ # Portions may be licensed to Aerospike, Inc. under one or more contributor
4
+ # license agreements.
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License"); you may not
7
+ # use this file except in compliance with the License. You may obtain a copy of
8
+ # the License at
9
+ #
10
+ # 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 'aerospike/command/multi_command'
19
+
20
+ module Aerospike
21
+
22
+ class BatchIndexCommand < MultiCommand #:nodoc:
23
+
24
+ attr_accessor :batch
25
+ attr_accessor :policy
26
+ attr_accessor :bin_names
27
+ attr_accessor :results
28
+ attr_accessor :read_attr
29
+
30
+ def initialize(node, batch, policy, bin_names, results, read_attr)
31
+ super(node)
32
+ @batch = batch
33
+ @policy = policy
34
+ @bin_names = bin_names
35
+ @results = results
36
+ @read_attr = read_attr
37
+ end
38
+
39
+ def write_buffer
40
+ bin_name_size = 0
41
+ operation_count = 0
42
+ field_count = 1
43
+ if bin_names
44
+ bin_names.each do |bin_name|
45
+ bin_name_size += bin_name.bytesize + OPERATION_HEADER_SIZE
46
+ end
47
+ operation_count = bin_names.length
48
+ end
49
+ begin_cmd
50
+ @data_offset += FIELD_HEADER_SIZE + 4 + 1 # batch.keys.length + flags
51
+
52
+ prev = nil
53
+ batch.keys.each do |key|
54
+ @data_offset += key.digest.length + 4 # 4 byte batch offset
55
+
56
+ if prev != nil && prev.namespace == key.namespace
57
+ @data_offset += 1
58
+ else
59
+ @data_offset += key.namespace.bytesize + FIELD_HEADER_SIZE + 1 + 1 + 2 + 2 # repeat/no-repeat flag + read_attr flags + field_count + operation_count
60
+ @data_offset += bin_name_size
61
+ end
62
+ end
63
+ size_buffer
64
+ write_header(policy,read_attr | INFO1_BATCH, 0, 1, 0)
65
+ write_field_header(0, Aerospike::FieldType::BATCH_INDEX)
66
+ @data_offset += @data_buffer.write_int32(batch.keys.length, @data_offset)
67
+ @data_offset += @data_buffer.write_byte(1, @data_offset)
68
+
69
+ prev = nil
70
+
71
+ batch.each_key_with_index do |key, index|
72
+ @data_offset += @data_buffer.write_int32(index, @data_offset)
73
+ @data_offset += @data_buffer.write_binary(key.digest, @data_offset)
74
+
75
+ if (prev != nil && prev.namespace == key.namespace)
76
+ @data_offset += @data_buffer.write_byte(1, @data_offset)
77
+ else
78
+ @data_offset += @data_buffer.write_byte(0, @data_offset)
79
+ @data_offset += @data_buffer.write_byte(read_attr, @data_offset)
80
+ @data_offset += @data_buffer.write_int16(field_count, @data_offset)
81
+ @data_offset += @data_buffer.write_int16(operation_count, @data_offset)
82
+ write_field_string(key.namespace, Aerospike::FieldType::NAMESPACE)
83
+
84
+ if bin_names
85
+ bin_names.each do |bin_name|
86
+ write_operation_for_bin_name(bin_name, Aerospike::Operation::READ)
87
+ end
88
+ end
89
+ prev = key
90
+ end
91
+ end
92
+ end_cmd
93
+ end
94
+
95
+ # Parse all results in the batch. Add records to shared list.
96
+ # If the record was not found, the bins will be nil.
97
+ def parse_row(result_code)
98
+ generation = @data_buffer.read_int32(6)
99
+ expiration = @data_buffer.read_int32(10)
100
+ batch_index = @data_buffer.read_int32(14)
101
+ field_count = @data_buffer.read_int16(18)
102
+ op_count = @data_buffer.read_int16(20)
103
+
104
+ key = parse_key(field_count)
105
+ req_key = batch.key_for_index(batch_index)
106
+
107
+ if key.digest == req_key.digest
108
+ if result_code == 0
109
+ record = parse_record(req_key, op_count, generation, expiration)
110
+ results[batch_index] = record
111
+ end
112
+ else
113
+ Aerospike.logger.warn("Unexpected batch key returned: #{key}")
114
+ end
115
+ end
116
+
117
+ end # class
118
+
119
+ end # module