fluent-plugin-droonga 1.0.0 → 1.0.1

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 (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