少し前のブログで、OSSのEmbulkについて書いてみましたが、MysqlからCsvへの変換という無難な内容だったので、今回はあまり使用例がアップされていないプラグインにフォーカスしてみます。
フォーカスするプラグインは、データの変換をRubyのコードで定義できる"embulk-filter-eval"です。
embulk-filter-evalの概要
embulk-filter-evalは"FILTER"に分類されるプラグインで、"INPUT"でデータを読み込んだ後、"OUTPUT"にデータを渡す前に、データに変換処理を加えられます。また、出力するカラムを選択できるので、不要なカラムを切り捨てることもできます。
データの転送に際して、ちょっとしたデータの加工をしたい場合や、決まったフォーマットへの変換処理に使えそうですね。
データの変換内容はRubyのコードで定義できるそうですが、具体的にどんなことができるのか、使ってみて分かったことを書いていきます。
なお、出力カラムの選択に関しては、下記の資料を見ればすぐに分かる設定ですので、今回は特に触れません。
インストール方法、ymlファイルの書式、出力カラムの選択方法などは下記のGitHubにアップされている資料をご参照ください。
https://github.com/mgi166/embulk-filter-eval
embulk-filter-evalの利用例
ここから具体的なデータ変換パターンについて、実際に試してみます。インプットのデータは"embulk example"で作成されるサンプルデータとし、ymlを編集する都度、"embulk preview config.yml"コマンドでどんな変換がされるのか確認していきます。
※どんな変換が行えるかの確認が目的なので、以降の利用例では計算の中身に深い意味は持たせていません
補足として、変換なしの"embulk preview config.yml"の結果は下記の通りです。
以降、embulk-filter-evalによる変換後の出力との比較として参考にしてください。
+---------+--------------+-------------------------+-------------------------+----------------------------+
| id:long | account:long | time:timestamp | purchase:timestamp | comment:string |
+---------+--------------+-------------------------+-------------------------+----------------------------+
| 1 | 32,864 | 2015-01-27 19:23:49 UTC | 2015-01-27 00:00:00 UTC | embulk |
| 2 | 14,824 | 2015-01-27 19:01:23 UTC | 2015-01-27 00:00:00 UTC | embulk jruby |
| 3 | 27,559 | 2015-01-28 02:20:02 UTC | 2015-01-28 00:00:00 UTC | Embulk "csv" parser plugin |
| 4 | 11,270 | 2015-01-29 11:54:36 UTC | 2015-01-29 00:00:00 UTC | NULL |
+---------+--------------+-------------------------+-------------------------+----------------------------+
| id:long | account:long | time:timestamp | purchase:timestamp | comment:string |
+---------+--------------+-------------------------+-------------------------+----------------------------+
| 1 | 32,864 | 2015-01-27 19:23:49 UTC | 2015-01-27 00:00:00 UTC | embulk |
| 2 | 14,824 | 2015-01-27 19:01:23 UTC | 2015-01-27 00:00:00 UTC | embulk jruby |
| 3 | 27,559 | 2015-01-28 02:20:02 UTC | 2015-01-28 00:00:00 UTC | Embulk "csv" parser plugin |
| 4 | 11,270 | 2015-01-29 11:54:36 UTC | 2015-01-29 00:00:00 UTC | NULL |
+---------+--------------+-------------------------+-------------------------+----------------------------+
数値の計算
GitHubにアップされている資料内にもありますが、手始めに数値計算から。ymlファイルを次のように書き換えて、再度実行します。
filters:
- type: eval
eval_columns:
- id: value * 2
- account: value / 3
- time: value
- purchase: value
- comment: value
- type: eval
eval_columns:
- id: value * 2
- account: value / 3
- time: value
- purchase: value
- comment: value
期待するデータの変換内容は
- idカラムの値が2倍になる
- accountカラムの値が3分の1になる
実行結果は次のようになりました。
+---------+--------------+-------------------------+-------------------------+----------------------------+
| id:long | account:long | time:timestamp | purchase:timestamp | comment:string |
+---------+--------------+-------------------------+-------------------------+----------------------------+
| 2 | 10,954 | 2015-01-27 19:23:49 UTC | 2015-01-27 00:00:00 UTC | embulk |
| 4 | 4,941 | 2015-01-27 19:01:23 UTC | 2015-01-27 00:00:00 UTC | embulk jruby |
| 6 | 9,186 | 2015-01-28 02:20:02 UTC | 2015-01-28 00:00:00 UTC | Embulk "csv" parser plugin |
| 8 | 3,756 | 2015-01-29 11:54:36 UTC | 2015-01-29 00:00:00 UTC | NULL |
+---------+--------------+-------------------------+-------------------------+----------------------------+
| id:long | account:long | time:timestamp | purchase:timestamp | comment:string |
+---------+--------------+-------------------------+-------------------------+----------------------------+
| 2 | 10,954 | 2015-01-27 19:23:49 UTC | 2015-01-27 00:00:00 UTC | embulk |
| 4 | 4,941 | 2015-01-27 19:01:23 UTC | 2015-01-27 00:00:00 UTC | embulk jruby |
| 6 | 9,186 | 2015-01-28 02:20:02 UTC | 2015-01-28 00:00:00 UTC | Embulk "csv" parser plugin |
| 8 | 3,756 | 2015-01-29 11:54:36 UTC | 2015-01-29 00:00:00 UTC | NULL |
+---------+--------------+-------------------------+-------------------------+----------------------------+
idカラムは期待通り2倍になっていますが、accountカラムは3分の1になったうえで小数点以下が切り捨てられてますね。整数同士の除算は整数になるRubyの仕様上、そうなっているのでしょうか。
Rubyの除算の仕様が原因だとすれば、除算している値のどちらかをfloatとして明示すれば、小数点以下も表示されるはずです。
そこで、ymlファイルを次のように書き換えてみます。
filters:
- type: eval
eval_columns:
- id: value * 2
- account: value / 3.to_f
- time: value
- purchase: value
- comment: value
- type: eval
eval_columns:
- id: value * 2
- account: value / 3.to_f
- time: value
- purchase: value
- comment: value
accountカラムの除算する数値を、floatとして明示しています。
ですが、結果は変わらず小数点が出ないまま…。
その後、色々試してみて分かりましたが、accountカラムの型がlong(整数)である以上、小数点以下は出ないようです。(accountカラムをdoubleにすると、最初のymlファイルでも小数点以下が表示されました)
embulk-filter-evalはデータ変換はするものの、根本的な型を変えたり、制約を越えられないようです。
文字列の操作
次は文字列を操作してみます。単純な結合はGitHubにアップされている資料内からできることが分かっていますが、Strginクラスの他のメソッドは使えるのでしょうか。データの変換ということで、一番使用頻度の高そうな文字列置換(gsubメソッド)を試してみます。
filters:
- type: eval
eval_columns:
- id: value
- account: value
- time: value
- purchase: value
- comment: value.gsub("embulk", "eval")
- type: eval
eval_columns:
- id: value
- account: value
- time: value
- purchase: value
- comment: value.gsub("embulk", "eval")
期待するデータの変換内容は
- commentカラムの"embulk"が"eval"に置換される
実行結果は次のようになりました。
+---------+--------------+-------------------------+-------------------------+----------------------------+
| id:long | account:long | time:timestamp | purchase:timestamp | comment:string |
+---------+--------------+-------------------------+-------------------------+----------------------------+
| 1 | 32,864 | 2015-01-27 19:23:49 UTC | 2015-01-27 00:00:00 UTC | eval |
| 2 | 14,824 | 2015-01-27 19:01:23 UTC | 2015-01-27 00:00:00 UTC | eval jruby |
| 3 | 27,559 | 2015-01-28 02:20:02 UTC | 2015-01-28 00:00:00 UTC | Embulk "csv" parser plugin |
| 4 | 11,270 | 2015-01-29 11:54:36 UTC | 2015-01-29 00:00:00 UTC | NULL |
+---------+--------------+-------------------------+-------------------------+----------------------------+
| id:long | account:long | time:timestamp | purchase:timestamp | comment:string |
+---------+--------------+-------------------------+-------------------------+----------------------------+
| 1 | 32,864 | 2015-01-27 19:23:49 UTC | 2015-01-27 00:00:00 UTC | eval |
| 2 | 14,824 | 2015-01-27 19:01:23 UTC | 2015-01-27 00:00:00 UTC | eval jruby |
| 3 | 27,559 | 2015-01-28 02:20:02 UTC | 2015-01-28 00:00:00 UTC | Embulk "csv" parser plugin |
| 4 | 11,270 | 2015-01-29 11:54:36 UTC | 2015-01-29 00:00:00 UTC | NULL |
+---------+--------------+-------------------------+-------------------------+----------------------------+
commentカラムの"embulk"が"eval"に変換されていますね。
時刻の操作
次は時刻の操作です。時刻の加算や、毎回時刻が協定世界時で表示されるので、日本の現地時刻を試してみます。
filters:
- type: eval
eval_columns:
- id: value
- account: value
- time: value + 5
- purchase: value.getlocal
- comment: value
- type: eval
eval_columns:
- id: value
- account: value
- time: value + 5
- purchase: value.getlocal
- comment: value
期待するデータの変換内容は
- timeカラムの値が5秒後の値になる
- purchaseカラムの値が日本の現地時刻になる
実行結果は次のようになりました。
+---------+--------------+-------------------------+-------------------------+----------------------------+
| id:long | account:long | time:timestamp | purchase:timestamp | comment:string |
+---------+--------------+-------------------------+-------------------------+----------------------------+
| 1 | 32,864 | 2015-01-27 19:23:54 UTC | 2015-01-27 00:00:00 UTC | embulk |
| 2 | 14,824 | 2015-01-27 19:01:28 UTC | 2015-01-27 00:00:00 UTC | embulk jruby |
| 3 | 27,559 | 2015-01-28 02:20:07 UTC | 2015-01-28 00:00:00 UTC | Embulk "csv" parser plugin |
| 4 | 11,270 | 2015-01-29 11:54:41 UTC | 2015-01-29 00:00:00 UTC | NULL |
+---------+--------------+-------------------------+-------------------------+----------------------------+
| id:long | account:long | time:timestamp | purchase:timestamp | comment:string |
+---------+--------------+-------------------------+-------------------------+----------------------------+
| 1 | 32,864 | 2015-01-27 19:23:54 UTC | 2015-01-27 00:00:00 UTC | embulk |
| 2 | 14,824 | 2015-01-27 19:01:28 UTC | 2015-01-27 00:00:00 UTC | embulk jruby |
| 3 | 27,559 | 2015-01-28 02:20:07 UTC | 2015-01-28 00:00:00 UTC | Embulk "csv" parser plugin |
| 4 | 11,270 | 2015-01-29 11:54:41 UTC | 2015-01-29 00:00:00 UTC | NULL |
+---------+--------------+-------------------------+-------------------------+----------------------------+
timeカラムは5秒後の値になっていますが、purchaseカラムには変化がありません。
他にも現地時刻に変換できないか試してみましたが、どうやっても時刻は協定世界時のままでした。Embulkでtimestampとして定義したカラムはこうなる仕様なのでしょうか。
※ご指摘をいただいたので、備考を追記
今回はpreviewで実行しているのでpurchaseカラムで時刻が表示されていますが、purchaseカラムのformatは'%Y%m%d'のため、runで実行すると時刻は表示されなくなります。
ただ、purchaseカラムのformatを'%Y-%m-%d %H:%M:%S'にしてから、runをしてみても結果は変わらず、協定世界時のままでした。
今回はpreviewで実行しているのでpurchaseカラムで時刻が表示されていますが、purchaseカラムのformatは'%Y%m%d'のため、runで実行すると時刻は表示されなくなります。
ただ、purchaseカラムのformatを'%Y-%m-%d %H:%M:%S'にしてから、runをしてみても結果は変わらず、協定世界時のままでした。
出力先がデータベースであれば、タイムゾーン設定が可能なので、特に問題はありませんが、ファイルで出力したい場合は少し工夫が必要になります。
方法の1つとしては、本来は時刻のカラムをStringとして読み込ませて、その中で変換・出力する方法が挙げられます。
その場合のymlファイルの書き方は、過去のブログをご参照ください。(cloudwatch_config.ymlファイルのあたりに記載されています)
簡単なロジック
ここまでは単純な値の書き換えをしてきましたが、Embulkの型の制約内であれば、(恐らく)Rubyのコードは自由に書けるようなので、最後は簡単なロジックを取り入れてデータ変換をしてみます。
filters:
- type: eval
eval_columns:
- id: if value > 2 then
print "value > 2, value is ", value, "\n";
value * 2;
else
print "value <= 2, value is ", value, "\n";
value * 3;
end
- account: value
- time: value
- purchase: value
- comment: value
- type: eval
eval_columns:
- id: if value > 2 then
print "value > 2, value is ", value, "\n";
value * 2;
else
print "value <= 2, value is ", value, "\n";
value * 3;
end
- account: value
- time: value
- purchase: value
- comment: value
期待するデータの変換内容は
- valueカラムの値が2より大きければ、メッセージ"value > 2, value is [実際の数値]"を出力し、値を2倍にする
- valueカラムの値が2以下なら、メッセージ"value <= 2, value is [実際の数値]"を出力し、値を3倍にする
値の変換以外に、ログの出力にも使えたらなと思い、printによる標準出力を入れてみました。
実行結果は次のようになりました。
value <= 2, value is 1
value <= 2, value is 2
value > 2, value is 3
value > 2, value is 4
| 6 | 14,824 | 2015-01-27 19:01:23 UTC | 2015-01-27 00:00:00 UTC | embulk jruby |
| 6 | 27,559 | 2015-01-28 02:20:02 UTC | 2015-01-28 00:00:00 UTC | Embulk "csv" parser plugin |
| 8 | 11,270 | 2015-01-29 11:54:36 UTC | 2015-01-29 00:00:00 UTC | NULL |
value <= 2, value is 2
value > 2, value is 3
value > 2, value is 4
+---------+--------------+-------------------------+-------------------------+----------------------------+
| id:long | account:long | time:timestamp | purchase:timestamp | comment:string |
+---------+--------------+-------------------------+-------------------------+----------------------------+
| 3 | 32,864 | 2015-01-27 19:23:49 UTC | 2015-01-27 00:00:00 UTC | embulk || 6 | 14,824 | 2015-01-27 19:01:23 UTC | 2015-01-27 00:00:00 UTC | embulk jruby |
| 6 | 27,559 | 2015-01-28 02:20:02 UTC | 2015-01-28 00:00:00 UTC | Embulk "csv" parser plugin |
| 8 | 11,270 | 2015-01-29 11:54:36 UTC | 2015-01-29 00:00:00 UTC | NULL |
+---------+--------------+-------------------------+-------------------------+----------------------------+
正直printは使えないだろうと思ってましたが、問題なく実行されたうえ、標準出力もされています。
ifによる条件分岐もしっかりできていますし、これならデータ変換の際、多少の融通は効きそうです。
今回実施したまとめ
結果をまとめると、次のようになります。- 数値の計算や、文字列の結合のような、単純な値の変換が可能
- Rubyのメソッドを利用可能
- カラムの型の制約を超えた変換はできない
- 簡単なロジックは書ける(ifによる条件分岐等)
- データ変換に関係しないコードも書ける&実行される(printによる標準出力等)
終わりに
embulk-filter-evalの使用例を挙げてみましたが、思った以上にRubyのコードが実行できるので、柔軟なデータの変換に使えますね。Embulkの特性上、インプットのデータをファイルとして出力する場合も、データベースに転送する場合も、embulk-filter-evalで変換内容を定義しておけば、同一の結果にできるのは便利だと思います。
1つだけ、これができたらなと思った点としては、同じ行の他のカラムの値も参照できたら、条件分岐の幅が広げられて便利だと思いました。(accountカラムの値を操作するのに、idカラムの値を参照する等)
0 件のコメント:
コメントを投稿