Norikraの使い方をサンプルのクエリとイベントを交えて解説


投稿日:2015年12月18日
  • 12
  • 2
  • 24



初めてEPL文に触れる人には敷居の高いNorikra

norikra_tutorial

NorikraのクエリはSQLライクなのでカジュアルに追加しよう」と、いかにも簡単そうに言われても文法がわからなければ書きようがありません。

早速公式の「Query of Norikraページ」を見てみます。
全部英語で表現も専門的でさっぱりわからない…

と、私のように初めて学ぶことが英語のドキュメントだと理解するのが大変という方も多いと思うので、私なりに解説ページを解説します。

公式ドキュメントをすらすらと理解できる方は素直に「公式のドキュメント」を参照してください。


目次


NorikraのQueryとは何か

NorikraのQueryはEsper’s EPL(エスパーズ イーピーエル)を踏襲しています。
EsperとはJavaで書かれたCEP(Complex Event Processing:複合イベント処理)のためのコンポーネントです。要はリアルタイムにイベントを処理するためのソフトです。

ではEsper’s EPLとは何かというと、EPL(Event Processing Language)の略で、Esperで使うイベントを操作するための言語です。このEPL文がSQL文と似ているためSQLライクと言われるわけです。
まとめると「イベント駆動型アプリケーションで利用されるEPL文 = NorikraのQuery」となります。

Esperについて詳しく調べたいという方は「Esper Reference」を参照してください(気軽に参照できるような文章量ではないですがw)。

また「こちらのページ」でQueryとイベントをテストできます。

Esper’s EPLと全く同じものというわけではなく、幾つかの相違点があります。
ただ、この投稿を読んでいる方にとってはEsper’s EPL自体が耳慣れないものだと思うので、相違点をあげるよりも、以下の書式を見てもらったほうが理解が早いと思います。


Queryは具体的に何をするもの?

Queryは入力イベントを選別したり、集計する条件を指定するためのものです。

norikra_tutorial06

ageが15以上のものだけ出力する。Aが出現した回数をカウントして出力する。といった具合です。


Queryの名前

全てのQueryは名前(name)と、グループ名(group)を持ちます。
nameは必須で一意である必要があり、groupは空白でもOKです(空白の場合はグループ名はdefaultになります)。


Queryの構文

EPL文はSQL文と同じように大文字小文字を区別しません。
しかしターゲット(target)名と、フィールド(field)名は区別します。

簡単なQueryのサンプル

SELECT
  age
FROM accesses.win:time_batch(1 hours)
GROUP BY age

accessesというターゲットから、ageというフィールドを取得し、ageでグループ化しています。

普段SQL文に親しんでいる方ならわかるとおり、SELECTFROMGROUPといった書式は共通です。
また、ターゲットはテーブルフィールドはカラムに相当します。
(win:time_batchなどは後述)


ターゲット名(target name)

ターゲットとは上の例の通り、RDBMSのテーブル名のようなものです。
使える文字は [a-zA-Z][_a-zA-Z0-9]* となっています。
つまり大文字小文字のアルファベットで始まり、続く文字は前出の文字に加え、アンダーバーと数字です。

ちなみにfluentdでターゲット名を指定するにはfluent-plugin-norikratarget_stringで指定します。


フィールド名(field name)とエンティティ

こちらはRDBMSのカラム名のようなものです。
fluentdから渡されるデータを例にすれば、JSON形式に変換されたlogのkeyの項目です。

例えばfluentdでdstatでサーバの使用状況を取得していたとします。

fluentdで扱うJSONは以下のようになっています。

2015-12-15T00:00:06+09:00	dstat	{
  "dstat":{
      "total_cpu_usage":{
      "usr":"2.006","sys":"0.519"
    },
      "dsk/total":{
      "read":"2015232.0","writ":"163020.800"
    }
  }
}

fluent-plugin-norikraでnorikraに渡すと以下の様なデータになります。(ターゲット名にdstatを指定した例)

{
  "name":"dstat",
  "fields": {
    "total_cpu_usage": {
      "usr":"2.006","sys":"0.519"
    },
    "dsk/total": {
      "read":"2015232.0","writ":"163020.800"
    }
  },
}

この例で言う、namedstatターゲット名fieldstotal_cpu_usagedsk/totalフィールド名です。

