aerospike 2.23.0 → 2.24.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.
@@ -22,20 +22,45 @@ module Aerospike
22
22
  # Container object for query policy command.
23
23
  class QueryPolicy < Policy
24
24
 
25
+ attr_accessor :concurrent_nodes
26
+ attr_accessor :max_records
25
27
  attr_accessor :include_bin_data
26
28
  attr_accessor :record_queue_size
27
29
  attr_accessor :records_per_second
30
+ attr_accessor :socket_timeout
31
+ attr_accessor :short_query
28
32
 
29
33
  def initialize(opt={})
30
34
  super(opt)
31
35
 
32
- @max_retries = 0
33
-
34
36
  # Indicates if bin data is retrieved. If false, only record digests (and
35
37
  # user keys if stored on the server) are retrieved.
36
38
  # Default is true.
37
39
  @include_bin_data = opt.fetch(:include_bin_data, true)
38
40
 
41
+ # Approximates the number of records to return to the client. This number is divided by the
42
+ # number of nodes involved in the query. The actual number of records returned
43
+ # may be less than MaxRecords if node record counts are small and unbalanced across
44
+ # nodes.
45
+ #
46
+ # This field is supported on server versions >= 4.9.
47
+ #
48
+ # Default: 0 (do not limit record count)
49
+ @max_records = opt.fetch(:max_records) { 0 }
50
+
51
+ # Issue scan requests in parallel or serially.
52
+ @concurrent_nodes = opt.fetch(:concurrent_nodes) { true }
53
+
54
+ # Determines network timeout for each attempt.
55
+ #
56
+ # If socket_timeout is not zero and socket_timeout is reached before an attempt completes,
57
+ # the Timeout above is checked. If Timeout is not exceeded, the transaction
58
+ # is retried. If both socket_timeout and Timeout are non-zero, socket_timeout must be less
59
+ # than or equal to Timeout, otherwise Timeout will also be used for socket_timeout.
60
+ #
61
+ # Default: 30s
62
+ @socket_timeout = opt[:socket_timeout] || 30000
63
+
39
64
  # Number of records to place in queue before blocking. Records received
40
65
  # from multiple server nodes will be placed in a queue. A separate thread
41
66
  # consumes these records in parallel. If the queue is full, the producer
@@ -49,6 +74,14 @@ module Aerospike
49
74
  # Default is 0
50
75
  @records_per_second = opt[:records_per_second] || 0
51
76
 
77
+ # Detemine wether query expected to return less than 100 records.
78
+ # If true, the server will optimize the query for a small record set.
79
+ # This field is ignored for aggregation queries, background queries
80
+ # and server versions 6.0+.
81
+ #
82
+ # Default: false
83
+ @short_query = opt[:short_query] ||false
84
+
52
85
  self
53
86
  end
54
87
 
@@ -34,8 +34,6 @@ module Aerospike
34
34
  def initialize(opt={})
35
35
  super(opt)
36
36
 
37
- @max_retries = 0
38
-
39
37
  # Approximates the number of records to return to the client. This number is divided by the
40
38
  # number of nodes involved in the query. The actual number of records returned
41
39
  # may be less than MaxRecords if node record counts are small and unbalanced across
@@ -82,7 +82,7 @@ module Aerospike
82
82
  @data_offset += binNameSize
83
83
  fieldCount+=1
84
84
  end
85
- else
85
+ else
86
86
  @data_offset += @partitions.length * 2 + FIELD_HEADER_SIZE
87
87
  fieldCount += 1
88
88
 
