Amazon Athena のメッセージパースに Grok を利用可能になりました(GrokSerDe)

こんにちは、藤本です。

現地時間 8/4、Amazon Athena に Logstash の Grok をベースとした新しい SerDe が追加されました。

早速試してみました。

概要

Amazon Athena は S3 にあるファイルを SQL を通して、分析しやすい形でデータを取得できるサービスです。例えば、Apache のアクセスログのデフォルトフォーマットは一行づつアクセス単位で区切られていて、アクセス時間、アクセス元IPアドレス、ステータスコードなど意味のある一つ一つのデータがスペース区切りで出力されます。ログファイルとして見る上では特に問題ないですが、分析するために意味のあるデータでフィルタ、集計したい場合、ログファイルのメッセージを意味のある単位にパースして、一度データベースに投入して、SQL クエリするような必要がありました。それが Athena を利用することで S3 にログファイルを配置していれば、SQL クエリ時にメッセージをパースしてくれ、フィルタ、集計してデータを取得することが可能となりました。

SQL を通してデータ取得するためにファイルのメッセージのフォーマットがどうで、データとしてどのようにマッピングするか定義する必要があります。今までは正規表現(RegexSerDe)、CSV(OpenCSVSerDe)、JSON(JsonSerDe)など 9つの SerDe をサポートしていました。今回追加されたのは Elastic社が開発している Logstash が持つ Grok filter のメッセージパース機能を使った GrokSerDe です。

GrokSerDe がリリースされたことで嬉しいのは既に用意された正規表現パターンを利用することができることかと思っています。Apache のアクセスログもそうですが、ログフォーマットが決まっているログファイルを Athena で分析したい場合、正規表現(RegexSerDe)を利用するケースが多いです。正規表現に馴染みのある方はパフォーマンスを考慮したパターンを書けると思いますが、正規表現に馴染みが少ない方はワイルドカード表現(.*.+)を多用しがちです。しかし、ワイルドカード表現は処理コストが悪いです。なので、出現可能性がある文字パターンが数値だけであれば数値だけ([0-9])に指定する。ステータスコードなどで出現文字数が文字数固定であれば文字数([0-9]{3})を指定することでパフォーマンス向上を図ることができます。Grok では用意されたパターンを利用することで正規表現に馴染みがない方も適切な正規表現パターンを指定することができます。例えば、メールアドレスに対応した%{EMAILADDRESS}、IPアドレス(v4、v6とも)に対応した%{IP}、URL に対応した%{URI}などの正規表現パターンをより高パフォーマンスな形式で簡単に指定できるようになりました。

用意されたパターンは Logstash 1.4.2 の grok-patterns になるようです。個人的には何でこれなんだろう、、、という感想です。何で最新バージョンじゃないんだろう(Logstash の最新バージョンは 5.5.1 だし、今は Grok pattern は個別 Github リポジトリとして管理されているし)。grok-patterns 以外のファイルでもプロダクト毎に特化したメッセージフォーマットを定義したファイルがあるのに対応していない(Logstash 2系からは ELB のアクセスログのメッセージフォーマットに対応した定義もある)。今後のアップデートに期待しましょう。

GrokSerDe の利用方法

GrokSerDe を利用するテーブル定義文は下記のようになります。

CREATE EXTERNAL TABLE <<TABLE_NAME>> (
  <<COLUMN_NAME>> <<COLUMN_TYPE>>,
  :
)
ROW FORMAT SERDE
   'com.amazonaws.glue.serde.GrokSerDe'
WITH SERDEPROPERTIES (
    'input.grokCustomPatterns' = <<GROK_COSTOM_PATTERN>>,
    'input.format'='<<GROK_PATTERN>>'
   )
STORED AS INPUTFORMAT
   'org.apache.hadoop.mapred.TextInputFormat'
OUTPUTFORMAT
   'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat'
LOCATION
   '<<S3_PATH>>';

大事なのはWITH SERDEPROPERTIESです。input.grokCustomPatternsinput.formatが指定可能です。

  • input.format(必須)

Grok 形式のメッセージフォーマットを記述します。ベースは正規表現となります。変数化した正規表現とマッピングさせたいカラム名を%{変数名:カラム名}のフォーマットで記述していくこととなります。