フィールド名はJSON形式のkeyでサポートされているものは何でも扱えますが、Queryで記述する際はスペース( )ドット(.)はEPL文で特別な意味を持つので使えません。
それらの文字はアンダースコア(_)で置換します。


ビュー(View)について

Queryの構文の解説で「win:time_batch(1 hours)」という見慣れないコードがありました。
これがViewと呼ばれるもので集計するデータに条件を追加するためのものです。

書式は以下のようになります。

namespace:name(view_parameters)

win:time_batch(1 hours)の例で言えば、win名前空間time_batchViewの名前1 hoursViewパラメータです。

norikra_tutorial07

winWindowの略で、1度に扱うデータの単位を指します。出荷するコンテナの規格みたいなものですね。

Queryの動作確認

書式を見るだけでは、なかなか理解できないという方も多いのではないでしょうか。
Norikraにはそんな方にお勧めの機能があります。コマンドでイベントを流して動作を確認することができます。

まずnorikraのページ(前回の解説の通りならhttp://<自分のIPアドレス>:26578/)にアクセスします。

norikra_tutorial01

Add Query」の「Editor」ボタンをクリックし、「Query」に以下のクエリを入力してください。
これはターゲット「test」のフィールド「name」を60秒間隔で取得するというクエリです。

SELECT name FROM test.win:time_batch(60 sec)

Query Nameは必須なので「test」とします。Groupはブランクで構いません。
入力したら「Add Query」ボタンで確定します。

norikra_tutorial02

するとQueriesTargetsに項目が追加されます。

norikra_tutorial03

このようにQueryを登録すると、対応するTargetが自動で追加されます。

TargetのAuto fieldtrueになっていますが、有効だと実際に入力イベントを流した際にfieldの型が自動で判別されます。今のところ何もイベントが無いので「lazy target」となっています。

ではコマンドでイベントを流します。(イベントは流した後、数秒停止します。順番に打ってください)

# cd ~/norikra ← /root/norikraに入れたためディレクトリを移動
# echo '{"name":"spike", "age":27}' | norikra-client event send test
# echo '{"name":"ken", "age":27}' | norikra-client event send test

time_batchで60秒を指定したので、60秒ほど待ってNorikraのページを更新します。
すると以下のようにEventsに虫眼鏡のマークが表示されます。

norikra_tutorial04

クリックすると処理したイベントの内容が表示されます。イベントの先頭の数値はイベントが出力されたUNIX時間です。
またTargetsのfieldの項目も自動で型が表示されます。

norikra_tutorial05

このように慣れないうちは自分の追加したQueryが想定したイベントで正しく処理されるか確認することをお勧めします。

このページでは可能な限りテストクエリテストイベントという形で紹介します。もし良かったらご自身でも試してみてください。

ビュー(View)を指定しない場合

Viewを指定しないQueryは全てのイベントを即座に出力します。
大量のイベントを処理する場合、相応のメモリを消費するので注意が必要です。

テストクエリ)ageが15以上の場合、全てのイベントを出力する

SELECT name, age
FROM test
WHERE age > 15

テストイベント(1つ1つコマンドを打つのが面倒なので以降まとめます)

# echo '{"name":"spike", "age":27}' | norikra-client event send test && \
echo '{"name":"jane", "age":20}' | norikra-client event send test && \
echo '{"name":"ken", "age":12}' | norikra-client event send test

出力イベント

[1450185487,{"age":27,"name":"spike"}]
[1450185495,{"age":20,"name":"jane"}]

タイムスタンプを見ると最初のイベントが実行されてから8秒後にふたつ目のイベントが実行されています。時間を指定してないので即イベントが出力されています。

また、age12kenの項目は除外されています。「WHERE age > 15」が正しく動作していることがわかります。

タイムウィンドウ(Time window)

一定の時間経過した時点でイベントを実行します。いわゆる指定した時間だけ入力したイベントをバッファしてまとめて処理します。

書式

win:time_batch( time-period )

time-periodの第1引数

第1引数は時間を指定します。使える単位は以下のとおり。

sec|second|seconds, min|minute|minutes, hour|hours, day|days to year|years

EPL文では以下のように組み合わせて使用します。(millisecondsは使えるか不明)

10 seconds
10 minutes 30 seconds
20 sec 100 msec
1 day 2 hours 20 minutes 15 seconds 110 milliseconds
0.5 minutes
1 year
1 year 1 month

