fluent-plugin-droonga 1.0.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. data/.travis.yml +1 -1
  2. data/Gemfile +1 -1
  3. data/fluent-plugin-droonga.gemspec +1 -1
  4. data/lib/droonga/catalog/collection_volume.rb +97 -0
  5. data/lib/droonga/catalog/dataset.rb +28 -1
  6. data/lib/droonga/catalog/errors.rb +28 -9
  7. data/lib/droonga/catalog/schema.rb +23 -2
  8. data/lib/droonga/catalog/single_volume.rb +28 -0
  9. data/lib/droonga/catalog/slice.rb +43 -0
  10. data/lib/droonga/catalog/version1.rb +3 -3
  11. data/lib/droonga/catalog/version2.rb +17 -81
  12. data/lib/droonga/catalog/version2_validator.rb +63 -0
  13. data/lib/droonga/catalog/volume.rb +33 -0
  14. data/lib/droonga/catalog/volume_collection.rb +56 -0
  15. data/lib/droonga/catalog_observer.rb +7 -19
  16. data/lib/droonga/collectors.rb +1 -1
  17. data/lib/droonga/collectors/{add.rb → or.rb} +1 -1
  18. data/lib/droonga/dispatcher.rb +24 -18
  19. data/lib/droonga/distributed_command_planner.rb +7 -11
  20. data/lib/droonga/distributor.rb +29 -17
  21. data/lib/droonga/event_loop.rb +2 -11
  22. data/lib/droonga/fluent_message_sender.rb +51 -5
  23. data/lib/droonga/handler_runner.rb +1 -1
  24. data/lib/droonga/job_protocol.rb +20 -0
  25. data/lib/droonga/job_pusher.rb +178 -0
  26. data/lib/droonga/{message_receiver.rb → job_receiver.rb} +13 -6
  27. data/lib/droonga/message_matcher.rb +18 -15
  28. data/lib/droonga/planner.rb +2 -3
  29. data/lib/droonga/plugins/crud.rb +1 -1
  30. data/lib/droonga/plugins/groonga/column_create.rb +4 -1
  31. data/lib/droonga/plugins/groonga/table_create.rb +1 -1
  32. data/lib/droonga/plugins/groonga/table_remove.rb +1 -1
  33. data/lib/droonga/plugins/search/distributed_search_planner.rb +9 -0
  34. data/lib/droonga/processor.rb +3 -3
  35. data/lib/droonga/reducer.rb +15 -12
  36. data/lib/droonga/searcher.rb +49 -4
  37. data/lib/droonga/server.rb +2 -0
  38. data/lib/droonga/single_step.rb +22 -7
  39. data/lib/droonga/slice.rb +7 -7
  40. data/lib/droonga/step_runner.rb +3 -2
  41. data/lib/droonga/worker.rb +10 -8
  42. data/test/command/suite/add/dimension/column.catalog.json +27 -0
  43. data/test/command/suite/add/dimension/column.expected +57 -0
  44. data/test/command/suite/add/dimension/column.test +51 -0
  45. data/test/command/suite/search/adjusters/multiple.catalog.json +38 -0
  46. data/test/command/suite/search/adjusters/multiple.expected +23 -0
  47. data/test/command/suite/search/adjusters/multiple.test +75 -0
  48. data/test/command/suite/search/adjusters/one.catalog.json +38 -0
  49. data/test/command/suite/search/adjusters/one.expected +23 -0
  50. data/test/command/suite/search/adjusters/one.test +66 -0
  51. data/test/command/suite/search/attributes/array.test +0 -2
  52. data/test/command/suite/search/attributes/hash.test +0 -2
  53. data/test/command/suite/search/complex.test +0 -2
  54. data/test/command/suite/search/condition/nested.test +0 -2
  55. data/test/command/suite/search/condition/query.test +0 -2
  56. data/test/command/suite/search/condition/script.test +0 -2
  57. data/test/command/suite/search/group/string.test +0 -4
  58. data/test/command/suite/search/group/subrecord/with-sort.catalog.json +33 -0
  59. data/test/command/suite/search/group/subrecord/with-sort.expected +34 -0
  60. data/test/command/suite/search/group/subrecord/with-sort.test +81 -0
  61. data/test/command/suite/search/multiple/chained.test +0 -4
  62. data/test/command/suite/search/multiple/parallel.test +0 -4
  63. data/test/command/suite/search/range/only-output.test +0 -2
  64. data/test/command/suite/search/range/only-sort.test +0 -2
  65. data/test/command/suite/search/range/sort-and-output.test +0 -2
  66. data/test/command/suite/search/range/too-large-output-offset.test +0 -2
  67. data/test/command/suite/search/range/too-large-sort-offset.test +0 -2
  68. data/test/command/suite/search/response/elapsed_time.catalog.json +13 -0
  69. data/test/command/suite/search/response/elapsed_time.expected +15 -0
  70. data/test/command/suite/search/response/elapsed_time.test +26 -0
  71. data/test/command/suite/search/response/records/value/time.test +0 -2
  72. data/test/command/suite/search/simple.test +0 -2
  73. data/test/command/suite/search/sort/default-offset-limit.test +0 -2
  74. data/test/command/suite/search/sort/invisible-column.test +0 -2
  75. data/test/unit/catalog/test_collection_volume.rb +103 -0
  76. data/test/unit/catalog/test_dataset.rb +69 -8
  77. data/test/unit/catalog/test_schema.rb +63 -23
  78. data/test/unit/catalog/test_single_volume.rb +31 -0
  79. data/test/unit/catalog/test_slice.rb +92 -0
  80. data/test/unit/catalog/test_version1.rb +1 -1
  81. data/test/unit/catalog/test_version2.rb +1 -32
  82. data/test/unit/catalog/test_version2_validator.rb +66 -0
  83. data/test/unit/catalog/test_volume_collection.rb +50 -0
  84. data/test/unit/plugins/groonga/test_column_create.rb +4 -1
  85. data/test/unit/plugins/groonga/test_table_create.rb +1 -1
  86. data/test/unit/test_message_matcher.rb +15 -15
  87. data/test/unit/test_watch_schema.rb +1 -1
  88. metadata +107 -94
  89. checksums.yaml +0 -7
  90. data/lib/droonga/message_pusher.rb +0 -64
