aerospike 0.1.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 (62) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +0 -0
  3. data/LICENSE +203 -0
  4. data/README.md +123 -0
  5. data/lib/aerospike.rb +69 -0
  6. data/lib/aerospike/aerospike_exception.rb +111 -0
  7. data/lib/aerospike/bin.rb +46 -0
  8. data/lib/aerospike/client.rb +649 -0
  9. data/lib/aerospike/cluster/cluster.rb +537 -0
  10. data/lib/aerospike/cluster/connection.rb +113 -0
  11. data/lib/aerospike/cluster/node.rb +248 -0
  12. data/lib/aerospike/cluster/node_validator.rb +85 -0
  13. data/lib/aerospike/cluster/partition.rb +54 -0
  14. data/lib/aerospike/cluster/partition_tokenizer_new.rb +128 -0
  15. data/lib/aerospike/cluster/partition_tokenizer_old.rb +135 -0
  16. data/lib/aerospike/command/batch_command.rb +120 -0
  17. data/lib/aerospike/command/batch_command_exists.rb +93 -0
  18. data/lib/aerospike/command/batch_command_get.rb +150 -0
  19. data/lib/aerospike/command/batch_item.rb +69 -0
  20. data/lib/aerospike/command/batch_node.rb +82 -0
  21. data/lib/aerospike/command/command.rb +680 -0
  22. data/lib/aerospike/command/delete_command.rb +57 -0
  23. data/lib/aerospike/command/execute_command.rb +42 -0
  24. data/lib/aerospike/command/exists_command.rb +57 -0
  25. data/lib/aerospike/command/field_type.rb +44 -0
  26. data/lib/aerospike/command/operate_command.rb +37 -0
  27. data/lib/aerospike/command/read_command.rb +174 -0
  28. data/lib/aerospike/command/read_header_command.rb +63 -0
  29. data/lib/aerospike/command/single_command.rb +60 -0
  30. data/lib/aerospike/command/touch_command.rb +50 -0
  31. data/lib/aerospike/command/write_command.rb +60 -0
  32. data/lib/aerospike/host.rb +43 -0
  33. data/lib/aerospike/info.rb +96 -0
  34. data/lib/aerospike/key.rb +99 -0
  35. data/lib/aerospike/language.rb +25 -0
  36. data/lib/aerospike/ldt/large.rb +69 -0
  37. data/lib/aerospike/ldt/large_list.rb +100 -0
  38. data/lib/aerospike/ldt/large_map.rb +82 -0
  39. data/lib/aerospike/ldt/large_set.rb +78 -0
  40. data/lib/aerospike/ldt/large_stack.rb +72 -0
  41. data/lib/aerospike/loggable.rb +55 -0
  42. data/lib/aerospike/operation.rb +70 -0
  43. data/lib/aerospike/policy/client_policy.rb +37 -0
  44. data/lib/aerospike/policy/generation_policy.rb +37 -0
  45. data/lib/aerospike/policy/policy.rb +54 -0
  46. data/lib/aerospike/policy/priority.rb +34 -0
  47. data/lib/aerospike/policy/record_exists_action.rb +45 -0
  48. data/lib/aerospike/policy/write_policy.rb +61 -0
  49. data/lib/aerospike/record.rb +42 -0
  50. data/lib/aerospike/result_code.rb +353 -0
  51. data/lib/aerospike/task/index_task.rb +59 -0
  52. data/lib/aerospike/task/task.rb +71 -0
  53. data/lib/aerospike/task/udf_register_task.rb +55 -0
  54. data/lib/aerospike/task/udf_remove_task.rb +55 -0
  55. data/lib/aerospike/udf.rb +24 -0
  56. data/lib/aerospike/utils/buffer.rb +139 -0
  57. data/lib/aerospike/utils/epoc.rb +28 -0
  58. data/lib/aerospike/utils/pool.rb +65 -0
  59. data/lib/aerospike/value/particle_type.rb +45 -0
  60. data/lib/aerospike/value/value.rb +380 -0
  61. data/lib/aerospike/version.rb +4 -0
  62. metadata +132 -0