例えば、S3 にあるログファイルのメッセージフォーマットが下記のような場合、

<<整数>> <<IPアドレス>> <<スペースを含まない文字列>>

下記のような定義となります。

%{INT:column1} %{IP:column2} %{NOTSPACE:column3}

INT、IP、NOTSPACE は GrokSerDe によって用意された変数名(中身は正規表現パターン)となります。

  • input.grokCustomPatterns(任意)

任意の正規表現パターンを変数として定義します。GrokSerDe によって正規表現パターンを格納した変数が提供されていると記載してきましたが、ユーザーでも正規表現パターンを定義することができます。

例えば数値が 5つ続く正規表現パターンを定義する場合、

FIVE_NUMBER [0-9]{5}

といった記述により変数FIVE_NUMBERを利用可能になります。

複数の変数を定義する場合は改行区切りで並べていくようです。

WITH SERDEPROPERTIES (
    'input.grokCustomPatterns' = 'FIVE_NUMBER [0-9]{5}
SIX_NUMBER [0-9]{6}
SEVEN_NUMBER [0-9]{7}',
    'input.format'='%{FIVE_NUMBER:fivenum} %{SIX_NUMBER:sixnum} %{SEVEN_NUMBER:sevennum}'
   )

試してみた

今回は ELB(Classic Load Balancer)のアクセスログを GrokSerDe を使って、クエリしてみます。

テーブル定義

Grok の場合、下記のような定義を行うことができます。

CREATE EXTERNAL TABLE `elblog_grok`(
    timestamp string,
    elb string,
    clientip string,
    clientport int,
    backendip string,
    backendport int,
    request_processing_time double,
    backend_processing_time double,
    response_processing_time double,
    response int,
    backend_response int,
    received_bytes bigint,
    bytes bigint,
    verb string,
    request string,
    useragent string,
    ssl_cipher string,
    ssl_protocol string
)
ROW FORMAT SERDE
    'com.amazonaws.glue.serde.GrokSerDe'
WITH SERDEPROPERTIES (
    'input.grokCustomPatterns' = 'ELB_URIPATHPARAM %{URIPATH}(?:%{URIPARAM})?
ELB_URI %{URIPROTO}://(?:%{USER}(?::[^@]*)?@)?(?:%{URIHOST})?(?:%{ELB_URIPATHPARAM})?
ELB_REQUEST_LINE (?:%{WORD:verb} %{ELB_URI:request}(?: HTTP/%{NUMBER})?|%{DATA})',
    'input.format'='%{TIMESTAMP_ISO8601:timestamp} %{NOTSPACE:elb} %{IP:clientip}:%{INT:clientport} %{IP:backendip}:%{INT:backendport} %{NUMBER:request_processing_time} %{NUMBER:backend_processing_time} %{NUMBER:response_processing_time} %{INT:response} %{INT:backend_response} %{INT:received_bytes} %{INT:bytes} "%{ELB_REQUEST_LINE}" (-|%{QS:useragent}) (-|%{NOTSPACE:ssl_cipher}) (-|%{NOTSPACE:ssl_protocol})'
   )
STORED AS INPUTFORMAT
    'org.apache.hadoop.mapred.TextInputFormat'
OUTPUTFORMAT
    'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat'
LOCATION
    's3://athena-examples-ap-northeast-1/elb/plaintext';

S3 バケットはサンプルで用意されているバケットを指定します。

クエリ

レスポンスのステータスコード単位で集約します。

Athena 3

うん、ちゃんとクエリできていますね。

RegexSerDe とパフォーマンス比較して

スペース以外の雑正規表現で指定した RegexSerDe より処理速度が早くなるか比較してみたところ、何度か試してみましたが、RegexSerDe の方が早かったです。 GrokSerDe を使うことによる処理コストがあるのかなぁ。。ザンネン。

まとめ

いかがでしたでしょうか?

雑正規表現の RegexSerDe の方がパフォーマンスがいい、という結果でしたが、GrokSerDe を利用することで正確な正規表現を指定でき、パースの誤りを防いだり、Grok の構文を見慣れれば正規表現より管理し易いかと思います。