@@ -11,4 +11,4 @@ rvm:
11
11
  - 2.1
12
12
  # - ruby-head
13
13
  before_install:
14
- - curl https://raw.github.com/groonga/groonga/master/data/travis/setup.sh | sh
14
+ - GROONGA_MASTER=yes curl https://raw.github.com/groonga/groonga/master/data/travis/setup.sh | sh
data/Gemfile CHANGED
@@ -41,7 +41,7 @@ droonga_client_dir = File.join(parent_dir, "droonga-client-ruby")
41
41
  if File.exist?(droonga_client_dir)
42
42
  gem "droonga-client", :path => droonga_client_dir
43
43
  else
44
- gem "droonga-client", github: "droonga/droonga-client-ruby"
44
+ gem "droonga-client", :github => "droonga/droonga-client-ruby"
45
45
  end
46
46
 
47
47
  drntest_dir = File.join(parent_dir, "drntest")
@@ -17,7 +17,7 @@
17
17
 
18
18
  Gem::Specification.new do |gem|
19
19
  gem.name = "fluent-plugin-droonga"
20
- gem.version = "1.0.0"
20
+ gem.version = "1.0.1"
21
21
  gem.authors = ["Droonga Project"]
22
22
  gem.email = ["droonga@groonga.org"]
23
23
  gem.description = "Droonga (distributed Groonga) plugin for Fluent event collector"
