2015年6月10日水曜日

embulk-filter-evalでどんなデータ変換ができるのか試してみる

こんにちは、井下です。

少し前のブログで、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 |
+---------+--------------+-------------------------+-------------------------+----------------------------+

数値の計算

GitHubにアップされている資料内にもありますが、手始めに数値計算から。
ymlファイルを次のように書き換えて、再度実行します。

filters: 
  - 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カラムは期待通り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

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")

期待するデータの変換内容は
  • 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 |
+---------+--------------+-------------------------+-------------------------+----------------------------+

commentカラムの"embulk"が"eval"に変換されていますね。

時刻の操作

次は時刻の操作です。時刻の加算や、毎回時刻が協定世界時で表示されるので、日本の現地時刻を試してみます。

filters:
  - 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 |
+---------+--------------+-------------------------+-------------------------+----------------------------+

timeカラムは5秒後の値になっていますが、purchaseカラムには変化がありません。
他にも現地時刻に変換できないか試してみましたが、どうやっても時刻は協定世界時のままでした。Embulkでtimestampとして定義したカラムはこうなる仕様なのでしょうか。
※ご指摘をいただいたので、備考を追記
今回は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

期待するデータの変換内容は
  • 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
+---------+--------------+-------------------------+-------------------------+----------------------------+
| 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 件のコメント:

コメントを投稿