epl-syntax-time-periodsより

time-periodの第2引数

第2引数には基準となる時間からのオフセット時間を指定します。
まず第2引数に「<num>L」と数値を指定するとunix epoch(UNIX時間)を基準として、第1引数の値で割り切れる時間でイベントを区切ります

例えば第2引数を指定しないで「win:time_batch(10 sec)」とした場合、最初のイベントを取得した時点を基準として10秒毎にイベントを処理します。

{"time":"2015/12/10 13:58:46","count":0}
{"time":"2015/12/10 13:58:56","count":1}

対して第2引数に0Lを指定して「win:time_batch(10 sec, 0L)」とした場合は以下のように、ピッタリ10秒単位でイベントを処理します。

{"time":"2015/12/10 13:58:40","count":0}
{"time":"2015/12/10 13:58:50","count":1}

第1引数で「1 min」第2引数で「0L」とすればピッタリ1分単位。第1引数を1hourとすればピッタリ1時間単位で処理されます。

さらにミリ秒単位で数値を指定すると、その時間の分だけずらして処理されます。

例えば「win:time_batch(10 sec, 5000L)」とした場合は5000ミリ秒=5秒だけずれるので以下のようになります。ちなみにミリ秒を%Lと表現するのはRubyのTime formatと同じです。

{"time":"2015/12/10 13:58:45","count":0}
{"time":"2015/12/10 13:58:55","count":1}

例えば○時30分から○時30分まで1時間単位でイベントを処理したいといった場合は60 * 30 * 1000 = 1800000で「win:time_batch(1 hour, 1800000L)」とすれば簡単に指定が可能です。

time-periodの第3引数

view-win-time-batch」に解説があります。

第3引数にFORCE_UPDATESTART_EAGERというフロー制御を追加することができるとのこと。
要約するとイベントが実行された時に計測するためのデータが無い場合どうするかという制御のようです。

FORCE_UPDATEは強制的に項目で埋めて、START_EAGERはイベント開始前であっても空の結果を用意するとのこと。
空であっても出てきたフィールド名は表示したいみたいなときに使うものと思われます。

しかしこれは私の勉強不足のため、再現できず、詳細は不明です。

テストクエリ)win:time_batchに加え、COUNT文で同じものをカウントします。

SELECT
  name,
  age,
  COUNT(*) AS count
FROM test.win:time_batch(1 min)
WHERE age > 15
GROUP BY name, age

テストイベント

# echo '{"name":"spike", "age":27}' | norikra-client event send test && \
echo '{"name":"jane", "age":20}' | norikra-client event send test && \
echo '{"name":"jane", "age":20}' | norikra-client event send test

出力イベント

[1450185248,{"count":1,"age":27,"name":"spike"}]
[1450185248,{"count":2,"age":20,"name":"jane"}]

2回流したjaneの項目はcount2になっています。また、time_batchで60秒単位でイベントを処理しているため出力のタイムスタンプが同じ時刻になっています。

テストクエリ)第2引数に0Lを指定

SELECT
  name,
  age,
  COUNT(*) AS count
FROM test.win:time_batch(1 min, 0L)
WHERE age > 15
GROUP BY name, age

テストイベント

# echo '{"name":"spike", "age":27}' | norikra-client event send test && \
echo '{"name":"jane", "age":20}' | norikra-client event send test && \
echo '{"name":"jane", "age":20}' | norikra-client event send test

出力イベント

[1450185660,{"count":1,"age":27,"name":"spike"}]
[1450185660,{"count":2,"age":20,"name":"jane"}]

タイムスタンプがunix epoch(UNIX時間)を基準に60秒単位になっています。

Externally timed batch window

基準となる時間を「イベントを流した時間以外のものにします。
unix epochフィールドに持っている場合は、そのフィールドを基準にイベントを集計することができます。
この指定を使えば、過去のデータであってもタイムスタンプに基づいて集計することができます。

書式)

win:ext_timed_batch(timestamp, 1 hours)

注意点として、時間は正しくソートされたものでなくてはならず、バラバラに渡されると破棄されます。詳しくは以下のサンプルを参照してください。

とりあえずサンプルとしてイベントの時間は「2015/12/00 00:00:00」としました。Unix epochで「1448841600」です。

テストクエリ