@@ -0,0 +1,97 @@
1
+ # Copyright (C) 2014 Droonga Project
2
+ #
3
+ # This library is free software; you can redistribute it and/or
4
+ # modify it under the terms of the GNU Lesser General Public
5
+ # License version 2.1 as published by the Free Software Foundation.
6
+ #
7
+ # This library is distributed in the hope that it will be useful,
8
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
9
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10
+ # Lesser General Public License for more details.
11
+ #
12
+ # You should have received a copy of the GNU Lesser General Public
13
+ # License along with this library; if not, write to the Free Software
14
+ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
15
+
16
+ require "digest/sha1"
17
+ require "zlib"
18
+
19
+ require "droonga/catalog/slice"
20
+
21
+ module Droonga
22
+ module Catalog
23
+ class CollectionVolume
24
+ def initialize(dataset, data)
25
+ @dataset = dataset
26
+ @data = data
27
+ compute_continuum if ratio_scaled_slicer?
28
+ end
29
+
30
+ def dimension
31
+ @data["dimension"] || "_key"
32
+ end
33
+
34
+ def slicer
35
+ @data["slicer"] || "hash"
36
+ end
37
+
38
+ def slices
39
+ @slices ||= @data["slices"].collect do |raw_slice|
40
+ Slice.new(@dataset, raw_slice)
41
+ end
42
+ end
43
+
44
+ def select_slices(range=0..-1)
45
+ slices.sort_by(&:label)[range]
46
+ end
47
+
48
+ def choose_slice(record)
49
+ return slices.first unless ratio_scaled_slicer?
50
+
51
+ key = record[dimension]
52
+ hash = Zlib.crc32(key)
53
+ min = 0
54
+ max = @continuum.size - 1
55
+ while (min < max)
56
+ index = (min + max) / 2
57
+ value, key = @continuum[index]
58
+ return key if value == hash
59
+ if value > hash
60
+ max = index
61
+ else
62
+ min = index + 1
63
+ end
64
+ end
65
+ @continuum[max][1]
66
+ end
67
+
68
+ def ratio_scaled_slicer?
69
+ slicer == "hash"
70
+ end
71
+
72
+ private
73
+ def compute_continuum
74
+ total_weight = compute_total_weight
75
+ continuum = []
76
+ n_slices = slices.size
77
+ slices.each do |slice|
78
+ weight = slice.weight
79
+ points = n_slices * 160 * weight / total_weight
80
+ points.times do |point|
81
+ hash = Digest::SHA1.hexdigest("#{@dataset.name}:#{point}")
82
+ continuum << [hash[0..7].to_i(16), slice]
83
+ end
84
+ end
85
+ @continuum = continuum.sort do |a, b|
86
+ a[0] - b[0]
87
+ end
88
+ end
89
+
90
+ def compute_total_weight
91
+ slices.reduce(0) do |result, slice|
92
+ result + slice.weight
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -14,10 +14,14 @@
14
14
  # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
15
15
 
16
16
  require "droonga/catalog/schema"
17
+ require "droonga/catalog/volume"
18
+ require "droonga/catalog/volume_collection"
17
19
 
18
20
  module Droonga
19
21
  module Catalog
20
22
  class Dataset
23
+ attr_reader :name
24
+
21
25
  def initialize(name, data)
22
26
  @name = name
23
27
  @data = data
@@ -35,7 +39,30 @@ module Droonga
35
39
  end
36
40
 
37
41
  def schema
38
- @schema ||= Droonga::Catalog::Schema.new(@name, @data["schema"])
42
+ @schema ||= Schema.new(@name, @data["schema"])
43
+ end
44
+
45
+ def plugins
46
+ @data["plugins"] || []
47
+ end
48
+
49
+ def fact
50
+ @data["fact"]
51
+ end
52
+
53
+ def n_workers
54
+ @data["nWorkers"] || 0
55
+ end
56
+
57
+ def replicas
58
+ @replicas ||= VolumeCollection.new(create_volumes(@data["replicas"]))
59
+ end
60
+
61
+ private
62
+ def create_volumes(raw_volumes)
63
+ raw_volumes.collect do |raw_volume|
64
+ Volume.create(self, raw_volume)
65
+ end
39
66
  end
40
67
  end
41
68
  end
@@ -18,6 +18,25 @@ require "droonga/error"
18
18
  module Droonga
19
19
  module Catalog
20
20
  class ValidationError < Error
21
+ class Detail
22
+ attr_reader :value_path, :message
23
+ def initialize(value_path, message)
24
+ @value_path = value_path
25
+ @message = message
26
+ end
27
+ end
28
+
29
+ attr_reader :path, :details
30
+ def initialize(path, details)
31
+ message = "validation error: <#{path}>"
32
+ details.each do |detail|
33
+ message << "\n * #{detail.value_path}: #{detail.message}"
34
+ end
35
+ super(message)
36
+ end
37
+ end
38
+
39
+ class LegacyValidationError < Error
21
40
  def initialize(message, path)
