aerospike 2.23.0 → 2.24.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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.