@@ -0,0 +1,73 @@
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
+ module Aerospike
19
+ class QueryExecutor # :nodoc:
20
+
21
+ def self.query_partitions(cluster, policy, tracker, statement, recordset)
22
+ interval = policy.sleep_between_retries
23
+
24
+ should_retry = false
25
+
26
+ loop do
27
+ list = tracker.assign_partitions_to_nodes(cluster, statement.namespace)
28
+
29
+ if policy.concurrent_nodes
30
+ threads = []
31
+ # Use a thread per node
32
+ list.each do |node_partition|
33
+
34
+ threads << Thread.new do
35
+ Thread.current.abort_on_exception = true
36
+ command = QueryPartitionCommand.new(node_partition.node, tracker, policy, statement, recordset, node_partition)
37
+ begin
38
+ command.execute
39
+ rescue => e
40
+ should_retry ||= command.should_retry(e)
41
+ # puts "should retry: #{should_retry}"
42
+ Aerospike.logger.error(e.backtrace.join("\n")) unless e == QUERY_TERMINATED_EXCEPTION
43
+ end
44
+ end
45
+ end
46
+ threads.each(&:join)
47
+ else
48
+ # Use a single thread for all nodes for all node
49
+ list.each do |node_partition|
50
+ command = QueryPartitionCommand.new(node_partition.node, tracker, policy, statement, recordset, node_partition)
51
+ begin
52
+ command.execute
53
+ rescue => e
54
+ should_retry ||= command.should_retry(e)
55
+ Aerospike.logger.error(e.backtrace.join("\n")) unless e == QUERY_TERMINATED_EXCEPTION
56
+ end
57
+ end
58
+ end
59
+
60
+ complete = tracker.complete?(@cluster, policy)
61
+
62
+ if complete || !should_retry
63
+ recordset.thread_finished
64
+ return
65
+ end
66
+ sleep(interval) if policy.sleep_between_retries > 0
67
+ statement.reset_task_id
68
+ end
69
+ end
70
+
71
+ end
72
+
73
+ end
@@ -0,0 +1,266 @@
1
+ # encoding: utf-8
2
+ # Copyright 2014-2020 Aerospike, Inc.
3
+ #
4
+ # Portions may be licensed to Aerospike, Inc. under one or more contributor
5
+ # license agreements.
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License") you may not
8
+ # use this file except in compliance with the License. You may obtain a copy of
9
+ # the License at http:#www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14
+ # License for the specific language governing permissions and limitations under
15
+ # the License.
16
+
17
+ require 'aerospike/query/stream_command'
18
+ require 'aerospike/query/recordset'
19
+
20
+ module Aerospike
21
+
22
+ private
23
+
24
+ class QueryPartitionCommand < QueryCommand #:nodoc:
25
+
26
+ def initialize(node, tracker, policy, statement, recordset, node_partitions)
27
+ super(node, policy, statement, recordset, @node_partitions)
28
+ @node_partitions = node_partitions
29
+ @tracker = tracker
30
+ end
31
+
32
+ def write_buffer
33
+ function_arg_buffer = nil
34
+ field_count = 0
35
+ filter_size = 0
36
+ bin_name_size = 0
37
+
38
+ begin_cmd
39
+
40
+ if @statement.namespace
41
+ @data_offset += @statement.namespace.bytesize + FIELD_HEADER_SIZE
42
+ field_count+=1
43
+ end
44
+
45
+ if @statement.set_name
46
+ @data_offset += @statement.set_name.bytesize + FIELD_HEADER_SIZE
47
+ field_count+=1
48
+ end
49
+
50
+ # Estimate recordsPerSecond field size. This field is used in new servers and not used
51
+ # (but harmless to add) in old servers.
52
+ if @policy.records_per_second > 0
53
+ @data_offset += 4 + FIELD_HEADER_SIZE
54
+ field_count+=1
55
+ end
56
+
57
+ # Estimate socket timeout field size. This field is used in new servers and not used
58
+ # (but harmless to add) in old servers.
59
+ @data_offset += 4 + FIELD_HEADER_SIZE
60
+ field_count+=1
61
+
62
+ # Estimate task_id field.
63
+ @data_offset += 8 + FIELD_HEADER_SIZE
64
+ field_count+=1
65
+
66
+ filter = @statement.filters[0]
67
+ bin_names = @statement.bin_names
68
+ packed_ctx = nil
69
+
70
+ if filter
71
+ col_type = filter.collection_type
72
+
73
+ # Estimate INDEX_TYPE field.
74
+ if col_type > 0
75
+ @data_offset += FIELD_HEADER_SIZE + 1
76
+ field_count+=1
77
+ end
78
+
79
+ # Estimate INDEX_RANGE field.
80
+ @data_offset += FIELD_HEADER_SIZE
81
+ filter_size+=1 # num filters
82
+ filter_size += filter.estimate_size
83
+
84
+ @data_offset += filter_size
85
+ field_count+=1
86
+
87
+ # TODO: Implement
88
+ # packed_ctx = filter.packed_ctx
89
+ # if packed_ctx
90
+ # @data_offset += FIELD_HEADER_SIZE + packed_ctx.length
91
+ # field_count+=1
92
+ # end
93
+ end
94
+
95
+ @statement.set_task_id
96
+ predexp = @policy.predexp || @statement.predexp
97
+
98
+ if predexp
99
+ @data_offset += FIELD_HEADER_SIZE
100
+ pred_size = Aerospike::PredExp.estimate_size(predexp)
101
+ @data_offset += pred_size
102
+ field_count += 1
103
+ end
104
+
105
+ # Estimate aggregation/background function size.
106
+ if @statement.function_name
107
+ @data_offset += FIELD_HEADER_SIZE + 1 # udf type
108
+ @data_offset += @statement.package_name.bytesize + FIELD_HEADER_SIZE
109
+ @data_offset += @statement.function_name.bytesize + FIELD_HEADER_SIZE
110
+
111
+ function_arg_buffer=''
112
+ if @statement.function_args && @statement.function_args.length > 0
113
+ function_arg_buffer = Value.of(@statement.function_args).to_bytes
114
+ end
115
+ @data_offset += FIELD_HEADER_SIZE + function_arg_buffer.bytesize
116
+ field_count += 4
117
+ end
118
+
119
+ max_records = 0
120
+ parts_full_size = 0
121
+ parts_partial_digest_size = 0
122
+ parts_partial_bval_size = 0
123
+
124
+ unless @node_partitions.nil?
125
+ parts_full_size = @node_partitions.parts_full.length * 2
126
+ parts_partial_digest_size = @node_partitions.parts_partial.length * 20
127
+
128
+ unless filter.nil?
129
+ parts_partial_bval_size = @node_partitions.parts_partial.length * 8
130
+ end
131
+ max_records = @node_partitions.record_max
132
+ end
133
+
134
+ if parts_full_size > 0
135
+ @data_offset += parts_full_size + FIELD_HEADER_SIZE
136
+ field_count+=1
137
+ end
138
+
139
+ if parts_partial_digest_size > 0
140
+ @data_offset += parts_partial_digest_size + FIELD_HEADER_SIZE
141
+ field_count+=1
142
+ end
143
+
144
+ if parts_partial_bval_size > 0
145
+ @data_offset += parts_partial_bval_size + FIELD_HEADER_SIZE
146
+ field_count+=1
147
+ end
148
+
149
+ # Estimate max records field size. This field is used in new servers and not used
150
+ # (but harmless to add) in old servers.
151
+ if max_records > 0
152
+ @data_offset += 8 + FIELD_HEADER_SIZE
153
+ field_count+=1
154
+ end
155
+
156
+ operation_count = 0
157
+ unless bin_names.empty?
158
+ # Estimate size for selected bin names (query bin names already handled for old servers).
159
+ bin_names.each do |bin_name|
160
+ estimate_operation_size_for_bin_name(bin_name)
161
+ end
162
+ operation_count = bin_names.length
163
+ end
164
+
165
+ projected_offset = @data_offset
166
+
167
+ size_buffer
168
+
169
+ read_attr = INFO1_READ
170
+ read_attr |= INFO1_NOBINDATA if !@policy.include_bin_data
171
+ read_attr |= INFO1_SHORT_QUERY if @policy.short_query
172
+
173
+ infoAttr = INFO3_PARTITION_DONE
174
+
175
+ write_header(@policy, read_attr, 0, field_count, operation_count)
176
+
177
+ write_field_string(@statement.namespace, FieldType::NAMESPACE) if @statement.namespace
178
+ write_field_string(@statement.set_name, FieldType::TABLE) if @statement.set_name
179
+
180
+ # Write records per second.
181
+ write_field_int(@policy.records_per_second, FieldType::RECORDS_PER_SECOND) if @policy.records_per_second > 0
182
+
183
+ # Write socket idle timeout.
184
+ write_field_int(@policy.socket_timeout, FieldType::SOCKET_TIMEOUT)
185
+
186
+ # Write task_id field
187
+ write_field_int64(@statement.task_id, FieldType::TRAN_ID)
188
+
189
+ unless predexp.nil?
190
+ write_field_header(pred_size, Aerospike::FieldType::PREDEXP)
191
+ @data_offset = Aerospike::PredExp.write(
192
+ predexp, @data_buffer, @data_offset
193
+ )
194
+ end
195
+
196
+ if filter
197
+ type = filter.collection_type
198
+
199
+ if type > 0
200
+ write_field_header(1, FieldType::INDEX_TYPE)
201
+ @data_offset += @data_buffer.write_byte(type, @data_offset)
202
+ end
203
+
204
+ write_field_header(filter_size, FieldType::INDEX_RANGE)
205
+ @data_offset += @data_buffer.write_byte(1, @data_offset)
206
+ @data_offset = filter.write(@data_buffer, @data_offset)
207
+
208
+ # TODO: Implement
209
+ # if packed_ctx
210
+ # write_field_header(packed_ctx.length, FieldType::INDEX_CONTEXT)
211
+ # @data_buffer.write_binary(packed_ctx, @data_offset)
212
+ # end
213
+ end
214
+
215
+ if @statement.function_name
216
+ write_field_header(1, FieldType::UDF_OP)
217
+ @data_offset += @data_buffer.write_byte(1, @data_offset)
218
+ write_field_string(@statement.package_name, FieldType::UDF_PACKAGE_NAME)
219
+ write_field_string(@statement.function_name, FieldType::UDF_FUNCTION)
220
+ write_field_string(function_arg_buffer, FieldType::UDF_ARGLIST)
221
+ end
222
+
223
+ if parts_full_size > 0
224
+ write_field_header(parts_full_size, FieldType::PID_ARRAY)
225
+ @node_partitions.parts_full.each do |part|
226
+ @data_offset += @data_buffer.write_uint16_little_endian(part.id, @data_offset)
227
+ end
228
+ end
229
+
230
+ if parts_partial_digest_size > 0
231
+ write_field_header(parts_partial_digest_size, FieldType::DIGEST_ARRAY)
232
+ @node_partitions.parts_partial.each do |part|
233
+ @data_offset += @data_buffer.write_binary(part.digest, @data_offset)
234
+ end
235
+ end
236
+
237
+ if parts_partial_bval_size > 0
238
+ write_field_header(parts_partial_bval_size, FieldType::BVAL_ARRAY)
239
+ @node_partitions.parts_partial.each do |part|
240
+ @data_offset += @data_buffer.write_uint64_little_endian(part.bval, @data_offset)
241
+ end
242
+ end
243
+
244
+ if max_records > 0
245
+ write_field(max_records, FieldType::MAX_RECORDS)
246
+ end
247
+
248
+ unless bin_names.empty?
249
+ bin_names.each do |bin_name|
250
+ write_operation_for_bin_name(bin_name, Operation::READ)
251
+ end
252
+ end
253
+
254
+ end_cmd
255
+
256
+ nil
257
+ end
258
+
259
+ def should_retry(e)
260
+ # !! converts nil to false
261
+ !!@tracker&.should_retry(@node_partitions, e)
262
+ end
263
+
264
+ end # class
265
+
266
+ end # module
@@ -81,6 +81,13 @@ module Aerospike
81
81
  end