22
41
  if path
23
42
  super("[Validation Error <#{path}>]#{message}")
@@ -27,13 +46,13 @@ module Droonga
27
46
  end
28
47
  end
29
48
 
30
- class MissingRequiredParameter < ValidationError
49
+ class MissingRequiredParameter < LegacyValidationError
31
50
  def initialize(name, path)
32
51
  super("[#{name}] A required parameter is missing.", path)
33
52
  end
34
53
  end
35
54
 
36
- class MismatchedParameterType < ValidationError
55
+ class MismatchedParameterType < LegacyValidationError
37
56
  def initialize(name, expected_types, actual, path)
38
57
  expected_types = [expected_types] unless expected_types.is_a?(Array)
39
58
  message = nil
@@ -49,43 +68,43 @@ module Droonga
49
68
  end
50
69
  end
51
70
 
52
- class InvalidDate < ValidationError
71
+ class InvalidDate < LegacyValidationError
53
72
  def initialize(name, value, path)
54
73
  super("[#{name}] Invalid date string: <#{value}>", path)
55
74
  end
56
75
  end
57
76
 
58
- class NegativeNumber < ValidationError
77
+ class NegativeNumber < LegacyValidationError
59
78
  def initialize(name, actual, path)
60
79
  super("[#{name}] A positive number is expected, but <#{actual}>", path)
61
80
  end
62
81
  end
63
82
 
64
- class SmallerThanOne < ValidationError
83
+ class SmallerThanOne < LegacyValidationError
65
84
  def initialize(name, actual, path)
66
85
  super("[#{name}] A number 1 or larger is expected, but <#{actual}>", path)
67
86
  end
68
87
  end
69
88
 
70
- class FarmNotZoned < ValidationError
89
+ class FarmNotZoned < LegacyValidationError
71
90
  def initialize(name, zones, path)
72
91
  super("The farm does not appear in zones: <#{name}>, zones=<#{zones}>", path)
73
92
  end
74
93
  end
75
94
 
76
- class UnknownFarmInZones < ValidationError
95
+ class UnknownFarmInZones < LegacyValidationError
77
96
  def initialize(name, zones, path)
78
97
  super("The farm is unknown: <#{name}>, zones=<#{zones}>", path)
79
98
  end
80
99
  end
81
100
 
82
- class UnknownFarmForPartition < ValidationError
101
+ class UnknownFarmForPartition < LegacyValidationError
83
102
  def initialize(name, slice, path)
84
103
  super("The farm is unknown: <{#name}>, slice=<#{slice}>", path)
85
104
  end
86
105
  end
87
106
 
88
- class UnsupportedValue < ValidationError
107
+ class UnsupportedValue < LegacyValidationError
89
108
  def initialize(name, value, path)
90
109
  super("[#{name}] Not supported value: <#{value}>", path)
91
110
  end
@@ -18,6 +18,22 @@ require "tsort"
18
18
  module Droonga
19
19
  module Catalog
20
20
  class Schema
21
+ class ColumnVectorOptions
22
+ def initialize(data)
23
+ @data = data
24
+ end
25
+
26
+ def weight
27
+ @data["weight"]
28
+ end
29
+
30
+ def flags
31
+ flags = []
32
+ flags << "WITH_WEIGHT" if weight
33
+ flags
34
+ end
35
+ end
36
+
21
37
  class ColumnIndexOptions
22
38
  def initialize(data)
23
39
  @data = data
@@ -49,11 +65,12 @@ module Droonga
49
65
  end
50
66
 
51
67
  class Column
52
- attr_reader :table, :name, :data, :index_options
68
+ attr_reader :table, :name, :data, :vector_options, :index_options
53
69
  def initialize(table, name, data)
54
70
  @table = table
55
71
  @name = name
56
72
  @data = data
73
+ @vector_options = ColumnVectorOptions.new(vector_options_data)
57
74
  @index_options = ColumnIndexOptions.new(index_options_data)
58
75
  end
59
76
 
@@ -81,7 +98,7 @@ module Droonga
81
98
  end
82
99
 
