fluent-plugin-droonga 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +1 -0
  3. data/.travis.yml +7 -0
  4. data/Gemfile +40 -0
  5. data/LICENSE.txt +14 -0
  6. data/README.md +18 -0
  7. data/Rakefile +25 -0
  8. data/benchmark/benchmark.rb +123 -0
  9. data/benchmark/utils.rb +243 -0
  10. data/benchmark/watch/benchmark-notify.rb +143 -0
  11. data/benchmark/watch/benchmark-notify.sh +19 -0
  12. data/benchmark/watch/benchmark-publish.rb +120 -0
  13. data/benchmark/watch/benchmark-scan.rb +210 -0
  14. data/benchmark/watch/catalog.json +32 -0
  15. data/benchmark/watch/fluentd.conf +12 -0
  16. data/bin/grn2jsons +85 -0
  17. data/fluent-plugin-droonga.gemspec +41 -0
  18. data/lib/droonga/adapter.rb +156 -0
  19. data/lib/droonga/catalog.rb +153 -0
  20. data/lib/droonga/command_mapper.rb +45 -0
  21. data/lib/droonga/engine.rb +83 -0
  22. data/lib/droonga/executor.rb +289 -0
  23. data/lib/droonga/handler.rb +140 -0
  24. data/lib/droonga/handler_plugin.rb +35 -0
  25. data/lib/droonga/job_queue.rb +83 -0
  26. data/lib/droonga/job_queue_schema.rb +65 -0
  27. data/lib/droonga/logger.rb +34 -0
  28. data/lib/droonga/plugin.rb +41 -0
  29. data/lib/droonga/plugin/adapter/groonga/select.rb +88 -0
  30. data/lib/droonga/plugin/adapter_groonga.rb +40 -0
  31. data/lib/droonga/plugin/handler/groonga/column_create.rb +103 -0
  32. data/lib/droonga/plugin/handler/groonga/table_create.rb +100 -0
  33. data/lib/droonga/plugin/handler_add.rb +44 -0
  34. data/lib/droonga/plugin/handler_forward.rb +70 -0
  35. data/lib/droonga/plugin/handler_groonga.rb +52 -0
  36. data/lib/droonga/plugin/handler_proxy.rb +82 -0
  37. data/lib/droonga/plugin/handler_search.rb +33 -0
  38. data/lib/droonga/plugin/handler_watch.rb +102 -0
  39. data/lib/droonga/proxy.rb +371 -0
  40. data/lib/droonga/searcher.rb +415 -0
  41. data/lib/droonga/server.rb +112 -0
  42. data/lib/droonga/sweeper.rb +42 -0
  43. data/lib/droonga/watch_schema.rb +88 -0
  44. data/lib/droonga/watcher.rb +256 -0
  45. data/lib/droonga/worker.rb +51 -0
  46. data/lib/fluent/plugin/out_droonga.rb +56 -0
  47. data/lib/groonga_command_converter.rb +137 -0
  48. data/sample/cluster/catalog.json +43 -0
  49. data/sample/cluster/fluentd.conf +12 -0
  50. data/sample/fluentd.conf +8 -0
  51. data/test/fixtures/catalog.json +43 -0
  52. data/test/fixtures/document.grn +23 -0
  53. data/test/helper.rb +24 -0
  54. data/test/helper/fixture.rb +28 -0
  55. data/test/helper/sandbox.rb +73 -0
  56. data/test/helper/stub_worker.rb +27 -0
  57. data/test/helper/watch_helper.rb +35 -0
  58. data/test/plugin/adapter/groonga/test_select.rb +176 -0
  59. data/test/plugin/handler/groonga/test_column_create.rb +127 -0
  60. data/test/plugin/handler/groonga/test_table_create.rb +140 -0
  61. data/test/plugin/handler/test_handler_add.rb +135 -0
  62. data/test/plugin/handler/test_handler_groonga.rb +64 -0
  63. data/test/plugin/handler/test_handler_search.rb +512 -0
  64. data/test/plugin/handler/test_handler_watch.rb +168 -0
  65. data/test/run-test.rb +55 -0
  66. data/test/test_adapter.rb +48 -0
  67. data/test/test_catalog.rb +59 -0
  68. data/test/test_command_mapper.rb +44 -0
  69. data/test/test_groonga_command_converter.rb +242 -0
  70. data/test/test_handler.rb +53 -0
  71. data/test/test_job_queue_schema.rb +45 -0
  72. data/test/test_output.rb +99 -0
  73. data/test/test_sweeper.rb +95 -0
  74. data/test/test_watch_schema.rb +57 -0
  75. data/test/test_watcher.rb +336 -0
  76. data/test/test_worker.rb +144 -0
  77. metadata +299 -0
