Mercari Engineering Blog

We're the software engineers behind Mercari. Check out our blog to see the tech that powers our marketplace.

lltsvでLTSV形式のデータをサクサクパースしよう

SREチームの@cubicdaiyaです。

今回はlltsvというツールを利用してLTSV形式のデータを処理する術について解説します。

LTSV

LTSVはLabeled Tab-separated Valuesの略で、コロンで区切られたラベルと値の組み合わせ(key:value)をタブ区切りで表現したフォーマットです。 主にログデータのフォーマットとしての利用が想定されています。

uri:/upload     status:400   size:13599  reqtime:0.280   apptime:0.150
uri:/downalod   status:200  size:12812  reqtime:0.330   apptime:0.210
uri:/item/new   status:200  size:29830  reqtime:0.050   apptime:0.050
uri:/item/fav   status:200  size:33123  reqtime:0.100   apptime:0.099
uri:/top        status:301  size:1256   reqtime:0.020   apptime:0.020

このようにLTSVは非常にシンプルなフォーマットなのでデータの定義や拡張が容易にできます。 例えば、nginxでログデータをLTSV形式で出力するには以下のようにログフォーマットを定義します。

log_format ltsv 'uri:$uri\t'
                'status:$status\t'
                'size:$body_bytes_sent\t'
                'reqtime:$request_time\t'
                'apptime:$upstream_response_time';

メルカリでは先述にもあるようにデータの定義や拡張が容易で、メンテナンスもしやすいことからnginxやApache等のHTTPサーバのログフォーマットにLTSVを利用しています。 また、ログデータの転送に利用しているFluentdfluent-agent-hydraがサポートしているのも理由の一つです。メルカリでのログデータの処理についてはWEB+DB PRESS vol.97でもう少し詳しく解説しているので興味がある方はご覧下さい。

gihyo.jp

lltsv

lltsvはLTSV形式のデータの加工やフィルタリングができるツールです。普段からよく利用しているのですが、 何度かパッチを書いて送っていたらコラボレータに追加してもらえたので今でも時々機能を追加したりしています。

github.com

それでは、さきほど紹介した5行のLTSVフォーマットのデータをlltsvで処理してみましょう。(ファイル名をsample.ltsvにして保存します)

uri:/upload     status:400   size:13599  reqtime:0.280   apptime:0.150
uri:/downalod   status:200  size:12812  reqtime:0.330   apptime:0.210
uri:/item/new   status:200  size:29830  reqtime:0.050   apptime:0.050
uri:/item/fav   status:200  size:33123  reqtime:0.100   apptime:0.099
uri:/top        status:301  size:1256   reqtime:0.020   apptime:0.020

特定のラベルにマッチするレコードだけを抜き出したり、

$ lltsv -k uri,reqtime,apptime sample.ltsv
uri:/upload     reqtime:0.280   apptime:0.150
uri:/download   reqtime:0.330   apptime:0.210
uri:/item/new   reqtime:0.050   apptime:0.050
uri:/item/fav   reqtime:0.100   apptime:0.099
uri:/top        reqtime:0.020   apptime:0.020

あるいは省くこともできます。

$ lltsv -i reqtime,apptime sample.ltsv
uri:/upload     status:400 size:13599
uri:/download   status:200 size:12812
uri:/item/new   status:200 size:29830
uri:/item/fav   status:200 size:33123
uri:/top        status:301 size:1256

デフォルトの動作だとラベルと値を一緒に出力しますが、集計処理をするような場合だと特定のラベルの値だけを出力したいこともあるでしょう。 そういう場合は-Kを指定します。

$ lltsv -k reqtime -K sample.ltsv
0.280
0.330
0.050
0.100
0.020

また、tail等のコマンドの出力をパイプで渡すこともできます。

$ tail -f /var/log/nginx/access.log | lltsv

フィルタ機能

次にlltsvのフィルタ機能を利用して特定のラベルの値にマッチしたレコードだけを抜き出してみましょう。 以下ではHTTPステータスコードが301のレコードだけを抜き出しています。

$ lltsv -f 'status == 301' sample.ltsv
uri:/top   status:301 size:1256 reqtime:0.020   apptime:0.020

続いてアップストリームサーバのレスポンスタイムが大きいレコード(100ms以上)を抜き出します。