83
100
  def flags
84
- [type_flag] + index_options.flags
101
+ [type_flag] + vector_options.flags + index_options.flags
85
102
  end
86
103
 
87
104
  def value_type
@@ -112,6 +129,10 @@ module Droonga
112
129
  end
113
130
 
114
131
  private
132
+ def vector_options_data
133
+ @data["vectorOptions"] || {}
134
+ end
135
+
115
136
  def index_options_data
116
137
  @data["indexOptions"] || {}
117
138
  end
@@ -0,0 +1,28 @@
1
+ # Copyright (C) 2014 Droonga Project
2
+ #
3
+ # This library is free software; you can redistribute it and/or
4
+ # modify it under the terms of the GNU Lesser General Public
5
+ # License version 2.1 as published by the Free Software Foundation.
6
+ #
7
+ # This library is distributed in the hope that it will be useful,
8
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
9
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10
+ # Lesser General Public License for more details.
11
+ #
12
+ # You should have received a copy of the GNU Lesser General Public
13
+ # License along with this library; if not, write to the Free Software
14
+ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
15
+
16
+ module Droonga
17
+ module Catalog
18
+ class SingleVolume
19
+ def initialize(data)
20
+ @data = data
21
+ end
22
+
23
+ def address
24
+ @data["address"]
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,43 @@
1
+ # Copyright (C) 2014 Droonga Project
2
+ #
3
+ # This library is free software; you can redistribute it and/or
4
+ # modify it under the terms of the GNU Lesser General Public
5
+ # License version 2.1 as published by the Free Software Foundation.
6
+ #
7
+ # This library is distributed in the hope that it will be useful,
8
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
9
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10
+ # Lesser General Public License for more details.
11
+ #
12
+ # You should have received a copy of the GNU Lesser General Public
13
+ # License along with this library; if not, write to the Free Software
14
+ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
15
+
16
+ require "droonga/catalog/volume"
17
+
18
+ module Droonga
19
+ module Catalog
20
+ class Slice
21
+ def initialize(dataset, data)
22
+ @dataset = dataset
23
+ @data = data
24
+ end
25
+
26
+ def weight
27
+ @data["weight"] || 1
28
+ end
29
+
30
+ def label
31
+ @data["label"]
32
+ end
33
+
34
+ def boundary
35
+ @data["boundary"]
36
+ end
37
+
38
+ def volume
39
+ @volume ||= Volume.create(@dataset, @data["volume"])
40
+ end
41
+ end
42
+ end
43
+ end
@@ -75,7 +75,7 @@ module Droonga
75
75
  select_range_and_replicas(partition, args, routes)
76
76
  end
77
77
  when "scatter"
78
- name = get_partition(dataset, args["key"])
78
+ name = get_partition(dataset, args["record"]["_key"])
79
79
  partition = dataset["ring"][name]
80
80
  select_range_and_replicas(partition, args, routes)
81
81
  end
@@ -166,7 +166,7 @@ module Droonga
166
166
  def do_validation(&block)
167
167
  begin
168
168
  yield
169
- rescue ValidationError => error
169
+ rescue LegacyValidationError => error
170
170
  @errors << error
171
171
  end
172
172
  end
@@ -413,7 +413,7 @@ module Droonga
413
413
  if directory_name.nil? or directory_name.empty?
414
414
  message = "\"#{partition}\" has no database name. " +
415
415
  "You mus specify a database name for \"#{name}\"."
416
- raise ValidationError.new(message, @path)
416
+ raise LegacyValidationError.new(message, @path)
417
417
  end
418
418
  end
419
419
  end
@@ -15,12 +15,14 @@
15
15
 
16
16
  require "droonga/catalog/base"
17
17
  require "droonga/catalog/dataset"
18
+ require "droonga/catalog/version2_validator"
18
19
 
19
20
  module Droonga
20
21
  module Catalog
21
22
  class Version2 < Base
22
23
  def initialize(data, path)
23
24
  super
25
+ validate
24
26
  prepare_data
25
27
  end
26
28
 
@@ -33,11 +35,11 @@ module Droonga
33
35
  pattern = Regexp.new("^#{name}\.")
34
36
  results = {}
35
37
  @datasets.each do |dataset_name, dataset|