SELECT
  name,
  age
FROM test.win:ext_timed_batch(timestamp * 1000, 1 min)
GROUP BY name, age

テストイベント

# echo '{"timestamp":1448841600, "name":"spike", "age":27}' | norikra-client event send test && \
echo '{"timestamp":1448841600, "name":"jon", "age":25}' | norikra-client event send test && \
echo '{"timestamp":1448841660, "name":"ken", "age":25}' | norikra-client event send test && \
echo '{"timestamp":1448841610, "name":"ken", "age":22}' | norikra-client event send test

出力イベント

[1450240544,{"age":27,"name":"spike"}]
[1450240544,{"age":25,"name":"jon"}]

まず、ext_timed_batchtimestampフィールドを基準にしています。timestampは秒単位なので、ミリ秒単位にするために1000を掛けています。

第2引数で「1 min」となっているので、一番初めのイベントのtimestamp「1448841600(2015/12/00 00:00:00)」を基準として、1分間データを計測します。

イベントの内、初めの2つは60秒位内なので正常に出力されています。
しかし、3つ目のイベントは1分経過しているのため、破棄されています。

続く4つ目のイベントは基準から10秒なので1分以内です。しかし3つ目のイベントで60秒以上経過したと判断されたため、そこで計測が終了します。そのため、4つ目のイベントは時間内にも関わらず出力されませんでした。

このように過去のデータを集計する場合はタイムスタンプの時間を正しくソートして流す必要があります。

Time length window (sliding time window)

カウント等を指定した時間だけまとめてイベント処理する。指定した時間を経過するとリセットされる。
指定した時間でまとめて出力するわけではなく、ビューを指定しない場合と同じように入力の数だけ出力するためスローが多いとメモリが枯渇するので注意。

書式

win:time( time-period )

テストクエリ

SELECT name, age, count(age) AS count
FROM test.win:time( 1 min )
GROUP BY age

テストイベント

# echo '{"name":"spike", "age":27}' | norikra-client event send test && \
echo '{"name":"jon", "age":25}' | norikra-client event send test && \
echo '{"name":"jon", "age":25}' | norikra-client event send test

(1分以上経過後に以下のイベント)

echo '{"name":"jon", "age":25}' | norikra-client event send test

出力イベント

[1450269185,{"count":1,"age":27,"name":"spike"}]
[1450269194,{"count":1,"age":25,"name":"jon"}]
[1450269202,{"count":2,"age":25,"name":"jon"}]
[1450269373,{"count":1,"age":25,"name":"jon"}]

基本的にイベントが渡された時間で即出力しています。
先頭から3つは初めのイベントから1分以内なので、カウントが追加されています。
しかし1分経過するとカウントがクリアされ、再び1からカウントされていることがわかります。

比較的少ないイベントで、○分以内に、○歳以上のユーサーが何人アクセスしたか計測する、といった用途に向いています。

Length (or Length batch) window

イベントのサイズに応じて集計、出力されます。
イベントが○つ集まってから集計する」といった用途に使われます。サイズはサイズでもイベントの数で、バイトなんかのサイズではないので注意してください。

書式

win:length( size ) もしくは
win:length_batch( size )

テストクエリ

SELECT name, age
FROM test.win:length_batch( 2 )

テストイベント

# echo '{"name":"spike", "age":27}' | norikra-client event send test && \
echo '{"name":"jon", "age":25}' | norikra-client event send test && \
echo '{"name":"ken", "age":15}' | norikra-client event send test

出力イベント

[1450274625,{"age":27,"name":"spike"}]
[1450274625,{"age":25,"name":"jon"}]

length_batch2としているので、2つを同時に出力しています。
namekenのイベントは次のイベントがスローされると一緒に出力されます。

Unique

名前が示す通り指定したフィールドの値が一意の場合だけ出力します。
書式にあるexpressionの部分に一意にしたいフィールドを指定します。
例えばnginxのlogを渡す場合、ホストを登録すれば、1度拾ったホストは再度カウントされません。実質time_batch等と組み合わせて使用するものと思われます。(uniqueだけで使うと毎回拾うので意味が無い)

書式

std:unique( expression [, expression ... ] )

名前空間がwinからstdに変わっていますが、これもWindowの1つで、stdはStandard view setのことです。
Uniqueはその中でもUnique Windowと呼ばれるものです。

