fluent-plugin-droonga 0.0.2

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