82
82
  end
83
83
 
84
+ def reset_task_id
85
+ @task_id = rand(RAND_MAX)
86
+ while @task_id == 0
87
+ @task_id = rand(RAND_MAX)
88
+ end
89
+ end
90
+
84
91
  private
85
92
 
86
93
  RAND_MAX = 2**63
@@ -80,7 +80,8 @@ module Aerospike
80
80
  raise expn
81
81
  end
82
82
 
83
- @tracker&.set_last(@node_partitions, key, key.bval)
83
+ # UDF results do not return a key
84
+ @tracker&.set_last(@node_partitions, key, key.bval) if key
84
85
  end
85
86
  end # while
86
87
 
@@ -125,6 +125,11 @@ module Aerospike
125
125
  8
126
126
  end
127
127
 
128
+ def write_uint64_little_endian(i, offset)
129
+ @buf[offset, 8] = [i].pack(UINT64LE)
130
+ 8
131
+ end
132
+
128
133
  def write_double(f, offset)
129
134
  @buf[offset, 8] = [f].pack(DOUBLE)
130
135
  8
@@ -203,14 +208,28 @@ module Aerospike
203
208
  end
204
209
  end
205
210
 
206
- def dump(from=nil, to=nil)
207
- from ||= 0
208
- to ||= @slice_end - 1
209
-
210
- @buf.bytes[from...to].each do |c|
211
- print c.ord.to_s(16)
212
- putc ' '
211
+ def dump(start=0, finish=nil)
212
+ finish ||= @slice_end - 1
213
+ width = 16
214
+
215
+ ascii = '|'
216
+ counter = 0
217
+
218
+ print '%06x ' % start
219
+ @buf.bytes[start...finish].each do |c|
220
+ if counter >= start
221
+ print '%02x ' % c
222
+ ascii << (c.between?(32, 126) ? c : ?.)
223
+ if ascii.length >= width
224
+ ascii << '|'
225
+ puts ascii
226
+ ascii = '|'
227
+ print '%06x ' % (counter + 1)
228
+ end
229
+ end
230
+ counter += 1
213
231
  end