テストクエリ

SELECT name, age
FROM test.win:time_batch(60 sec).std:unique(age)

テストイベント

# echo '{"name":"spike", "age":27}' | norikra-client event send test && \
echo '{"name":"jane", "age":25}' | norikra-client event send test && \
echo '{"name":"ken", "age":27}' | norikra-client event send test

出力イベント

[1450275726,{"age":25,"name":"jane"}]
[1450275726,{"age":27,"name":"ken"}]

3つ流しましたが、unique(age)で年齢が一意のものだけ表示するため、spikeの結果は消えて、kenだけが生きている。
被った場合は後半のイベントが有効になるようです。

と公式ページでは以上のViewが紹介されていますが、その他にも多くのビューが存在します。
詳しくは「Chapter 13. EPL Reference: Views」をご覧ください。


Field name escape

クエリの指定でフィールド名に使えるのは [_a-zA-Z0-9]+ だけです。SQLではドットスペースは特別な意味を持つのでフィールド名の指定には使えません。
それらの文字はアンダースコアで補完することができるようです。

テストクエリ

SELECT user_name, age
FROM test.win:time_batch(1 min)

テストイベント

# echo '{"user name":"spike", "age":27}' | norikra-client event send test
ERROR on norikra server: TypeError, can't dup NilClass
 For more details, see norikra server's logs

と、エラーが出ます。auto fieldの関係かな?と推察して、まず「user_name」を流して型を確定してから「user name」を流すと正常に処理されました。

# echo '{"user_name":"spike", "age":27}' | norikra-client event send test && \
echo '{"user name":"John", "age":25}' | norikra-client event send test && \
echo '{"user.name":"ken", "age":20}' | norikra-client event send test

出力イベント

[1450278734,{"user_name":"spike","age":27}]
[1450278734,{"user_name":null,"age":25}]
[1450278734,{"user_name":null,"age":20}]

あれ?ふたつ目以降が正しく処理されてない。nullになってる。

原因は不明ですが、どうやら正しく値が取れないようです。そしてスペースやドットを持つフィールドはアンダースコアに変換したイベントを1度流して型を確定しないとエラーで受け付けない。

…ということで、クエリに使う文字列は素直に [_a-zA-Z0-9]+ にしておけ。ということで納得しておきます。


Container fields

ハッシュオブジェクトや配列にアクセスする方法。

ハッシュオブジェクトはドットで繋げる。

例)ハッシュオブジェクト

"user": {
	"name": "tagomoris",
	"email": "tagomoris@gmail.com",
},

クエリ例)

user.name // tagomoris

ではテストしてみます。

テストクエリ

SELECT user.name
FROM test.win:time_batch(10 sec)

テストイベント

# echo '{"user": { "name": "tagomoris","email": "tagomoris@gmail.com" }}' | norikra-client event send test

出力イベント

[1449817825,{"user.name":"tagomoris"}]

配列

配列は配列の順序を$0で指定。後方参照のような形。二番目の項目は$1、三番目は$2といった感じ。

例)配列

"route": ["Shibuya", "Ohsaki", "Shinjuku", "Ikebukuro"]

クエリ例)

route.$1 // Ohsaki

ではこちらもテストします。

テストクエリ

SELECT route.$1
FROM test.win:time_batch(10 sec)

テストイベント

# echo '{"route": ["Shibuya", "Ohsaki", "Shinjuku", "Ikebukuro"]}' | norikra-client event send test && \
echo '{"route": ["Shibuya", "Shinjuku", "Ikebukuro"]}' | norikra-client event send test

出力イベント

[1449818014,{"route.$1":"Ohsaki"}]
[1449818054,{"route.$1":"Shinjuku"}]

Java methods, operators and functions

名前のとおりJavaのメソッドと、演算子等を使ってクエリを組み立てることもできる。

具体的には以下のクラスを使えるとのこと。

java.lang.*
java.math.*
java.text.*
java.util.*

また関数も使えます。

CASE value WHEN v1 THEN r1 WHEN v2 THEN r2 [...] ELSE rr END
CASE WHEN cond1 THEN r1 [...] ELSE rr END
CAST( expression, type_name )
PREV(), CURRENT_TIMESTAMP()
AVG(), COUNT(), MAX(), MIN(), SUM() and DISTINCT
FIRST(), LAST(), MAXBY(), MINBY()