36
- n_workers = dataset["nWorkers"]
37
- plugins = dataset["plugins"]
38
- dataset["replicas"].each do |replica|
39
- replica["slices"].each do |slice|
40
- volume_address = slice["volume"]["address"]
38
+ n_workers = dataset.n_workers
39
+ plugins = dataset.plugins
40
+ dataset.replicas.each do |volume|
41
+ volume.slices.each do |slice|
42
+ volume_address = slice.volume.address
41
43
  if pattern =~ volume_address
42
44
  path = File.join([device, $POSTMATCH, "db"])
43
45
  path = File.expand_path(path, base_path)
@@ -60,18 +62,18 @@ module Droonga
60
62
  dataset = dataset(name)
61
63
  case args["type"]
62
64
  when "broadcast"
63
- replicas = select_replicas(dataset["replicas"], args["replica"])
64
- replicas.each do |replica|
65
- slices = select_slices(replica)
65
+ volumes = dataset.replicas.select(args["replica"].to_sym)
66
+ volumes.each do |volume|
67
+ slices = volume.select_slices
66
68
  slices.each do |slice|
67
- routes << slice["volume"]["address"]
69
+ routes << slice.volume.address
68
70
  end
69
71
  end
70
72
  when "scatter"
71
- replicas = select_replicas(dataset["replicas"], args["replica"])
72
- replicas.each do |replica|
73
- slice = select_slice(replica, args["key"])
74
- routes << slice["volume"]["address"]
73
+ volumes = dataset.replicas.select(args["replica"].to_sym)
74
+ volumes.each do |volume|
75
+ slice = volume.choose_slice(args["record"])
76
+ routes << slice.volume.address
75
77
  end
76
78
  end
77
79
  routes
@@ -79,82 +81,16 @@ module Droonga
79
81
 
80
82
  private
81
83
  def validate
82
- # TODO: Implement me.
84
+ validator = Version2Validator.new(@data, @path)
85
+ validator.validate
83
86
  end
84
87
 
85
88
  def prepare_data
86
89
  @datasets = {}
87
90
  @data["datasets"].each do |name, dataset|
88
- replicas = dataset["replicas"]
89
- replicas.each do |replica|
90
- total_weight = compute_total_weight(replica)
91
- continuum = []
92
- slices = replica["slices"]
93
- n_slices = slices.size
94
- slices.each do |slice|
95
- weight = slice["weight"] || default_weight
96
- points = n_slices * 160 * weight / total_weight
97
- points.times do |point|
98
- hash = Digest::SHA1.hexdigest("#{name}:#{point}")
99
- continuum << [hash[0..7].to_i(16), slice]
100
- end
101
- end
102
- replica["continuum"] = continuum.sort do |a, b|
103
- a[0] - b[0]
104
- end
105
- end
106
91
  @datasets[name] = Dataset.new(name, dataset)
107
92
  end
108
93
  end
109
-
110
- def default_weight
111
- 1
112
- end
113
-
114
- def compute_total_weight(replica)
115
- slices = replica["slices"]
116
- slices.reduce(0) do |result, slice|
117
- result + (slice["weight"] || default_weight)
118
- end
119
- end
120
-
121
- def select_replicas(replicas, how)
122
- case how
123
- when "top"
124
- [replicas.first]
125
- when "random"
126
- [replicas.sample]
127
- when "all"
128
- replicas
129
- end
130
- end
131
-
132
- def select_slices(replica, range=0..-1)
133
- sorted_slices = replica["slices"].sort_by do |slice|
134
- slice["label"]
135
- end
136
- sorted_slices[range]
137
- end
138
-
139
- def select_slice(replica, key)
140
- continuum = replica["continuum"]
141
- return replica["slices"].first unless continuum
142
-
143
- hash = Zlib.crc32(key)
144
- min = 0
145
- max = continuum.size - 1
146
- while (min < max) do
147
- index = (min + max) / 2
148
- value, key = continuum[index]
149
- return key if value == hash
150
- if value > hash
151
- max = index
152
- else
153
- min = index + 1
154
- end
155
- end
156
- continuum[max][1]
157
- end
158
94
  end
159
95
  end
160
96
  end