232
+ puts
214
233
  end
215
234
 
216
235
  end # buffer
@@ -1,4 +1,4 @@
1
1
  # encoding: utf-8
2
2
  module Aerospike
3
- VERSION = "2.23.0"
3
+ VERSION = "2.24.0"
4
4
  end
data/lib/aerospike.rb CHANGED
@@ -166,6 +166,8 @@ require 'aerospike/query/partition_filter'
166
166
  require 'aerospike/query/node_partitions'
167
167
  require 'aerospike/query/scan_executor'
168
168
  require 'aerospike/query/scan_partition_command'
169
+ require 'aerospike/query/query_executor'
170
+ require 'aerospike/query/query_partition_command'
169
171
 
170
172
  require 'aerospike/query/pred_exp/and_or'
171
173
  require 'aerospike/query/pred_exp/geo_json_value'
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.23.0
4
+ version: 2.24.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: 2022-10-25 00:00:00.000000000 Z
12
+ date: 2022-11-15 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: msgpack
@@ -108,6 +108,7 @@ files:
108
108
  - lib/aerospike/command/write_command.rb
109
109
  - lib/aerospike/connection/authenticate.rb
110
110
  - lib/aerospike/connection/create.rb
111
+ - lib/aerospike/exp/exp.rb
111
112
  - lib/aerospike/features.rb
112
113
  - lib/aerospike/geo_json.rb
113
114
  - lib/aerospike/host.rb
@@ -168,6 +169,8 @@ files:
168
169
  - lib/aerospike/query/pred_exp/regex_flags.rb
169
170
  - lib/aerospike/query/pred_exp/string_value.rb
170
171
  - lib/aerospike/query/query_command.rb
172
+ - lib/aerospike/query/query_executor.rb
173
+ - lib/aerospike/query/query_partition_command.rb
171
174
  - lib/aerospike/query/recordset.rb
172
175
  - lib/aerospike/query/scan_command.rb
173
176
  - lib/aerospike/query/scan_executor.rb
@@ -218,7 +221,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
218
221
  - !ruby/object:Gem::Version
219
222
  version: '0'
220
223
  requirements: []
221
- rubygems_version: 3.1.6
224
+ rubygems_version: 3.3.5
222
225
  signing_key:
223
226
  specification_version: 4
224
227
  summary: An Aerospike driver for Ruby.