これはいちいちテストするより、Esperの用例を読むほうが早い。

EPL Reference: Operators
EPL Reference: Functions


Group by, Having, Order by, Limit

Group byによるグループ化、Havingによる絞り込み、Order byによる並び替え、Limitによる取得行の制限。
どれもSQLを扱う人にとっては、お馴染みです。恐らくnorikraを利用する上で最も利用頻度の高い関数。

集約(Aggregates)

平均偏差 avedev([all|distinct] expression [, filter_expr])
 平均  avg([all|distinct] expression [, filter_expr])
 カウント  count([all|distinct] expression [, filter_expr])
 カウント(全フィールド)  count(* [, filter_expr])
 最大値  max([all|distinct] expression)
 最大値(制限付き)  fmax([all|distinct] expression, filter_expr)
 不明(理解不能)  maxever([all|distinct] expression)
 不明(理解不能)  fmaxever([all|distinct] expression, filter_expr)
 中央値  median([all|distinct] expression [, filter_expr])
 最小値  min([all|distinct] expression)
 最小値(制限付き)  fmin([all|distinct] expression, filter_expr)
 不明(理解不能)  minever([all|distinct] expression)
 不明(理解不能)  fminever([all|distinct] expression, filter_expr)
 標準偏差  stddev([all|distinct] expression [, filter_expr])
 合計  sum([all|distinct] expression [, filter_expr])

Aggregation Functionsより

everの付いた集約の関数が理解できないが、恐らく今まで計測した中で最小・最大を返すのだと思われる。「今まで」がtime等で区切った範囲内なのか、計測開始から現在までなのかは検証してないので不明。

グループ化(Group-by)

特定のフィールドの値でグループ化するのに使われる。COUNT()にカウントする対象のフィールドを入れる。* で全てという指定もできる。AS <name>で別名(エイリアス)を付ける。

例えば以下のようにすれば、同じ年齢の男女別カウントが集計できる。

テストクエリ

SELECT sex, age, COUNT(age) AS count
FROM test.win:time_batch(1 min)
GROUP BY sex, age

テストイベント

# echo '{"user":"spike", "sex":"fem", "age":27}' | norikra-client event send test && \
echo '{"user":"ken", "sex":"fem", "age":27}' | norikra-client event send test && \
echo '{"user":"michael", "sex":"fem", "age":20}' | norikra-client event send test && \
echo '{"user":"Jane", "sex":"mal", "age":18}' | norikra-client event send test

結果Events

[1450316667,{"sex":"fem","count":2,"age":27}]
[1450316667,{"sex":"mal","count":1,"age":18}]
[1450316667,{"sex":"fem","count":1,"age":20}]

27歳のfemが2、20歳のfemが1、18歳のmalが1と正しく集計できています。

HAVING句

グループ化した上で条件を追加します。通常絞り込みはWHEREを使うが、GROUP BYWHEREの後に実行されるのでGROUP BYの結果を絞り込むことはできない。
つまり、SQLと同じです。

テストクエリ

SELECT sex, age, COUNT(age) AS count
FROM test.win:time_batch(1min)
GROUP BY sex, age
HAVING age = 27

テストイベント

# echo '{"user":"spike", "sex":"fem", "age":30}' | norikra-client event send test && \
echo '{"user":"ken", "sex":"fem", "age":27}' | norikra-client event send test && \
echo '{"user":"Jane", "sex":"mal", "age":27}' | norikra-client event send test

出力イベント

[1449894728,{"sex":"fem","count":1,"age":27}]
[1449894728,{"sex":"mal","count":1,"age":27}]

本来であればspikeの結果が表示されるが、ageを27に限定したので表示されない。

LIMIT句

取得する行数を制限するLIMIT句
これもSQLと同じように使えます。通常はLIMIT 8 OFFSET 2といった指定の仕方をする。
LIMITはそのまま取得する行数を制限し、OFFSET開始行を指定する。
OFFSETのカウントは取得行の先頭が0で、1とすると2行目から取得する。

LIMIT 1, 2」とすることもできる。この表記の場合LIMITとOFFSETは反転する。そのため「LIMIT 2 OFFSET 1」と同じ意味になる。

テストクエリ

SELECT user
FROM test.win:time_batch(60 sec)
LIMIT 1, 2

テストイベント

