droonga-engine 1.0.9 → 1.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 (195) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -0
  3. data/benchmark/timer-watcher/benchmark.rb +44 -0
  4. data/bin/droonga-engine-absorb-data +246 -187
  5. data/bin/droonga-engine-catalog-generate +12 -12
  6. data/bin/droonga-engine-catalog-modify +4 -4
  7. data/bin/droonga-engine-join +352 -171
  8. data/bin/droonga-engine-set-role +54 -0
  9. data/bin/droonga-engine-unjoin +107 -112
  10. data/droonga-engine.gemspec +3 -3
  11. data/install.sh +55 -36
  12. data/install/centos/functions.sh +2 -2
  13. data/install/debian/functions.sh +2 -2
  14. data/lib/droonga/address.rb +26 -24
  15. data/lib/droonga/buffered_tcp_socket.rb +65 -10
  16. data/lib/droonga/catalog/base.rb +9 -6
  17. data/lib/droonga/catalog/dataset.rb +17 -41
  18. data/lib/droonga/catalog/fetcher.rb +64 -0
  19. data/lib/droonga/catalog/generator.rb +245 -0
  20. data/lib/droonga/catalog/loader.rb +66 -0
  21. data/lib/droonga/{catalog_modifier.rb → catalog/modifier.rb} +11 -18
  22. data/lib/droonga/catalog/replicas_volume.rb +123 -0
  23. data/lib/droonga/catalog/schema.rb +37 -37
  24. data/lib/droonga/catalog/single_volume.rb +11 -3
  25. data/lib/droonga/catalog/slice.rb +10 -6
  26. data/lib/droonga/catalog/{collection_volume.rb → slices_volume.rb} +47 -11
  27. data/lib/droonga/catalog/version1.rb +47 -19
  28. data/lib/droonga/catalog/version2.rb +11 -10
  29. data/lib/droonga/catalog/version2_validator.rb +4 -4
  30. data/lib/droonga/catalog/volume.rb +17 -5
  31. data/lib/droonga/changable.rb +25 -0
  32. data/lib/droonga/cluster.rb +237 -0
  33. data/lib/droonga/collector_runner.rb +4 -0
  34. data/lib/droonga/collectors.rb +2 -1
  35. data/lib/droonga/collectors/recursive_sum.rb +26 -0
  36. data/lib/droonga/command/droonga_engine.rb +404 -127
  37. data/lib/droonga/command/droonga_engine_service.rb +47 -11
  38. data/lib/droonga/command/droonga_engine_worker.rb +21 -1
  39. data/lib/droonga/command/remote_command_base.rb +78 -0
  40. data/lib/droonga/command/serf_event_handler.rb +29 -20
  41. data/lib/droonga/data_absorber_client.rb +222 -0
  42. data/lib/droonga/database_scanner.rb +106 -0
  43. data/lib/droonga/{live_nodes_list_loader.rb → deferrable.rb} +11 -24
  44. data/lib/droonga/differ.rb +58 -0
  45. data/lib/droonga/dispatcher.rb +155 -32
  46. data/lib/droonga/distributed_command_planner.rb +9 -11
  47. data/lib/droonga/engine.rb +83 -78
  48. data/lib/droonga/engine/version.rb +1 -1
  49. data/lib/droonga/engine_node.rb +301 -0
  50. data/lib/droonga/engine_state.rb +62 -40
  51. data/lib/droonga/farm.rb +44 -5
  52. data/lib/droonga/file_observer.rb +16 -12
  53. data/lib/droonga/fluent_message_receiver.rb +98 -29
  54. data/lib/droonga/fluent_message_sender.rb +30 -23
  55. data/lib/droonga/forward_buffer.rb +160 -0
  56. data/lib/droonga/forwarder.rb +73 -40
  57. data/lib/droonga/handler.rb +7 -6
  58. data/lib/droonga/handler_messenger.rb +15 -6
  59. data/lib/droonga/handler_runner.rb +6 -1
  60. data/lib/droonga/internal_fluent_message_receiver.rb +28 -8
  61. data/lib/droonga/job_pusher.rb +10 -7
  62. data/lib/droonga/job_receiver.rb +6 -4
  63. data/lib/droonga/logger.rb +7 -1
  64. data/lib/droonga/node_name.rb +90 -0
  65. data/lib/droonga/node_role.rb +72 -0
  66. data/lib/droonga/path.rb +34 -9
  67. data/lib/droonga/planner.rb +73 -7
  68. data/lib/droonga/plugin/async_command.rb +154 -0
  69. data/lib/droonga/plugins/catalog.rb +1 -0
  70. data/lib/droonga/plugins/crud.rb +22 -6
  71. data/lib/droonga/plugins/dump.rb +66 -135
  72. data/lib/droonga/plugins/groonga/delete.rb +13 -0
  73. data/lib/droonga/plugins/search/distributed_search_planner.rb +4 -4
  74. data/lib/droonga/plugins/system.rb +5 -26
  75. data/lib/droonga/plugins/system/absorb_data.rb +405 -0
  76. data/lib/droonga/plugins/system/statistics.rb +71 -0
  77. data/lib/droonga/plugins/system/status.rb +53 -0
  78. data/lib/droonga/process_control_protocol.rb +3 -1
  79. data/lib/droonga/process_supervisor.rb +32 -15
  80. data/lib/droonga/reducer.rb +69 -0
  81. data/lib/droonga/safe_file_writer.rb +1 -1
  82. data/lib/droonga/serf.rb +207 -276
  83. data/lib/droonga/serf/agent.rb +228 -0
  84. data/lib/droonga/serf/command.rb +94 -0
  85. data/lib/droonga/serf/downloader.rb +120 -0
  86. data/lib/droonga/serf/remote_command.rb +348 -0
  87. data/lib/droonga/serf/tag.rb +56 -0
  88. data/lib/droonga/service_installation.rb +2 -2
  89. data/lib/droonga/session.rb +49 -1
  90. data/lib/droonga/single_step.rb +6 -11
  91. data/lib/droonga/single_step_definition.rb +32 -1
  92. data/lib/droonga/slice.rb +14 -9
  93. data/lib/droonga/supervisor.rb +27 -20
  94. data/lib/droonga/test/stub_handler_messenger.rb +2 -1
  95. data/lib/droonga/timestamp.rb +69 -0
  96. data/lib/droonga/worker_process_agent.rb +33 -15
  97. data/sample/cluster-state.json +8 -0
  98. data/sample/cluster/Rakefile +30 -6
  99. data/test/command/fixture/integer-key-table.jsons +11 -0
  100. data/test/command/fixture/string-key-table.jsons +11 -0
  101. data/test/command/run-test.rb +4 -0
  102. data/test/command/suite/add/error/invalid-integer.expected +3 -3
  103. data/test/command/suite/add/error/invalid-time.expected +3 -3
  104. data/test/command/suite/add/{minimum.expected → key-integer.expected} +0 -0
  105. data/test/command/suite/add/{minimum.test → key-integer.test} +0 -0
  106. data/test/command/suite/add/key-string.expected +6 -0
  107. data/test/command/suite/add/key-string.test +9 -0
  108. data/test/command/suite/add/mismatched-key-type/acceptable/integer-for-string.expected +6 -0
  109. data/test/command/suite/add/mismatched-key-type/acceptable/integer-for-string.test +9 -0
  110. data/test/command/suite/add/mismatched-key-type/acceptable/string-for-integer.expected +6 -0
  111. data/test/command/suite/add/mismatched-key-type/acceptable/string-for-integer.test +9 -0
  112. data/test/command/suite/add/without-values.expected +6 -0
  113. data/test/command/suite/add/without-values.test +11 -0
  114. data/test/command/suite/dump/column/index.expected +33 -1
  115. data/test/command/suite/dump/column/index.test +1 -0
  116. data/test/command/suite/dump/column/scalar.expected +29 -1
  117. data/test/command/suite/dump/column/scalar.test +1 -0
  118. data/test/command/suite/dump/column/vector.expected +29 -1
  119. data/test/command/suite/dump/column/vector.test +1 -0
  120. data/test/command/suite/dump/record/scalar.catalog.json +12 -0
  121. data/test/command/suite/dump/record/scalar.expected +84 -0
  122. data/test/command/suite/dump/record/scalar.test +16 -0
  123. data/test/command/suite/dump/record/vector/reference.expected +83 -1
  124. data/test/command/suite/dump/record/vector/reference.test +1 -0
  125. data/test/command/suite/dump/table/array.expected +27 -1
  126. data/test/command/suite/dump/table/array.test +1 -0
  127. data/test/command/suite/dump/table/double_array_trie.expected +27 -1
  128. data/test/command/suite/dump/table/double_array_trie.test +1 -0
  129. data/test/command/suite/dump/table/hash.expected +27 -1
  130. data/test/command/suite/dump/table/hash.test +1 -0
  131. data/test/command/suite/dump/table/patricia_trie.expected +27 -1
  132. data/test/command/suite/dump/table/patricia_trie.test +1 -0
  133. data/test/command/suite/groonga/delete/{success.expected → key-integer.expected} +0 -0
  134. data/test/command/suite/groonga/delete/key-integer.test +17 -0
  135. data/test/command/suite/groonga/delete/key-string.expected +19 -0
  136. data/test/command/suite/groonga/delete/{success.test → key-string.test} +4 -6
  137. data/test/command/suite/groonga/delete/mismatched-type-key/acceptable/integer-for-string.expected +19 -0
  138. data/test/command/suite/groonga/delete/mismatched-type-key/acceptable/integer-for-string.test +17 -0
  139. data/test/command/suite/groonga/delete/mismatched-type-key/acceptable/string-for-integer.expected +19 -0
  140. data/test/command/suite/groonga/delete/mismatched-type-key/acceptable/string-for-integer.test +17 -0
  141. data/test/command/suite/message/error/missing-dataset.test +1 -0
  142. data/test/command/suite/system/absorb-data/records.catalog.json +58 -0
  143. data/test/command/suite/system/absorb-data/records.expected +32 -0
  144. data/test/command/suite/system/absorb-data/records.test +24 -0
  145. data/test/command/suite/system/statistics/object/count/empty.expected +11 -0
  146. data/test/command/suite/system/statistics/object/count/empty.test +12 -0
  147. data/test/command/suite/system/statistics/object/count/per-volume/empty.catalog.json +36 -0
  148. data/test/command/suite/system/statistics/object/count/per-volume/empty.expected +19 -0
  149. data/test/command/suite/system/statistics/object/count/per-volume/empty.test +12 -0
  150. data/test/command/suite/system/statistics/object/count/per-volume/record.catalog.json +40 -0
  151. data/test/command/suite/system/statistics/object/count/per-volume/record.expected +19 -0
  152. data/test/command/suite/system/statistics/object/count/per-volume/record.test +23 -0
  153. data/test/command/suite/system/statistics/object/count/per-volume/schema.catalog.json +40 -0
  154. data/test/command/suite/system/statistics/object/count/per-volume/schema.expected +19 -0
  155. data/test/command/suite/system/statistics/object/count/per-volume/schema.test +13 -0
  156. data/test/command/suite/system/statistics/object/count/record.catalog.json +12 -0
  157. data/test/command/suite/system/statistics/object/count/record.expected +11 -0
  158. data/test/command/suite/system/statistics/object/count/record.test +23 -0
  159. data/test/command/suite/system/statistics/object/count/schema.catalog.json +12 -0
  160. data/test/command/suite/system/statistics/object/count/schema.expected +11 -0
  161. data/test/command/suite/system/statistics/object/count/schema.test +13 -0
  162. data/test/command/suite/system/status.expected +3 -2
  163. data/test/unit/catalog/test_dataset.rb +4 -1
  164. data/test/unit/{test_catalog_generator.rb → catalog/test_generator.rb} +2 -2
  165. data/test/unit/catalog/test_replicas_volume.rb +79 -0
  166. data/test/unit/catalog/test_single_volume.rb +2 -2
  167. data/test/unit/catalog/test_slice.rb +33 -1
  168. data/test/unit/catalog/{test_collection_volume.rb → test_slices_volume.rb} +72 -11
  169. data/test/unit/catalog/test_version2.rb +3 -0
  170. data/test/unit/helper/distributed_search_planner_helper.rb +2 -2
  171. data/test/unit/plugins/catalog/test_fetch.rb +4 -4
  172. data/test/unit/plugins/crud/test_add.rb +44 -4
  173. data/test/unit/plugins/groonga/test_column_create.rb +4 -4
  174. data/test/unit/plugins/groonga/test_column_list.rb +4 -4
  175. data/test/unit/plugins/groonga/test_column_remove.rb +4 -4
  176. data/test/unit/plugins/groonga/test_column_rename.rb +4 -4
  177. data/test/unit/plugins/groonga/test_delete.rb +73 -10
  178. data/test/unit/plugins/groonga/test_table_create.rb +4 -4
  179. data/test/unit/plugins/groonga/test_table_list.rb +4 -4
  180. data/test/unit/plugins/groonga/test_table_remove.rb +4 -4
  181. data/test/unit/plugins/search/test_handler.rb +4 -4
  182. data/test/unit/plugins/search/test_planner.rb +4 -2
  183. data/test/unit/plugins/system/test_status.rb +31 -15
  184. data/test/unit/plugins/test_watch.rb +16 -16
  185. data/test/unit/test_address.rb +4 -4
  186. metadata +134 -35
  187. data/lib/droonga/catalog/volume_collection.rb +0 -79
  188. data/lib/droonga/catalog_fetcher.rb +0 -53
  189. data/lib/droonga/catalog_generator.rb +0 -243
  190. data/lib/droonga/catalog_loader.rb +0 -56
  191. data/lib/droonga/command/remote.rb +0 -404
  192. data/lib/droonga/data_absorber.rb +0 -264
  193. data/lib/droonga/node_status.rb +0 -71
  194. data/lib/droonga/serf_downloader.rb +0 -115
  195. data/test/unit/catalog/test_volume_collection.rb +0 -78