$ lltsv -f 'apptime > 0.100' sample.ltsv
uri:/upload     status:400  size:13599  reqtime:0.280   apptime:0.150
uri:/download   status:200  size:12812  reqtime:0.330   apptime:0.210

ちなみに、reqtimeapptimeというラベル名はそれぞれ以下の意味を持っており、

  • reqtime: サーバがリクエストを処理するのにかかった時間
  • apptime: アップストリーム(プロキシ先)のサーバがレスポンスを返すのにかかった時間

これらはltsv.orgに掲載されている、WebサーバがWebサーバのログフォーマットにLTSVを利用する際に推奨されるラベル名のリストを元にしています。

また、正規表現も利用可能です。(Goのregexpパッケージを利用しています)

$ lltsv -f 'uri =~ ^/item/' sample.ltsv
uri:/item/new   status:200  size:29830  reqtime:0.050   apptime:0.050
uri:/item/fav   status:200  size:33123  reqtime:0.100   apptime:0.099

lltsvで利用可能な比較演算子は以下になります。(末尾が*の演算子は大文字と小文字を区別しない(Case-insensitive)ことを表しています)

>= > == < <=  算術演算による比較
== ==* != !=* 文字列による比較
=~ !~ =~* !~* 正規表現による比較

フィルタ機能の制限

lltsvでは-fでフィルタを追加する際、ラベル名と演算子と値はスペースで区切られている必要がある点に注意しましょう。 例えば、以下のフィルタは正しく動作しますが、

-f 'status == 200'

スペースを削除して実行するとfilter expression is invalid: status==200と出力されてエラーになります。

-f 'status==200'

また、同一ラベルに対して複数のフィルタを指定した場合、最後に指定したフィルタのみが適用されます。

$ lltsv -f 'status == 200' -f 'status == 301' sample.ltsv
uri:/top        status:301      size:1256       reqtime:0.020   apptime:0.020

同一ラベルに対して複数のフィルタを適用する方法は今のところ提供されていません。

式評価機能

lltsvはさらに式評価機能を備えており、これを利用して新しいラベルと値を動的に生成したり、既存ラベルの値を上書きすることができます。 例えば、reqtimeapptimeの差分を表すdiffというラベルを追加し、さらにdiffの値が100ms以上のレコードだけ抜き出す、といったことが可能です。

$ lltsv -k uri,status,size,reqtime,apptime,diff -e 'diff=reqtime-apptime' -f 'diff > 0.100' sample.ltsv
uri:/upload     status:400  size:13599  reqtime:0.280   apptime:0.150   diff:0.13
uri:/download   status:200  size:12812  reqtime:0.330   apptime:0.210   diff:0.12

あるいはreqtimeapptimeに1000をかけて単位を秒からミリ秒に変換することもできます。

$ lltsv -e 'reqtime=reqtime*1000' -e 'apptime=apptime*1000' sample.ltsv
uri:/upload     status:400  size:13599  reqtime:280 apptime:150
uri:/download   status:200  size:12812  reqtime:330 apptime:210
uri:/item/new   status:200  size:29830  reqtime:50  apptime:50
uri:/item/fav   status:200  size:33123  reqtime:100 apptime:99
uri:/top        status:301  size:1256   reqtime:20  apptime:20

lltsvの式評価機能は与えられた式からGoのAST(抽象構文木)を生成した後、生成した構文木をトラバースする形で与えられた式を評価します。 そのため、f = (a + b) * (c - d) のような若干複雑な式を記述することができるほか、 フィルタ機能と違ってラベルや値と演算子の間にスペースがなくてもうまい具合に解釈してくれます。 ただし、1行毎にevalを実行するようなものなので処理速度は遅い点に注意しましょう。

GoでASTを扱う方法については弊社の@tenntennが詳しいので興味のある人は彼のQiitaエントリを覗いてみると良いでしょう。 実際のところ、私もこの機能を実装するにあたって大いに参考させていただきました。

qiita.com qiita.com qiita.com

まとめ

lltsvを利用してLTSV形式のデータを処理するさまざまな方法について解説しました。 LTSVはシンプルなフォーマットなのでgrepやawk、perlのワンライナーでもある程度簡単にデータの加工やフィルタリングが可能ですが、 lltsvがあるとLTSVの加工やフィルタリングがさらに捗ります。

それでは、良いLTSVライフを。