@@ -0,0 +1,112 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2013 droonga project
4
+ #
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License version 2.1 as published by the Free Software Foundation.
8
+ #
9
+ # This library is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
+ # Lesser General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU Lesser General Public
15
+ # License along with this library; if not, write to the Free Software
16
+ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
+
18
+ require "groonga"
19
+
20
+ module Droonga
21
+ module Server
22
+ def before_run
23
+ $log.trace("#{log_tag}: before_run: start")
24
+ $log.trace("#{log_tag}: before_run: done")
25
+ end
26
+
27
+ def after_run
28
+ $log.trace("#{log_tag}: after_run: start")
29
+ $log.trace("#{log_tag}: after_run: done")
30
+ end
31
+
32
+ def stop(stop_graceful)
33
+ $log.trace("#{log_tag}: stop: start")
34
+ super(stop_graceful)
35
+ $log.trace("#{log_tag}: stop: done")
36
+ end
37
+
38
+ private
39
+ def log_tag
40
+ "[#{Process.ppid}][#{Process.pid}] server"
41
+ end
42
+
43
+ def start_worker(wid)
44
+ worker = super(wid)
45
+ worker.extend(WorkerStopper)
46
+ worker
47
+ end
48
+
49
+ module WorkerStopper
50
+ def send_stop(stop_graceful)
51
+ in_signal_sending do
52
+ open_queue do |queue|
53
+ $log.trace("#{log_tag}: stop: start")
54
+
55
+ $log.trace("#{log_tag}: stop: queue: unblock: start")
56
+ max_n_retries = 10
57
+ max_n_retries.times do |i|
58
+ $log.trace("#{log_tag}: stop: queue: unblock: #{i}: start")
59
+ super(stop_graceful)
60
+ queue.unblock
61
+ alive_p = alive?
62
+ $log.trace("#{log_tag}: stop: queue: unblock: #{i}: done: " +
63
+ "#{alive_p}")
64
+ break unless alive_p
65
+ sleep(i * 0.1)
66
+ end
67
+ $log.trace("#{log_tag}: stop: queue: unblock: done")
68
+
69
+ $log.trace("#{log_tag}: stop: done")
70
+ end
71
+ end
72
+ end
73
+
74
+ def send_reload
75
+ in_signal_sending do
76
+ open_queue do |queue|
77
+ $log.trace("#{log_tag}: reload: start")
78
+ super
79
+ queue.unblock
80
+ $log.trace("#{log_tag}: reload: done")
81
+ end
82
+ end
83
+ end
84
+
85
+ private
86
+ def log_tag
87
+ "[#{Process.ppid}][#{Process.pid}][#{@wid}] server: worker-stopper"
88
+ end
89
+
90
+ def in_signal_sending
91
+ Thread.new do
92
+ yield
93
+ end
94
+ end
95
+
96
+ def open_queue
97
+ config = @worker.config
98
+ # TODO: Use JobQueue object
99
+ context = Groonga::Context.new
100
+ database = context.open_database(config[:database])
101
+ queue = context[config[:queue_name]]
102
+ begin
103
+ yield(queue)
104
+ ensure
105
+ queue.close
106
+ database.close
107
+ context.close
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,42 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2013 droonga project
4
+ #
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License version 2.1 as published by the Free Software Foundation.
8
+ #
9
+ # This library is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
+ # Lesser General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU Lesser General Public
15
+ # License along with this library; if not, write to the Free Software
16
+ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
+
18
+ module Droonga
19
+ class Sweeper
20
+ SUBSCRIBER_LIFETIME_SECONDS = 10 * 60 # 10 min
21
+
22
+ def initialize(context)
23
+ @context = context
24
+ end
25
+
26
+ def sweep_expired_subscribers(options={})
27
+ now = options[:now] || Time.now
28
+ boundary = now - SUBSCRIBER_LIFETIME_SECONDS
29
+ expired_subscribers = @context["Subscriber"].select do |subscriber|
30
+ subscriber.last_modified < boundary
31
+ end
32
+ expired_subscribers.each do |subscriber|
33
+ watcher.unsubscribe(:subscriber => subscriber._key)
34
+ end
35
+ end
36
+
37
+ private
38
+ def watcher
39
+ @watcher ||= Watcher.new(@context)
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,88 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2013 droonga project
4
+ #
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License version 2.1 as published by the Free Software Foundation.
8
+ #
9
+ # This library is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
+ # Lesser General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU Lesser General Public
15
+ # License along with this library; if not, write to the Free Software
16
+ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
+
18
+ require "groonga"
19
+
20
+ module Droonga
21
+ class WatchSchema
22
+ def initialize(context)
23
+ @context = context
24
+ end
25
+
26
+ def ensure_created
27
+ if @context["Keyword"]
28
+ $log.trace "#{log_tag} skip table creation"
29
+ return
30
+ end
31
+ $log.trace "#{log_tag} ensure_tables: start"
32
+ ensure_tables
33
+ $log.trace "#{log_tag} ensure_tables: done"
34
+ end
35
+
36
+ private
37
+ def ensure_tables
38
+ Groonga::Schema.define(:context => @context) do |schema|
39
+ schema.create_table("Keyword",
40
+ :type => :patricia_trie,
41
+ :key_type => "ShortText",
42
+ :key_normalize => true,
43
+ :force => true) do |table|
44
+ end
45
+
46
+ schema.create_table("Query",
47
+ :type => :hash,
48
+ :key_type => "ShortText",
49
+ :force => true) do |table|
50
+ end
51
+
52
+ schema.create_table("Route",
53
+ :type => :hash,
54
+ :key_type => "ShortText",
55
+ :force => true) do |table|
56
+ end
57
+
58
+ schema.create_table("Subscriber",
59
+ :type => :hash,
60
+ :key_type => "ShortText",
61
+ :force => true) do |table|
62
+ table.time("last_modified")
63
+ end
64
+
65
+ schema.change_table("Query") do |table|
66
+ table.reference("keywords", "Keyword", :type => :vector)
67
+ end
68
+
69
+ schema.change_table("Subscriber") do |table|
70
+ table.reference("route", "Route")
71
+ table.reference("subscriptions", "Query", :type => :vector)
72
+ end
73
+
74
+ schema.change_table("Keyword") do |table|
75
+ table.index("Query", "keywords", :name => "queries")
76
+ end
77
+
78
+ schema.change_table("Query") do |table|
79
+ table.index("Subscriber", "subscriptions", :name => "subscribers")
80
+ end
81
+ end
82
+ end
83
+
84
+ def log_tag
85
+ "[#{Process.ppid}][#{Process.pid}] watch_schema"
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,256 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2013 droonga project
4
+ #
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License version 2.1 as published by the Free Software Foundation.
8
+ #
9
+ # This library is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
+ # Lesser General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU Lesser General Public
15
+ # License along with this library; if not, write to the Free Software
16
+ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
+
18
+ module Droonga
19
+ class Watcher
20
+ EXACT_MATCH = false
21
+
22
+ def initialize(context)
23
+ @context = context
24
+
25
+ @subscriber_table = @context["Subscriber"]
26
+ @query_table = @context["Query"]
27
+ @keyword_table = @context["Keyword"]
28
+ end
29
+
30
+ def subscribe(request)
31
+ subscriber = request[:subscriber]
32
+ condition = request[:condition]
33
+ query = request[:query]
34
+ route = request[:route]
35
+
36
+ # XXX better validation and error class must be written!!
37
+ if subscriber.nil? || subscriber.empty? || condition.nil? ||
38
+ query.nil? || route.nil?
39
+ raise "invalid request"
40
+ end
41
+ raise "too long query" if query.size > 4095
42
+
43
+ query_record = @query_table[query]
44
+ unless query_record
45
+ keywords = pick_keywords([], condition)
46
+ query_record = @query_table.add(query, :keywords => keywords)
47
+ end
48
+ subscriber_record = @subscriber_table[subscriber]
49
+ if subscriber_record
50
+ subscriptions = subscriber_record.subscriptions
51
+ unless subscriptions.include?(query_record)
52
+ subscriptions << query_record
53
+ subscriber_record.subscriptions = subscriptions
54
+ end
55
+ subscriber_record.last_modified = Time.now
56
+ else
57
+ @subscriber_table.add(subscriber,
58
+ :subscriptions => [query_record],
59
+ :route => route,
60
+ :last_modified => Time.now)
61
+ end
62
+ end
63
+
64
+ def unsubscribe(request)
65
+ subscriber = request[:subscriber]
66
+ query = request[:query]
67
+
68
+ if subscriber.nil? || subscriber.empty?
69
+ raise "invalid request"
70
+ end
71
+
72
+ subscriber_record = @subscriber_table[subscriber]
73
+ return unless subscriber_record
74
+
75
+ if query.nil? || query.empty?
76
+ delete_subscriber(subscriber_record)
77
+ else
78
+ query_record = @query_table[query]
79
+ return unless query_record
80
+
81
+ subscriptions = subscriber_record.subscriptions
82
+ new_subscriptions = subscriptions.select do |query|
83
+ query != query_record
84
+ end
85
+
86
+ if new_subscriptions.empty?
87
+ delete_subscriber(subscriber_record)
88
+ else
89
+ subscriber_record.subscriptions = new_subscriptions
90
+ sweep_orphan_queries(subscriptions)
91
+ end
92
+ end
93
+ end
94
+
95
+ def feed(request, &block)
96
+ targets = request[:targets]
97
+
98
+ hits = []
99
+ targets.each do |key, target|
100
+ scan_body(hits, target)
101
+ end
102
+
103
+ publish(hits, request, &block)
104
+ end
105
+
106
+ def pick_keywords(memo, condition)
107
+ case condition
108
+ when Hash
109
+ memo << condition["query"]
110
+ when String
111
+ memo << condition
112
+ when Array
113
+ condition[1..-1].each do |element|
114
+ pick_keywords(memo, element)
115
+ end
116
+ end
117
+ memo
118
+ end
119
+
120
+ def scan_body(hits, body)
121
+ trimmed = body.strip
122
+ candidates = {}
123
+ # FIXME scan reports the longest keyword matched only
124
+ @keyword_table.scan(trimmed).each do |keyword, word, start, length|
125
+ @query_table.select do |query|
126
+ query.keywords =~ keyword
127
+ end.each do |record|
128
+ candidates[record.key] ||= []
129
+ candidates[record.key] << keyword
130
+ end
131
+ end
132
+ candidates.each do |query, keywords|
133
+ hits << query if query_match(query, keywords)
134
+ end
135
+ end
136
+
137
+ def query_match(query, keywords)
138
+ return true unless EXACT_MATCH
139
+ @conditions = {} unless @conditions
140
+ condition = @conditions[query.id]
141
+ unless condition
142
+ condition = JSON.parse(query.key)
143
+ @conditions[query.id] = condition
144
+ # CAUTION: @conditions can be huge.
145
+ end
146
+ words = {}
147
+ keywords.each do |keyword|
148
+ words[keyword.key] = true
149
+ end
150
+ eval_condition(condition, words)
151
+ end
152
+
153
+ def eval_condition(condition, words)
154
+ case condition
155
+ when Hash
156
+ # todo
157
+ when String
158
+ words[condition]
159
+ when Array
160
+ case condition.first
161
+ when "||"
162
+ condition[1..-1].each do |element|
163
+ return true if eval_condition(element, words)
164
+ end
165
+ false
166
+ when "&&"
167
+ condition[1..-1].each do |element|
168
+ return false unless eval_condition(element, words)
169
+ end
170
+ true
171
+ when "-"
172
+ return false unless eval_condition(condition[1], words)
173
+ condition[2..-1].each do |element|
174
+ return false if eval_condition(element, words)
175
+ end
176
+ true
177
+ end
178
+ end
179
+ end
180
+
181
+ def publish(hits, request)
182
+ routes = {}
183
+ hits.each do |query|
184
+ subscribers = @subscriber_table.select do |subscriber|
185
+ subscriber.subscriptions =~ query
186
+ end
187
+ subscribers.each do |subscriber|
188
+ route = subscriber.route.key
189
+ routes[route] ||= []
190
+ routes[route] << subscriber.key.key
191
+ end
192
+ =begin
193
+ # "group" version. This is slower than above...
194
+ route_records = subscribers.group("route",
195
+ :max_n_sub_records => subscribers.size)
196
+ route_records.each do |route_record|
197
+ route = route_record._key
198
+ routes[route] ||= []
199
+ route_record.sub_records.each do |subscriber|
200
+ routes[route] << subscriber.key.key
201
+ end
202
+ end
203
+ =end
204
+ end
205
+ routes.each do |route, subscribers|
206
+ yield(route, subscribers)
207
+ end
208
+ end
209
+
210
+ private
211
+ def delete_subscriber(subscriber)
212
+ queries = subscriber.subscriptions
213
+ route = subscriber.route
214
+ subscriber.delete
215
+ sweep_orphan_queries(queries)
216
+ sweep_orphan_route(route)
217
+ end
218
+
219
+ def delete_query(query)
220
+ keywords = query.keywords
221
+ query.delete
222
+ sweep_orphan_keywords(keywords)
223
+ end
224
+
225
+ def sweep_orphan_queries(queries)
226
+ queries.each do |query|
227
+ related_subscribers = @subscriber_table.select do |subscriber|
228
+ subscriber.subscriptions =~ query
229
+ end
230
+ if related_subscribers.empty?
231
+ delete_query(query)
232
+ end
233
+ end
234
+ end
235
+
236
+ def sweep_orphan_keywords(keywords)
237
+ keywords.each do |keyword|
238
+ related_queries = @query_table.select do |query|
239
+ query.keywords =~ keyword
240
+ end
241
+ if related_queries.empty?
242
+ keyword.delete
243
+ end
244
+ end
245
+ end
246
+
247
+ def sweep_orphan_route(route)
248
+ related_subscribers = @subscriber_table.select do |subscriber|
249
+ subscriber.route == route
250
+ end
251
+ if related_subscribers.empty?
252
+ route.delete
253
+ end
254
+ end
255
+ end
256
+ end