@@ -69,10 +69,23 @@ module Droonga
69
69
  end
70
70
 
71
71
  def delete_record_by_key(table, key)
72
+ key = normalize_record_key(key, table)
72
73
  record = table[key]
73
74
  record.delete unless record.nil?
74
75
  end
75
76
 
77
+ def normalize_record_key(key, table)
78
+ case table.domain.name
79
+ when "Int8", "UInt8",
80
+ "Int16", "UInt16",
81
+ "Int32", "UInt32",
82
+ "Int64", "UInt64"
83
+ key.to_i
84
+ else
85
+ key.to_s
86
+ end
87
+ end
88
+
76
89
  def delete_record_by_id(table, id)
77
90
  record = table[id.to_i]
78
91
  record.delete if record and record.valid_id?
@@ -210,13 +210,13 @@ module Droonga
210
210
  end
211
211
 
212
212
  def final_offset
213
- return @original_output_offset if @dataset.single_slice?
213
+ return @original_output_offset unless @dataset.sliced?
214
214
 
215
215
  @original_sort_offset + @original_output_offset
216
216
  end
217
217
 
218
218
  def final_limit
219
- return @original_output_limit if @dataset.single_slice?
219
+ return @original_output_limit unless @dataset.sliced?
220
220
 
221
221
  if @original_sort_limit == UNLIMITED and