# echo '{"user":"spike", "sex":"fem", "age":30}' | norikra-client event send test && \
echo '{"user":"ken", "sex":"fem", "age":27}' | norikra-client event send test && \
echo '{"user":"Jane", "sex":"mal", "age":27}' | norikra-client event send test

出力イベント

[1449896604,{"user":"ken"}]
[1449896604,{"user":"Jane"}]

LIMITOFFSET1なので、2行目から取得LIMIT2としたので2行だけ出力されている。

ORDER BY句

並べ替え(ソート)を行うORDER BY
フィールドの後にDESCを付ければ降順ASCなら昇順となる。省略すると昇順になる。
複数ある場合は「age DESC, name ASC」とする。ageが降順になり、ageが同じの場合nameが昇順で並べ替えされる。

テストクエリ

SELECT user, age
FROM test.win:time_batch(1 min)
ORDER BY age

テストイベント

# echo '{"user":"spike", "sex":"fem", "age":30}' | norikra-client event send test && \
echo '{"user":"ken", "sex":"fem", "age":27}' | norikra-client event send test && \
echo '{"user":"Jane", "sex":"mal", "age":28}' | norikra-client event send test

結果Events

[1449915398,{"age":27,"user":"ken"}]
[1449915398,{"age":28,"user":"Jane"}]
[1449915398,{"age":30,"user":"spike"}]

ageの項目で昇順になっています。

rollup句、cube句、grouping sets句

GROUP BY ROLLUP小計

テストクエリ

SELECT goods, sum( price )
FROM test.win:time_batch( 1 min )
GROUP BY ROLLUP( goods )

テストイベント

# echo '{"goods":"book", "price":100}' | norikra-client event send test &&\
echo '{"goods":"book", "price":180}' | norikra-client event send test &&\
echo '{"goods":"pen", "price":50}' | norikra-client event send test

出力イベント

[1449920810,{"goods":"book","sum(price)":280}]
[1449920810,{"goods":"pen","sum(price)":50}]
[1449920810,{"goods":null,"sum(price)":330}]

goodsごとにpriceが集計され、最後に小計されたpriceが出力されている。

cubeはクロス集計

grouping sets小計行とグループ化された集計行を区別。
norikraの例で言えば…、ちょっと使いみちを想像できませんw

JOINs and SubQueries

テーブル(Target)の結合もできます。しかし、fully-qualified field(完全修飾フィールド?)が必要。とのこと。

fully-qualified fieldは勉強不足のため、何のことか理解できませんでした。具体的にはTARGET.FIELDALIAS.FIELDを指定することを意味するとのこと。つまり、一意なエイリアスを指定しないといけないということだろうか。

結合

とりあえずサンプルにある通り実行してみます。

テストクエリ

SELECT
 a.user AS user,
 a.sex AS sex,
 b.age AS age
FROM user_sex.win:time_batch(60 sec) AS a,
 user_age.win:time_batch(60 sec) AS b
WHERE a.user = b.user
 AND b.user IN ('spike', 'ken', 'Jane')
GROUP BY a.user, a.sex, b.age

テストイベント

# echo '{"user":"spike", "sex":"fem"}' | norikra-client event send user_sex &&\
echo '{"user":"ken", "sex":"fem"}' | norikra-client event send user_sex &&\
echo '{"user":"Jane", "sex":"mal"}' | norikra-client event send user_sex &&\
echo '{"user":"spike", "age":18}' | norikra-client event send user_age &&\
echo '{"user":"ken", "age":25}' | norikra-client event send user_age &&\
echo '{"user":"Michael", "age":27}' | norikra-client event send user_age

出力イベント

[1449977286,{"sex":"fem","age":18,"user":"spike"}]
[1449977286,{"sex":"fem","age":25,"user":"ken"}]

SELECTで「AS user」と全ての要素に対してエイリアスを作成する。
FROMではsexageの2つのターゲットを指定し、どちらにもエイリアスを指定している。
WHEREで「a.user」と「b.user」を結合するという結合条件を指定。
さらにANDで条件を追加してIN~で「b.user」の結合条件を追加で指定している。
最後「GROUP BY」でエイリアスを作成した項目を指定。