@@ -0,0 +1,537 @@
1
+ # encoding: utf-8
2
+ # Copyright 2014 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 'thread'
18
+ require 'timeout'
19
+
20
+ require 'atomic'
21
+
22
+ module Aerospike
23
+
24
+ private
25
+
26
+ class Cluster
27
+
28
+ attr_reader :connection_timeout, :connection_queue_size
29
+
30
+ def initialize(policy, *hosts)
31
+ @cluster_seeds = hosts
32
+ @connection_queue_size = policy.connection_queue_size
33
+ @connection_timeout = policy.timeout
34
+ @aliases = {}
35
+ @cluster_nodes = []
36
+ @partition_write_map = {}
37
+ @node_index = Atomic.new(0)
38
+ @closed = Atomic.new(false)
39
+ @mutex = Mutex.new
40
+
41
+ wait_till_stablized
42
+
43
+ if policy.fail_if_not_connected && !connected?
44
+ raise Aerospike::Exceptions::Aerospike.new(Aerospike::ResultCode::SERVER_NOT_AVAILABLE)
45
+ end
46
+
47
+ launch_tend_thread
48
+
49
+ Aerospike.logger.info('New cluster initialized and ready to be used...')
50
+
51
+ self
52
+ end
53
+
54
+ def add_seeds(hosts)
55
+ @mutex.synchronize do
56
+ @cluster_seeds.concat(hosts)
57
+ end
58
+ end
59
+
60
+ def seeds
61
+ @mutex.synchronize do
62
+ @cluster_seeds.dup
63
+ end
64
+ end
65
+
66
+ def connected?
67
+ # Must copy array reference for copy on write semantics to work.
68
+ node_array = nodes
69
+ (node_array.length > 0) && !@closed.value
70
+ end
71
+
72
+ def get_node(partition)
73
+ # Must copy hashmap reference for copy on write semantics to work.
74
+ nmap = partitions
75
+ if node_array = nmap[partition.namespace]
76
+ node = node_array.value[partition.partition_id]
77
+
78
+ if node && node.active?
79
+ return node
80
+ end
81
+ end
82
+
83
+ return random_node
84
+ end
85
+
86
+ # Returns a random node on the cluster
87
+ def random_node
88
+ # Must copy array reference for copy on write semantics to work.
89
+ node_array = nodes
90
+ length = node_array.length
91
+ for i in 0..length
92
+ # Must handle concurrency with other non-tending threads, so node_index is consistent.
93
+ index = (@node_index.update{|v| v+1} % node_array.length).abs
94
+ node = node_array[index]
95
+
96
+ if node.active?
97
+ return node
98
+ end
99
+ end
100
+ raise Aerospike::Exceptions::InvalidNode.new
101
+ end
102
+
103
+ # Returns a list of all nodes in the cluster
104
+ def nodes
105
+ @mutex.synchronize do
106
+ # Must copy array reference for copy on write semantics to work.
107
+ @cluster_nodes.dup
108
+ end
109
+ end
110
+
111
+ # Find a node by name and returns an error if not found
112
+ def get_node_by_name(node_name)
113
+ node = find_node_by_name(node_name)
114
+
115
+ raise Aerospike::Exceptions::InvalidNode.new unless node
116
+
117
+ node
118
+ end
119
+
120
+ # Closes all cached connections to the cluster nodes and stops the tend thread
121
+ def close
122
+ unless @closed.value
123
+ # send close signal to maintenance channel
124
+ @closed.value = true
125
+ @tend_thread.kill
126
+
127
+ nodes.each do |node|
128
+ node.close
129
+ end
130
+ end
131
+
132
+ end
133
+
134
+ def find_alias(aliass)
135
+ @mutex.synchronize do
136
+ @aliases[aliass]
137
+ end
138
+ end
139
+
140
+ def update_partitions(conn, node)
141
+ # TODO: Cluster should not care about version of tokenizer
142
+ # decouple clstr interface
143
+ nmap = {}
144
+ if node.use_new_info?
145
+ Aerospike.logger.info("Updating partitions using new protocol...")
146
+
147
+ tokens = PartitionTokenizerNew.new(conn)
148
+ nmap = tokens.update_partition(partitions, node)
149
+ else
150
+ Aerospike.logger.info("Updating partitions using old protocol...")
151
+ tokens = PartitionTokenizerOld.new(conn)
152
+ nmap = tokens.update_partition(partitions, node)
153
+ end
154
+
155
+ # update partition write map
156
+ set_partitions(nmap) if nmap
157
+
158
+ Aerospike.logger.info("Partitions updated...")
159
+ end
160
+
161
+ def request_info(policy, *commands)
162
+ node = random_node
163
+ conn = node.get_connection(policy.timeout)
164
+ Info.request(conn, *commands).tap do
165
+ node.put_connection(conn)
166
+ end
167
+ end
168
+
169
+ private
170
+
171
+ def launch_tend_thread
172
+ @tend_thread = Thread.new do
173
+ abort_on_exception = false
174
+ while true
175
+ begin
176
+ tend
177
+ sleep 1 # 1 second
178
+ rescue => e
179
+ Aerospike.logger.error("Exception occured during tend: #{e}")
180
+ end
181
+ end
182
+ end
183
+ end
184
+
185
+ def tend
186
+ nodes = self.nodes
187
+
188
+ # All node additions/deletions are performed in tend thread.
189
+ # If active nodes don't exist, seed cluster.
190
+ if nodes.empty?
191
+ Aerospike.logger.info("No connections available; seeding...")
192
+ seed_nodes
193
+
194
+ # refresh nodes list after seeding
195
+ nodes = self.nodes
196
+ end
197
+
198
+ # Refresh all known nodes.
199
+ friend_list = []
200
+ refresh_count = 0
201
+
202
+ # Clear node reference counts.
203
+ nodes.each do |node|
204
+ node.reference_count.value = 0
205
+ node.responded.value = false
206
+
207
+ if node.active?
208
+ begin
209
+ friends = node.refresh
210
+ refresh_count += 1
211
+ friend_list.concat(friends) if friends
212
+ rescue => e
213
+ Aerospike.logger.warn("Node `#{node}` refresh failed: #{e.to_s}")
214
+ end
215
+ end
216
+ end
217
+
218
+ # Add nodes in a batch.
219
+ add_list = find_nodes_to_add(friend_list)
220
+ add_nodes(add_list) unless add_list.empty?
221
+
222
+ # Handle nodes changes determined from refreshes.
223
+ # Remove nodes in a batch.
224
+ remove_list = find_nodes_to_remove(refresh_count)
225
+ remove_nodes(remove_list) unless remove_list.empty?
226
+
227
+ Aerospike.logger.info("Tend finished. Live node count: #{nodes.length}")
228
+ end
229
+
230
+ def wait_till_stablized
231
+ count = -1
232
+
233
+ # will run until the cluster is stablized
234
+ thr = Thread.new do
235
+ abort_on_exception=true
236
+ while true
237
+ tend
238
+
239
+ # Check to see if cluster has changed since the last Tend.
240
+ # If not, assume cluster has stabilized and return.
241
+ if count == nodes.length
242
+ break
243
+ end
244
+
245
+ sleep(0.001) # sleep for a miliseconds
246
+
247
+ count = nodes.length
248
+ end
249
+ end
250
+
251
+ # wait for the thread to finish or timeout
252
+ begin
253
+ Timeout.timeout(1) do
254
+ thr.join
255
+ end
256
+ rescue Timeout::Error
257
+ thr.kill if thr.alive?
258
+ end
259
+
260
+ end
261
+
262
+ def set_partitions(part_map)
263
+ @mutex.synchronize do
264
+ @partition_write_map = part_map
265
+ end
266
+ end
267
+
268
+ def partitions
269
+ res = nil
270
+ @mutex.synchronize do
271
+ res = @partition_write_map
272
+ end
273
+
274
+ res
275
+ end
276
+
277
+ def seed_nodes
278
+ seed_array = seeds
279
+
280
+ Aerospike.logger.info("Seeding the cluster. Seeds count: #{seed_array.length}")
281
+
282
+ list = []
283
+
284
+ seed_array.each do |seed|
285
+ begin
286
+ seed_node_validator = NodeValidator.new(seed, @connection_timeout)
287
+ rescue => e
288
+ Aerospike.logger.warn("Seed #{seed.to_s} failed: #{e}")
289
+ next
290
+ end
291
+
292
+ nv = nil
293
+ # Seed host may have multiple aliases in the case of round-robin dns configurations.
294
+ seed_node_validator.aliases.each do |aliass|
295
+
296
+ if aliass == seed
297
+ nv = seed_node_validator
298
+ else
299
+ begin
300
+ nv = NodeValidator.new(aliass, @connection_timeout)
301
+ rescue Exection => e
302
+ Aerospike.logger.warn("Seed #{seed.to_s} failed: #{e}")
303
+ next
304
+ end
305
+ end
306
+
307
+ if !find_node_name(list, nv.name)
308
+ node = create_node(nv)
309
+ add_aliases(node)
310
+ list << node
311
+ end
312
+ end
313
+
314
+ end
315
+
316
+ if list.length > 0
317
+ add_nodes_copy(list)
318
+ end
319
+
320
+ end
321
+
322
+ # Finds a node by name in a list of nodes
323
+ def find_node_name(list, name)
324
+ list.any?{|name| node.name == name}
325
+ end
326
+
327
+ def add_alias(host, node)
328
+ if host && node
329
+ @mutex.synchronize do
330
+ @aliases[host] = node
331
+ end
332
+ end
333
+ end
334
+
335
+ def remove_alias(aliass)
336
+ if aliass
337
+ @mutex.synchronize do
338
+ @aliases.delete(aliass)
339
+ end
340
+ end
341
+ end
342
+
343
+ def find_nodes_to_add(hosts)
344
+ list = []
345
+
346
+ hosts.each do |host|
347
+ begin
348
+ nv = NodeValidator.new(host, @connection_timeout)
349
+
350
+ # if node is already in cluster's node list,
351
+ # or already included in the list to be added, we should skip it
352
+ node = find_node_by_name(nv.name)
353
+ node ||= list.detect{|n| n.name == nv.name}
354
+
355
+ # make sure node is not already in the list to add
356
+ if node
357
+ # Duplicate node name found. This usually occurs when the server
358
+ # services list contains both internal and external IP addresses
359
+ # for the same node. Add new host to list of alias filters
360
+ # and do not add new node.
361
+ node.reference_count.update{|v| v + 1}
362
+ node.add_alias(host)
363
+ add_alias(host, node)
364
+ next
365
+ end
366
+
367
+ node = create_node(nv)
368
+ list << node
369
+
370
+ rescue => e
371
+ Aerospike.logger.warn("Add node #{node.to_s} failed: #{e}")
372
+ end
373
+ end
374
+
375
+ list
376
+ end
377
+
378
+ def create_node(nv)
379
+ Node.new(self, nv)
380
+ end
381
+
382
+ def find_nodes_to_remove(refresh_count)
383
+ node_list = nodes
384
+
385
+ remove_list = []
386
+
387
+ node_list.each do |node|
388
+ if !node.active?
389
+ # Inactive nodes must be removed.
390
+ remove_list << node
391
+ next
392
+ end
393
+
394
+ case node_list.length
395
+ when 1
396
+ # Single node clusters rely solely on node health.
397
+ remove_list << node if node.unhealthy?
398
+
399
+ when 2
400
+ # Two node clusters require at least one successful refresh before removing.
401
+ if refresh_count == 1 && node.reference_count.value == 0 && !node.responded.value
402
+ # Node is not referenced nor did it respond.
403
+ remove_list << node
404
+ end
405
+
406
+ else
407
+ # Multi-node clusters require two successful node refreshes before removing.
408
+ if refresh_count >= 2 && node.reference_count.value == 0
409
+ # Node is not referenced by other nodes.
410
+ # Check if node responded to info request.
411
+ if node.responded.value
412
+ # Node is alive, but not referenced by other nodes. Check if mapped.
413
+ if !find_node_in_partition_map(node)
414
+ # Node doesn't have any partitions mapped to it.
415
+ # There is not point in keeping it in the cluster.
416
+ remove_list << node
417
+ end
418
+ else
419
+ # Node not responding. Remove it.
420
+ remove_list << node
421
+ end
422
+ end
423
+ end
424
+ end
425
+
426
+ remove_list
427
+ end
428
+
429
+ def find_node_in_partition_map(filter)
430
+ partitions_list = partitions
431
+
432
+ partitions_list.each do |node_array|
433
+ max = node_array.length
434
+
435
+ for i in 0...max
436
+ node = node_array[i]
437
+ # Use reference equality for performance.
438
+ if node == filter
439
+ return true
440
+ end
441
+ end
442
+ end
443
+ false
444
+ end
445
+
446
+ def add_nodes(nodes_to_add)
447
+ # Add all nodes at once to avoid copying entire array multiple times.
448
+ nodes_to_add.each do |node|
449
+ add_aliases(node)
450
+ end
451
+
452
+ add_nodes_copy(nodes_to_add)
453
+ end
454
+
455
+ def add_aliases(node)
456
+ # Add node's aliases to global alias set.
457
+ # Aliases are only used in tend thread, so synchronization is not necessary.
458
+ node.get_aliases.each do |aliass|
459
+ @aliases[aliass] = node
460
+ end
461
+ end
462
+
463
+ def add_nodes_copy(nodes_to_add)
464
+ @mutex.synchronize do
465
+ @cluster_nodes.concat(nodes_to_add)
466
+ end
467
+ end
468
+
469
+ def remove_nodes(nodes_to_remove)
470
+ # There is no need to delete nodes from partition_write_map because the nodes
471
+ # have already been set to inactive. Further connection requests will result
472
+ # in an exception and a different node will be tried.
473
+
474
+ # Cleanup node resources.
475
+ nodes_to_remove.each do |node|
476
+ # Remove node's aliases from cluster alias set.
477
+ # Aliases are only used in tend thread, so synchronization is not necessary.
478
+ node.get_aliases.each do |aliass|
479
+ Aerospike.logger.debug("Removing alias #{aliass}")
480
+ remove_alias(aliass)
481
+ end
482
+
483
+ node.close
484
+ end
485
+
486
+ # Remove all nodes at once to avoid copying entire array multiple times.
487
+ remove_nodes_copy(nodes_to_remove)
488
+ end
489
+
490
+ def set_nodes(nodes)
491
+ @mutex.synchronize do
492
+ # Replace nodes with copy.
493
+ @cluster_nodes = nodes
494
+ end
495
+ end
496
+
497
+ def remove_nodes_copy(nodes_to_remove)
498
+ # Create temporary nodes array.
499
+ # Since nodes are only marked for deletion using node references in the nodes array,
500
+ # and the tend thread is the only thread modifying nodes, we are guaranteed that nodes
501
+ # in nodes_to_remove exist. Therefore, we know the final array size.
502
+ nodes_list = nodes
503
+ node_array = []
504
+ count = 0
505
+
506
+ # Add nodes that are not in remove list.
507
+ nodes_list.each do |node|
508
+ if node_exists(node, nodes_to_remove)
509
+ Aerospike.logger.info("Removed node `#{node}`")
510
+ else
511
+ node_array[count] = node
512
+ count += 1
513
+ end
514
+ end
515
+
516
+ # Do sanity check to make sure assumptions are correct.
517
+ if count < node_array.length
518
+ Aerospike.logger.warn("Node remove mismatch. Expected #{node_array.length}, Received #{count}")
519
+
520
+ # Resize array.
521
+ node_array = node_array.dup[0..count-1]
522
+ end
523
+
524
+ set_nodes(node_array)
525
+ end
526
+
527
+ def node_exists(search, node_list)
528
+ node_list.any? {|node| node == search }
529
+ end
530
+
531
+ def find_node_by_name(node_name)
532
+ nodes.detect{|node| node.name == node_name }
533
+ end
534
+
535
+ end
536
+
537
+ end