222
222
  @original_output_limit == UNLIMITED
@@ -251,7 +251,7 @@ module Droonga
251
251
  "type" => "sum",
252
252
  }
253
253
  if unifiable?
254
- unless @dataset.single_slice?
254
+ if @dataset.sliced?
255
255
  if @query["sortBy"].is_a?(Hash)
256
256
  @query["sortBy"]["limit"] = UNLIMITED
257
257
  end
@@ -261,7 +261,7 @@ module Droonga
261
261
  "target" => "records",
262
262
  }
263
263
  unless @output["elements"].include?("records")
264
- @records_limit = UNLIMITED unless @dataset.single_slice?
264
+ @records_limit = UNLIMITED if @dataset.sliced?
265
265
  @output["elements"] << "records"
266
266
  @output["attributes"] ||= ["_key"]
267
267
  @output_records = false
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2013-2014 Droonga Project
1
+ # Copyright (C) 2013-2015 Droonga Project
2
2
  #
3
3
  # This library is free software; you can redistribute it and/or
4
4
  # modify it under the terms of the GNU Lesser General Public
@@ -20,31 +20,10 @@ module Droonga
20
20
  module System
21
21
  extend Plugin
22
22
  register("system")
23
-
24
- class StatusHandler < Droonga::Handler
25
- action.synchronous = true
26
-
27
- def handle(message)
28
- engine_state = @messenger.engine_state
29
- live_nodes = engine_state.live_nodes
30
- nodes = {}
31
- engine_state.all_nodes.collect do |identifier|
32
- nodes[identifier] = {
33
- "live" => live_nodes.include?(identifier),
34
- }
35
- end
36
-
37
- {
38
- "nodes" => nodes,
39
- }
40
- end
41
- end
42
-
43
- define_single_step do |step|
44
- step.name = "system.status"
45
- step.handler = StatusHandler
46
- step.collector = Collectors::Or
47
- end
48
23
  end
