title | layout |
---|---|
プラグイン: 全てのパーティション上でリクエストを処理し、ストレージを操作する新たなコマンドを追加する |
ja |
{% comment %} ############################################## THIS FILE IS AUTOMATICALLY GENERATED FROM "_po/ja/tutorial/1.0.2/plugin-development/handler/index.po" DO NOT EDIT THIS FILE MANUALLY! ############################################## {% endcomment %}
- TOC {:toc}
このチュートリアルでは、各ボリュームでのhadling phaseにおいて分散された処理を実行するプラグインを開発するための方法を学びます。 言い換えると、このチュートリアルでは 新しいコマンドをDroonga Engineに加える方法 を説明します。
- adaption phaseのチュートリアルを完了していること。
適合フェーズからリクエストが転送されてくると、Droonga Engineは*処理フェーズ(processing phase)*に入ります。
処理フェーズでは、Droonga Engineはリクエストを「ステップ」ごとに段階的に処理します。 1つの ステップ は、立案フェーズ、配布フェーズ、ハンドリング・フェーズ、そして 集約フェーズ という4つのフェーズから成り立っています。
- 立案フェーズ では、Droonga Engineはリクエストを処理するための複数のより小さなステップを生成します。 単純なコマンドでは、このフェーズのためのコードを書く必要はありません。その場合には、リクエストを処理するためのステップが1つだけ存在するということになります。
- 配布フェーズ では、Droonga Engineは、リクエストを処理するためのタスクを表すメッセージを複数のボリュームに配布します。 (この処理は完全にDroonga Engine自身によって行われるため、このフェーズはプラグインでの拡張はできません。)
- ハンドリング・フェーズでは、各single volumeが、配布された単一のタスクメッセージを入力として処理して、その結果を返します。
ストレージへの読み書きが実際に発生するのは、この時になります。
実際に、いくつかのコマンド(例えば
search
、add
、create_table
など)はこのタイミングでストレージの読み書きを行っています。 - 集約フェーズ では、Droonga Engineが各ボリュームから返された結果を集約して、単一の結果に統合します。 Droonga Engineは汎用の便利なcollectorをいくつか含んでいるため、多くの場合において、あなたはこのフェーズのためのコードを書く必要はありません。
すべてのステップの処理が終了すると、Droonga Engineは結果を後適合フェーズへと転送します。
ハンドリング・フェーズでの操作を定義するクラスは、ハンドラーと呼ばれます。 簡単に言うと、新しいハンドラーを追加するということは、新しいコマンドを追加するということを意味します。
このチュートリアルでは、新しい独自のコマンド countRecords
を実装することにします。
まず、コマンドの仕様を設計しましょう。
このコマンドは、個々のsingle volumeにおける指定テーブルの全レコードの数を報告します。 これは、クラスタ内でどのようにレコードが分散されているかを調べる助けになるでしょう。 このコマンドはデータベースの内容を何も変更しないので、これは読み取り専用のコマンドと言うことができます。
リクエストは、以下のようにテーブル名を必ず1つ含まなくてはなりません
{
"dataset" : "Starbucks",
"type" : "countRecords",
"body" : {
"table": "Store"
}
}
上記のような内容のJSON形式のファイル count-records.json
を作成します。
以降の検証では、このファイルを使い続けていきましょう。
レスポンスは、各single volumeごとのそのテーブルにあるレコードの数を含んでいなくてはなりません。 これは以下のように、配列として表現できます:
{
"inReplyTo": "(message id)",
"statusCode": 200,
"type": "countRecords.result",
"body": [10, 10]
}
ボリュームが2つある場合、20個のレコードが均等に保持されているはずなので、配列は上記のように2つの要素を持つことになるでしょう。 この例は、各ボリュームがレコードを10個ずつ保持している事を示しています。
それでは、ここまでで述べたような形式のリクエストを受け付けて上記のようなレスポンスを返す、というプラグインを作っていきましょう。
プラグインのディレクトリ構成は、適合フェーズ用のプラグインのチュートリアルでの説明と同じ様式に則ります。
count-records.rb
というファイルとして、count-records
プラグインを作りましょう。ディレクトリツリーは以下のようになります:
lib
└── droonga
└── plugins
└── count-records.rb
次に、以下のようにしてプラグインの骨組みを作ります:
lib/droonga/plugins/count-records.rb:
require "droonga/plugin"
module Droonga
module Plugins
module CountRecordsPlugin
extend Plugin
register("count-records")
end
end
end
以下のようにして、プラグインの中で新しいコマンド countRecords
のための「ステップ」を定義します:
lib/droonga/plugins/count-records.rb:
require "droonga/plugin"
module Droonga
module Plugins
module CountRecordsPlugin
extend Plugin
register("count-records")
define_single_step do |step|
step.name = "countRecords"
end
end
end
end
step.name
の値は、コマンド自身の名前と同じです。
今のところは、コマンドの名前を定義しただけです。
それ以上のことはしていません。
このコマンドはハンドラーを持っていないため、まだ何も処理が行われません。 それではコマンドの挙動を定義しましょう。
lib/droonga/plugins/count-records.rb:
require "droonga/plugin"
module Droonga
module Plugins
module CountRecordsPlugin
extend Plugin
register("count-records")
define_single_step do |step|
step.name = "countRecords"
step.handler = :Handler
end
class Handler < Droonga::Handler
def handle(message)
[0]
end
end
end
end
end
Handler
というクラスは、新しいコマンドのためのハンドラークラスです。
- ハンドラークラスは、組み込みのクラス
Droonga::Handler
を継承してなければなりません。 - ハンドラークラスは、リクエストをどのように扱うかの処理を実装します。
インスタンスメソッド
#handle
が実際にリクエストを処理します。
現時点で、このハンドラーは何も処理を行わず、単に数値1つからなる配列を含む処理結果を返すだけです。 戻り値はレスポンスのbodyを組み立てるのに使われます。
ハンドラーはstep.handler
設定でステップに紐付けられます。
ここではHandler
クラスをdefine_single_step
の後で定義しているため、:Handler
というシンボルでハンドラークラスを指定しています。
もしハンドラークラスをdefine_single_step
よりも前で定義していれば、単にstep.handler = Handler
と書くことができます。
更に、"OtherPlugin::Handler"
のようなクラスパスの文字列も使用できます。
次に、step.collector
設定を使ってコレクターをステップに紐付ける必要があります。
lib/droonga/plugins/count-records.rb:
# (snip)
define_single_step do |step|
step.name = "countRecords"
step.handler = :Handler
step.collector = Collectors::Sum
end
# (snip)
Collectors::Sum
は組み込みコレクターの一つです。
これは、各ボリュームのハンドラーインスタンスから返って来た結果を結合して一つの結果にします。
Update catalog.json to activate this plugin.
Add "count-records"
to "plugins"
.
(snip)
"datasets": {
"Starbucks": {
(snip)
"plugins": ["count-records", "groonga", "crud", "search"],
(snip)
Let's get Droonga started. Note that you need to specify ./lib directory in RUBYLIB environment variable in order to make ruby possible to find your plugin.
# kill $(cat fluentd.pid)
# RUBYLIB=./lib fluentd --config fluentd.conf --log fluentd.log --daemon fluentd.pid
Then, send a request message for the countRecords
command to the Droonga Engine.
# droonga-request --tag starbucks count-records.json
Elapsed time: 0.01494
[
"droonga.message",
1392621168,
{
"inReplyTo": "1392621168.0119512",
"statusCode": 200,
"type": "countRecords.result",
"body": [
0,
0,
0
]
}
]
You'll get a response message like above. Look at these points:
- The
type
of the response becomescountRecords.result
. It is automatically named by the Droonga Engine. - The format of the
body
is same to the returned value of the handler'shandle
method.
There are three elements in the array. Why?
- Remember that the
Starbucks
dataset was configured with two replicas and three sub volumes for each replica, in thecatalog.json
of the basic tutorial. - Because it is a read-only command, a request is delivered to only one replica (and it is chosen at random). Then only three single volumes receive the command, so only three results appear, not six. (TODO: I have to add a figure to indicate active nodes: [000, 001, 002, 010, 011, 012] => [000, 001, 002])
- The
Collectors::Sum
collects them. Those three results are joined to just one array by the collector.
As the result, just one array with three elements appears in the final response.
Now, each instance of the handler class always returns 0
as its result.
Let's implement codes to count up the number of records from the actual storage.
lib/droonga/plugins/count-records.rb:
# (snip)
class Handler < Droonga::Handler
def handle(message)
request = message.request
table_name = request["table"]
table = @context[table_name]
count = table.size
[count]
end
end
# (snip)
Look at the argument of the handle
method.
It is different from the one an adapter receives.
A handler receives a message meaning a distributed task.
So you have to extract the request message from the distributed task by the code request = message.request
.
The instance variable @context
is an instance of Groonga::Context
for the storage of the corresponding single volume.
See the class reference of Rroonga.
You can use any feature of Rroonga via @context
.
For now, we simply access to the table itself by its name and read the value of its size
method - it returns the number of records.
Then, test it. Restart the Droonga Engine and send the request again.
# kill $(cat fluentd.pid)
# RUBYLIB=./lib fluentd --config fluentd.conf --log fluentd.log --daemon fluentd.pid
# droonga-request --tag starbucks count-records.json
Elapsed time: 0.01494
[
"droonga.message",
1392621168,
{
"inReplyTo": "1392621168.0119512",
"statusCode": 200,
"type": "countRecords.result",
"body": [
14,
15,
11
]
}
]
Because there are totally 40 records, they are stored evenly like above.
Next, let's add another new custom command deleteStores
.
The command deletes records of the Store
table, from the storage.
Because it modifies something in existing storage, it is a read-write command.
The request must have the condition to select records to be deleted, like:
{
"dataset" : "Starbucks",
"type" : "deleteStores",
"body" : {
"keyword": "Broadway"
}
}
Any record including the given keyword "Broadway"
in its "key"
is deleted from the storage of all volumes.
Create a JSON file delete-stores-broadway.json
with the content above.
We'll use it for testing.
The response must have a boolean value to indicate "success" or "fail", like:
{
"inReplyTo": "(message id)",
"statusCode": 200,
"type": "deleteStores.result",
"body": true
}
If the request is successfully processed, the body
becomes true
. Otherwise false
.
The body
is just one boolean value, because we don't have to receive multiple results from volumes.
Now let's create the delete-stores
plugin, as the file delete-stores.rb
. The directory tree will be:
lib
└── droonga
└── plugins
└── delete-stores.rb
次に、以下のようにしてプラグインの骨組みを作ります:
lib/droonga/plugins/delete-stores.rb:
require "droonga/plugin"
module Droonga
module Plugins
module DeleteStoresPlugin
extend Plugin
register("delete-stores")
end
end
end
Define a "step" for the new deleteStores
command, in your plugin. Like:
lib/droonga/plugins/delete-stores.rb:
require "droonga/plugin"
module Droonga
module Plugins
module DeleteStoresPlugin
extend Plugin
register("delete-stores")
define_single_step do |step|
step.name = "deleteStores"
step.write = true
end
end
end
end
Look at a new configuration step.write
.
Because this command modifies the storage, we must indicate it clearly.
Let's define the handler.
lib/droonga/plugins/delete-stores.rb:
require "droonga/plugin"
module Droonga
module Plugins
module DeleteStoresPlugin
extend Plugin
register("delete-stores")
define_single_step do |step|
step.name = "deleteStores"
step.write = true
step.handler = :Handler
step.collector = Collectors::And
end
class Handler < Droonga::Handler
def handle(message)
request = message.request
keyword = request["keyword"]
table = @context["Store"]
table.delete do |record|
record.key =~ keyword
end
true
end
end
end
end
end
Remember, you have to extract the request message from the received task message.
The handler finds and deletes existing records which have the given keyword in its "key", by the API of Rroonga.
And, the Collectors::And
is bound to the step by the configuration step.collector
.
It is is also one of built-in collectors, and merges boolean values returned from handler instances for each volume, to one boolean value.
Update catalog.json to activate this plugin.
Add "delete-stores"
to "plugins"
.
(snip)
"datasets": {
"Starbucks": {
(snip)
"plugins": ["delete-stores", "count-records", "groonga", "crud", "search"],
(snip)
Restart the Droonga Engine and send the request.
# kill $(cat fluentd.pid)
# RUBYLIB=./lib fluentd --config fluentd.conf --log fluentd.log --daemon fluentd.pid
# droonga-request --tag starbucks count-records.json
Elapsed time: 0.01494
[
"droonga.message",
1392621168,
{
"inReplyTo": "1392621168.0119512",
"statusCode": 200,
"type": "deleteStores.result",
"body": true
}
]
Because results from volumes are unified to just one boolean value, the response's body
is a true
.
As the verification, send the request of countRecords
command.
# droonga-request --tag starbucks count-records.json
Elapsed time: 0.01494
[
"droonga.message",
1392621168,
{
"inReplyTo": "1392621168.0119512",
"statusCode": 200,
"type": "countRecords.result",
"body": [
7,
13,
6
]
}
]
Note, the number of records are smaller than the previous result. This means that four or some records are deleted from each volume.
We have learned how to add a new simple command working around the data. In the process, we also have learned how to create plugins working in the handling phrase.