上の例ではターゲットageuserフィールドの値がMichaelの項目は、WHERE INで条件に指定されていないため表示されていない。
また、WHERE INがなかったとしても、「WHERE a.user = b.user」で同じ項目を結合条件にしているため、aとbの両方に同じ項目が無いと表示されない
つまり2つの条件で表示されていないことになる。

サブクエリ

サブクエリも使いこなすと非常に便利。
サブクエリ側はエイリアスを設定するも、メインクエリに関しては必要ないようです。
サブクエリには何らかのViewがないとエラーになるので注意。

テストクエリ

SELECT
 user,
  ( SELECT a.sex
  FROM user_sex.std:unique(user) AS a
  WHERE a.user = user_age.user
  ) AS sex,
  age
FROM user_age

テストイベント

# echo '{"user":"spike", "age":18}' | norikra-client event send user_age

出力イベント

[1449978049,{"sex":null,"age":18,"user":"spike"}]

sexnullになっている。これはサブクエリに指定したuser_sexが無いため。あくまでメインのターゲットはuser_ageなため、サブクエリのuser_sexが無くてもイベントは実行される。

そこで以下のようにする。

追加テストイベント

# echo '{"user":"spike", "sex":"fem"}' | norikra-client event send user_sex &&\
echo '{"user":"spike", "age":18}' | norikra-client event send user_age

出力イベント

[1449978102,{"sex":"fem","age":18,"user":"spike"}]

無事にサブクエリを取得することができました。
普通はメインクエリにwin:time_batch()などを指定して、時間内にメインクエリとサブクエリを受け付けるようにする。そうすることでサブクエリがnullになるのを防げる。

サブクエリの情報は1度取得すれば何回でも使い回し可能なようです。しかしメモリから溢れれば消えるはずなので、セットが前提の場合は時間内に取得できることを前提のlogに対して行う。


Fully-qualified field names and container fields

ターゲットの名前と、ハッシュの名前が被って、さらにハッシュ内のフィールドと、一般のフィールド名が被った場合は、どちらの項目かわからなくなる。
というよりも、この文章を読んでもわからないはずw
公式のサンプルを見たほうが理解が早いと思います。

以下の様な場合、Events.codeが指すものが、ハッシュ内のcodeなのか、TARGETがEventsのcodeなのか判断できない。

-- TARGET Name: Events
-- Data:
--  {
--    "Events": {"code":404, "path":"/"},
--    "code": 0,
--  }
SELECT
  Events.code -- 404 ? 0 ?
FROM Events

そのような場合はハッシュ側でターゲット名を指定する。

SELECT
  Events.Events.code, -- 404
  Events.Events.path, -- "/"
  code                -- 0
FROM Events

これで区別が付く。


Filters/Patterns

フィルタとパターンも使える。

フィルタはWHERE INと似ており、取得する条件を指定する。○が○だったら、○が○と○の範囲内だったら、○に○が含まれなかったらなど、複雑な指定も可能。

パターンはWHEREと似ている。○が○以上だったらといった指定ができる。他にはタイマーで○時から○時まで実行といった指定も可能。

書式は多岐にわたるため以下を参照のこと。

Filter-based Event Streams

EPL Reference: Patterns


その他のルール

Norikra独自のルール。

NorikraではSELECT * FROMといった指定は動作しません。使用するフィールドを指定する必要があります。これはスキーマレスなNorikraの制限です。

バッククォート「`」はサポートしません。スペースのある項目はアンダースコアで対応します。「user age」は「user_age」で指定できる。

イベントに必要なフィールドがあるかどうかはnorikraが自動で調べています。必要なフィールドのないイベントはnorikraが自動で無視します。

とのこと。


以上でNorikraの基本的な使い方を解説しました。
後半はうまい翻訳が思いつかず、手抜きですが、概要は理解していただけたと思います。

初めて触れるので難しそうに感じるだけで、一旦理解してしまえば、なんとも合理的な仕組みです。
Viewの扱いと、Norikraの書式さえ理解すれば、後はSQLとほぼ同じです。
SQLライクにQueryを書こう」と解説される意味がわかります。

次は「fluentdとNorikraでDoS攻撃を遮断し、メールで通知する方法」を解説します。



現在のページを共有する



現在のページに関連する記事


おすすめの記事


コメントを残す

コメントは認証制のため、すぐには反映されません。

プログラミングに関する質問は「日本語でプログラミングの悩みを解決するQ&Aサイト sukegra」をご利用ください。