49
24
  end
50
25
  end
26
+
27
+ require "droonga/plugins/system/status"
28
+ require "droonga/plugins/system/statistics"
29
+ require "droonga/plugins/system/absorb_data"
@@ -0,0 +1,405 @@
1
+ # Copyright (C) 2015 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
15
+
16
+ require "fiber"
17
+
18
+ require "droonga/plugin"
19
+ require "droonga/plugin/async_command"
20
+ require "droonga/catalog/dataset"
21
+ require "droonga/serf"
22
+ require "droonga/node_name"
23
+
24
+ require "drndump/dump_client"
25
+
26
+ module Droonga
27
+ module Plugins
28
+ module System
29
+ class AbsorbDataHandler < AsyncCommand::Handler
30
+ DEFAULT_MESSAGES_PER_SECOND = 100
31
+ DEFAULT_PROGRESS_INTERVAL_SECONDS = 3
32
+ MIN_PROGRESS_INTERVAL_SECONDS = 1
33
+
34
+ class MissingHostParameter < BadRequest
35
+ def initialize
36
+ super("\"host\" must be specified.")
37
+ end
38
+ end
39
+
40
+ class DataAbsorber < AsyncCommand::AsyncHandler
41
+ class EmptyResponse < StandardError
42
+ end
43
+
44
+ class EmptyBody < StandardError
45
+ end
46
+
47
+ def start
48
+ logger.trace("start: start")
49
+ on_start
50
+
51
+ count_total_n_objects do |n_objects|
52
+ @n_initial_objects = n_objects
53
+ logger.info("initially #{n_objects} objects exist in the dataset")
54
+ do_absorb do
55
+ ensure_completely_restored do
56
+ on_finish
57
+ logger.trace("start: finish")
58
+ end
59
+ end
60
+ end
61
+
62
+ logger.trace("start: done")
63
+ end
64
+
65
+ private
66
+ def prefix
67
+ "system.absorb-data"
68
+ end
69
+
70
+ def error_name
71
+ "AbsorbFailure"
72
+ end
73
+
74
+ def error_message
75
+ "failed to absorb data"
76
+ end
77
+
78
+ def do_absorb(&block)
79
+ logger.trace("do_absorb: start")
80
+ @dumper_error_message = nil
81
+
82
+ @n_restored_objects = 0
83
+ @measure_start_time = Time.now
84
+ @previous_measure_time = @measure_start_time
85
+ @previous_n_restored_objects = 0.0
86
+ @previous_report_time = Time.now
87
+
88
+ @dumper = create_dumper
89
+ @dumper.on_finish = lambda do
90
+ yield
91
+ end
92
+ begin
93
+ logger.info("starting to absorb the source dataset")
94
+ @dumper_error_message = @dumper.run(dump_options) do |message|
95
+ begin
96
+ message["dataset"] = current_dataset
97
+ message["targetRole"] = target_role if target_role
98
+ message["xSender"] = "system.absorb-data"
99
+ @messenger.forward(message,
100
+ "to" => my_node_name,
101
+ "type" => message["type"])
102
+ try_report_progress(:count_restored_objects => true)
103
+ rescue Exception => exception
104
+ @dumper_error_message = exception.to_s
105
+ logger.exception("failed to process progress",
106
+ exception)
107
+ on_finish
108
+ end
109
+ end
110
+ rescue Exception => exception
111
+ @dumper_error_message = exception.to_s
112
+ logger.exception("failed to start dump",
113
+ exception)
114
+ end
115
+
116
+ on_finish if @dumper_error_message
117
+ logger.trace("do_absorb: done")
118
+ end
119
+
120
+ def create_dumper
121
+ dumper = Drndump::DumpClient.new(dumper_params)
122
+ dumper.on_progress = lambda do |message|
123
+ logger.trace("dump progress",
124
+ :message => message)
125
+ end
126
+ dumper.on_error = lambda do |error|
127
+ if error.is_a?(Exception)
128
+ logger.exception("unexpected exception while dump",
129
+ error)
130
+ else
131
+ logger.error("unexpected error while dump",
132
+ :error => error)
133
+ end
134
+ end
135
+ dumper
136
+ end
137
+
138
+ def dumper_params
139
+ {
140
+ :host => source_host,
141
+ :port => source_port,
142
+ :tag => source_tag,
143
+ :dataset => source_dataset,
144
+
145
+ :receiver_host => myself.host,
146
+ :receiver_port => 0,
147
+ }
148
+ end
149
+
150
+ def dump_options
151
+ {
152
+ :backend => :coolio,
153
+ :loop => @loop,
154
+ :messages_per_second => messages_per_second,
155
+ }
156
+ end
157
+
158
+ def ensure_completely_restored(&block)
159
+ runner = Fiber.new do
160
+ completely_restored = false
161
+ n_expected_objects = @dumper.n_forecasted_messages
162
+ while not completely_restored
163
+ count_total_n_objects do |count|
164
+ @n_restored_objects = count - @n_initial_objects
165
+ completely_restored ||= @n_restored_objects == n_expected_objects
166
+ try_report_progress
167
+ end
168
+ Fiber.yield
169
+ end
170
+ count_client.close
171
+ yield
172
+ end
173
+
174
+ timer = Coolio::TimerWatcher.new(DEFAULT_PROGRESS_INTERVAL_SECONDS, true)
175
+ timer.on_timer do
176
+ if runner.alive?
177
+ begin
178
+ runner.resume
179
+ rescue
180
+ timer.detach
181
+ logger.trace("start: timer detached on unexpected exception",
182
+ :watcher => timer)
183
+ logger.exception(error_message, $!)
184
+ error(error_name, error_message)
185
+ end
186
+ else
187
+ timer.detach
188
+ logger.trace("start: timer detached on unexpected exception",
189
+ :watcher => timer)
190
+ end
191
+ end
192
+ @loop.attach(timer)
193
+ end
194
+
195
+ def on_finish
196
+ begin
197
+ if @dumper_error_message
198
+ error(error_name, @dumper_error_message)
199
+ else
200
+ report_progress
201
+ end
202
+ rescue Exception => exception
203
+ @dumper_error_message = exception.to_s
204
+ logger.exception("failed to finish dump",
205
+ exception)
206
+ error(error_name, @dumper_error_message)
207
+ end
208
+ super
209
+ end
210
+
211
+ #TODO: Currently we are counting/comparing the number of
212
+ # physical objects, but it will be different from
213
+ # the source dataset if the destination is differently
214
+ # sliced dataset. So, we should count/compare the
215
+ # number of logical objects in the future after
216
+ # supporting management of sliced datasets.
217
+ def count_total_n_objects(&block)
218
+ count_message = {
219
+ "type" => "system.statistics.object.count",
220
+ "dataset" => current_dataset,
221
+ "body" => {
222
+ "output" => ["total"],
223
+ },
224
+ }
225
+ count_client.request(count_message) do |response|
226
+ yield(response["body"]["total"])
227
+ end
228
+ end
229
+
230
+ def count_client
231
+ @count_client ||= Droonga::Client.new(count_client_options)
232
+ end
233
+
234
+ def count_client_options
235
+ {
236
+ :host => myself.host,
237
+ :port => myself.port,
238
+ :tag => myself.tag,
239
+ :protocol => :droonga,
240
+ :backend => :coolio,
241
+ :loop => @loop,
242
+ }
243
+ end
244
+
245
+ def try_report_progress(options={})
246
+ now = Time.now
247
+ elapsed_seconds = (now - @previous_report_time).to_i
248
+ if elapsed_seconds >= progress_interval_seconds
249
+ if options[:count_restored_objects]
250
+ @previous_report_time = now
251
+ count_total_n_objects do |count|
252
+ @previous_report_time = Time.now
253
+ @n_restored_objects = count - @n_initial_objects
254
+ report_progress
255
+ end
256
+ else
257
+ @previous_report_time = now
258
+ report_progress
259
+ end
260
+ end
261
+ end
262
+
263
+ def report_progress
264
+ message = "#{progress_percentage}% done " +
265
+ "(maybe #{formatted_remaining_time} remaining)"
266
+ forward("#{prefix}.progress",
267
+ "nProcessedMessages" => @dumper.n_received_messages,
268
+ "nRestoredObjects" => @n_restored_objects,
269
+ "percentage" => progress_percentage,
270
+ "message" => message)
271
+ end
272
+
273
+ MIN_REPORTED_THROUGHPUT = 0.01
274
+
275
+ def recent_throughput
276
+ now = Time.now
277
+ n_objects = @n_restored_objects - @previous_n_restored_objects
278
+
279
+ if now - @previous_measure_time < 1
280
+ now = @previous_measure_time
281
+ n_objects = @previous_n_restored_objects
282
+ else
283
+ @previous_measure_time = now
284
+ @previous_n_restored_objects = n_objects.to_f
285
+ end
286
+
287
+ if now == @measure_start_time
288
+ actual_throughput = 0
289
+ else
290
+ elapsed_seconds = now - @measure_start_time
291
+ actual_throughput = n_objects / elapsed_seconds
292
+ end
293
+
294
+ [actual_throughput, MIN_REPORTED_THROUGHPUT].max
295
+ end
296
+
297
+ def n_remaining_objects
298
+ [@dumper.n_forecasted_messages - @n_restored_objects, 0].max
299
+ end
300
+
301
+ def remaining_seconds
302
+ throughput = [recent_throughput, messages_per_second].min
303
+ remaining_seconds = n_remaining_objects.to_f / throughput
304
+ @dumper.remaining_seconds + remaining_seconds
305
+ end
306
+
307
+ ONE_MINUTE_IN_SECONDS = 60
308
+ ONE_HOUR_IN_SECONDS = ONE_MINUTE_IN_SECONDS * 60
309
+
310
+ def formatted_remaining_time
311
+ seconds = remaining_seconds
312
+ hours = (seconds / ONE_HOUR_IN_SECONDS).floor
313
+ seconds -= hours * ONE_HOUR_IN_SECONDS
314
+ minutes = (seconds / ONE_MINUTE_IN_SECONDS).floor
315
+ seconds -= minutes * ONE_MINUTE_IN_SECONDS
316
+ sprintf("%02i:%02i:%02i", hours, minutes, seconds)
317
+ end
318
+
319
+ def progress_percentage
320
+ return 0 if @dumper.n_forecasted_messages.zero?
321
+ processed = @dumper.n_received_messages + @n_restored_objects
322
+ expected = @dumper.n_forecasted_messages * 2
323
+ progress = processed.to_f / expected
324
+ [(progress * 100).to_i, 100].min
325
+ end
326
+
327
+ def myself
328
+ @myself ||= NodeName.parse(my_node_name)
329
+ end
330
+
331
+ def my_node_name
332
+ ENV["DROONGA_ENGINE_NAME"]
333
+ end
334
+
335
+ def current_dataset
336
+ @request.dataset
337
+ end
338
+
339
+ def target_role
340
+ @request.request["targetRole"]
341
+ end
342
+
343
+ def prepare_progress_interval_seconds
344
+ interval_seconds = @request.request["progressIntervalSeconds"] ||
345
+ DEFAULT_PROGRESS_INTERVAL_SECONDS
346
+ interval_seconds = interval_seconds.to_i
347
+ [interval_seconds, MIN_PROGRESS_INTERVAL_SECONDS].max
348
+ end
349
+
350
+ def progress_interval_seconds
351
+ @progress_interval_seconds ||= prepare_progress_interval_seconds
352
+ end
353
+
354
+ def source_host
355
+ @source_host ||= @request.request["host"]
356
+ end
357
+
358
+ def source_port
359
+ @source_port ||= @request.request["port"] ||
360
+ NodeName::DEFAULT_PORT
361
+ end
362
+
363
+ def source_tag
364
+ @source_tag ||= @request.request["tag"] ||
365
+ NodeName::DEFAULT_TAG
366
+ end
367
+
368
+ def source_dataset
369
+ @source_dataset ||= @request.request["dataset"] ||
370
+ Catalog::Dataset::DEFAULT_NAME
371
+ end
372
+
373
+ def messages_per_second
374
+ @messages_per_second ||= @request.request["messagesPerSecond"] ||
375
+ DEFAULT_MESSAGES_PER_SECOND
376
+ end
377
+
378
+ def log_tag
379
+ "[#{Process.ppid}] data-absorber"
380
+ end
381
+ end
382
+
383
+ def handle(message)
384
+ unless message.request.include?("host")
385
+ raise MissingHostParameter.new
386
+ end
387
+ super
388
+ end
389
+
390
+ private
391
+ def start(request)
392
+ absorber = DataAbsorber.new(loop, messenger, request)
393
+ absorber.start
394
+ end
395
+ end
396
+
397
+ define_single_step do |step|
398
+ step.name = "system.absorb-data"
399
+ step.single_operation = true
400
+ step.handler = AbsorbDataHandler
401
+ step.collector = Collectors::Or
402
+ end
403
+ end
404
+ end
405
+ end