aerospike 0.1.0

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