【FFTシリーズ第2回】リアルタイムFFTとアラートシステムの構築
Machbaseリアルタイムデータ研究所
初めてお読みになる方は、先に以下の記事をご覧ください。
はじめに
前回は、時系列データベースの高速処理性能を生かして、高速フーリエ変換(FFT)を行う方法を紹介しました。実際の用途では、FFTの結果を確認するだけでなく、さまざまな条件でリアルタイムまたはバッチ処理によって異常を検知し、担当者へ通知することが最終的な目的になります。例えば、次のようなアラートが考えられます。
特定のセンサーのピーク値が許容値を超えた場合(最大値)
特定のセンサーのトラフ値が許容範囲を外れた場合(最小値)
特定のセンサーのRMS値が許容値を超えた場合(振動の強さ)
FFTの結果で、特定の周波数帯の振幅が許容値を超えた場合(特定の故障パターンの検知)
より高度な条件も考えられますが、本記事では最初のピーク値のケースを使い、次の2つの通知方式を実装してテストします。
アラートの時刻と実測値をログテーブルに記録する
REST APIで構成したWebhookへ、アラート情報をリアルタイムに送信する
最初の方式は、以前の記事「Machbase Neoでシステム監視サービスを構築する」でも扱っています。
2番目のリアルタイム通知は、現場で発生してほしくない緊急事態や、危険性・重要性の高いイベントを対象に設定することで、大きな被害を未然に防ぐ用途に役立ちます。
使用バージョンについて
本記事は2024年5月20日時点で、Machbase Neo 8.0.18-rc3a以降を使って検証・作成しました。このバージョンから、TQLのチャートをダッシュボードに直接表示できるようになり、別途HTMLやJavaScriptを用意する必要がなくなったためです。執筆時点では正式版ではありませんでしたが、8.0.19以降で定着することを想定しています。
8.0.18-rc2以前のバージョンを使う必要がある場合に備えて、別途HTMLファイルも用意しています。
アラートシステムを作ってみる
1. まずはデモ動画で確認
次の動画では、完成したデモを確認できます。前回の記事から変わった画面や動作をご覧ください。
2. Neoのインストールとneo-appsの入手
インストール手順は前回と同じです。リンク先の手順1(Neoのインストール)と手順2(neo-appsのダウンロード)を参照し、可能であれば8.0.18-rc3a以降のバージョンを入手してください。その後、本記事に戻り、手順3から進めます。
3. realtime-fftに追加したファイル
今回はアラート用データの生成と異常検知が中心です。そのため、取り込み処理、関連するTQL、ダッシュボードを追加しました。多くのファイル名はalarm-で始まります。
チュートリアル用ワークシート
alarm-data.wrk
データの取り込み
alarm-insert-noise-x.tql
alarm-insert-noise-y.tql
alarm-insert-noise-z.tql
改良したFFTチャート
alarm-fft-2d.tql:パラメーターでX・Y・Z軸を指定し、2D FFTチャートを作成
alarm-fft-3d.tql:パラメーターでX・Y・Z軸を指定し、3D FFTチャートを作成
アラートの設定・処理用TQL
alarm-peak-publish-post.tql:アラート発生時にWebhookでメッセージを送信
alarm-peak-insert.tql:アラート発生時にログテーブルへデータを記録
alarm-data-stat.tql:統計チャートを作成
ダッシュボード
alarm-data.dsh:統計ダッシュボード(8.0.18-rc3a以降)
alarm-fft.dsh:リアルタイムFFTダッシュボード(8.0.18-rc3a以降)
alarm-data.html:統計ダッシュボードのHTML版(バージョンを問わず利用可能)
alarm-peak-history.dsh:リアルタイムのアラート履歴チャート(バージョンを問わず利用可能)
4. スキーマの作成とデータの取り込み
FFTテーブルのセンサー構成
デモでは実際の振動センサーを模擬し、さまざまなパターンが見えるようにデータを生成します。X・Y・Zの3軸を持つ1つのセンサーを想定し、各軸で毎秒1,000件のデータを作ります。Neo内のタイマーで各取り込み用TQLを1秒ごとに実行するため、データベースには合計で毎秒3,000件のセンサーデータが入ります。
alarm-insert-noise-x.tql(X軸:150Hz・振幅2.0 + 300Hz・振幅50)
alarm-insert-noise-y.tql(Y軸:100Hz・振幅1.0 + 0.1Hz・振幅10)
alarm-insert-noise-z.tql(Z軸:0.5Hz・振幅20 + 15Hz・振幅10 + 35Hz・振幅3 + 220Hz・振幅2 + 400Hz・振幅2)
ノイズの生成
次は3つのTQLのうちの1つです。ランダムにノイズを加えるため、値を100倍する処理を入れています。MAPVALUEの条件はrandom() < 0.0001で、コード上の確率は1万分の1です。
alarm-insert-noise-x.tql
FAKE( oscillator( freq(150, 2.0), freq(300, 50), range("now", "1s", "1ms") ))MAPVALUE(1, (random() < 0.0001) ? value(1) * 100: value(1))PUSHVALUE(0, "vib-x2", "name")
APPEND(table("fft"))FFTテーブルとタイマーの作成
ワークシート「alarm-fft.wrk」でスキーマを作成します。今回は、アラートの履歴を保存するログテーブルalarm_historyを追加しました。また、Retention機能を使い、保存するデータを1か月分に制限しています。詳しくは関連マニュアルをご参照ください。
タイマーも作成して、ノイズを含むデータを自動的に取り込みます。タイマー作成のスクリプトは、前回と同じ手順で実行します。

5. リアルタイムFFTの結果をダッシュボードで確認(alarm-fft.dsh)
前回との違いは、複数のFFTチャートを1画面に表示するために、別のHTMLファイルを作らなくてよいことです。8.0.18-rc3aで追加された、ダッシュボードのTQLチャート機能を使います。
ダッシュボードを開くと、3軸それぞれの2D・3D FFT結果がリアルタイムに表示され、滑らかに更新されます。
表示画面

5秒ごとに、チャートが滑らかに更新されます。
3D FFTではCHART_BAR3D()ではなくCHART()を直接使用し、配置などの詳細なチャートオプションを指定しています。以下のコードをご参照ください。また、GETリクエストでkey=value形式のパラメーターを渡せるため、チャートの設定も便利になりました。
alarm-fft-3d.tql
SQL_SELECT(
'time', 'value',
from('fft', param('axis') ?? 'vib-y2'),
between('last-' + (param('range') ?? '10s'), 'last')
)
MAPKEY( roundTime(value(0), '500ms') )
GROUPBYKEY()
FFT(minHz(0), maxHz(1000))
FLATTEN()
PUSHKEY('fft')
PUSHVALUE(0, list(value(0), value(1), value(2)))
POPVALUE(1, 2, 3)
CHART(
plugins("gl"),
chartOption(strSprintf(`{
"xAxis3D": {
"name": "time",
"type": "time",
"show": true,
},
"yAxis3D": {
"name": "Hz",
"type": "value",
"show": true
},
"zAxis3D": {
"name": "Amp",
"type": "value",
"show": true
},
"grid3D": {
"boxWidth": 70,
"boxHeight":70,
"boxDepth": 70,
"left": "10%%",
"viewControl": {
"projection": "orthographic",
"autoRotate": false,
"speed": 0
}
},
"title": {
"text": "%s",
"left": 'center'
},
"visualMap": [{
"type": "continuous",
"calculable": true,
"min": 0,
"max": 10,
"inRange": {
"color": ["#313695", "#4575b4", "#74add1", "#abd9e9", "#e0f3f8", "#ffffbf", "#fee090", "#fdae61", "#f46d43", "#d73027", "#a50026"]
}
}],
"tooltip": {
"show": true,
"trigger": "axis"
},
"series": [{
"type": "bar3D",
"coordinateSystem": "cartesian3D",
"data":column(0),
"shading": "lambert",
"itemStyle": {
"opacity": 1
}
}]
}`, param('title') ?? "X axis FFT in 3D"))
)
チャート作成時のパラメーター設定
チャートの種類をTQLにすると、ファイルとパラメーターを指定できます。TQL側ではparam('キー')で値を受け取ります。

6. 振動データの統計ダッシュボード(alarm-data.dsh)
統計指標も、別のHTMLを用意せず、ダッシュボード上ですべて設定できます。
上段の3つは統計値のチャート、下段の3つ(X・Y・Z Alarm)は指定範囲を外れたアラートを表示します。このダッシュボードは、alarm-data-stat.tql、alarm-peak-insert.tql、alarm-peak-publish-post.tqlを3秒ごとに実行します。
アラートのチャートは、正常時には0、異常時には該当する値を表示します。デモでは確率的に、およそ5〜10秒に1回アラートが発生します。今回はピーク値を対象とし、各軸を次のように設定しています。
動作内容 | X軸 | Y軸 | Z軸 |
データ生成用TQL | alarm-insert-noise-x.tql | alarm-insert-noise-y.tql | alarm-insert-noise-z.tql |
アラートの通知方式 | alarm_historyテーブルに履歴を記録 | alarm_historyテーブルに履歴を記録 | Webhookでアラートイベントを送信 |
アラート条件(チャート作成時にパラメーターで指定) | X軸のピーク値が200を超えた場合 | Y軸のピーク値が300を超えた場合 | Z軸のピーク値が100を超えた場合 |
アラート検知用TQL | alarm-peak-insert.tql | alarm-peak-insert.tql | alarm-peak-publish-post.tql |
次の画面では、Y軸のピーク値によるアラートが発生した瞬間を確認できます。

7. アラートが発生すると
X軸とY軸のアラートはalarm_historyテーブルに記録し、Z軸はWebhookでイベントを送信します。これを実行するコードを見ていきましょう。
alarm-peak-insert.tql(X軸またはY軸)
GROUP()で1秒間の最大値を求め、param('peak')で受け取ったしきい値を超えた場合に、WHEN()とINSERT()でログテーブルへ記録します。
SQL_SELECT(
'time', 'value',
from('fft', param('axis') ?? 'vib-y2'),
between(strSprintf('now-%s', param('range') ?? '20s') , 'now')
)
GROUP(
by( value(0),
timewindow(time(strSprintf('now-%s', param('range') ?? '20s')), time('now'), period(param('precision') ?? "1s")),
"TIME"
),
max( value(1),
"PEAK ALARM"
)
)
SET(PEAK, parseFloat(param('peak') ?? 100))
MAPVALUE(0, strTime(value(0), 'DEFAULT', tz('Local')))
WHEN(value(1) > parseFloat(param('peak') ?? 100),
do( value(0), param('axis'), value(1), {
ARGS()
MAPVALUE(3, strSprintf("%s peak alarm!..: more than %.2f", param('axis'), parseFloat(param('peak') ?? 100)))
INSERT("time", "name", "value", "desc", table("alarm_history"))
}
)
)
MAPVALUE(1, value(1) > $PEAK ? value(1) : 0)
CHART(
..省略
)alarm-peak-publish-post.tql(Z軸)
次のコードは、Z軸のアラート発生時に、Google ChatのスペースへPOSTでメッセージを送信します。キーはセキュリティ上の理由から、本記事ではXXXXに置き換えています。
SQL_SELECT(
'time', 'value',
from('fft', param('axis') ?? 'vib-z2'),
between(strSprintf('now-%s', param('range') ?? '20s') , 'now')
)
GROUP(
by( value(0),
timewindow(time(strSprintf('now-%s', param('range') ?? '20s')), time('now'), period(param('precision') ?? "1s")),
"TIME"
),
max( value(1),
"PEAK ALARM"
)
)MAPVALUE(0, strTime(value(0), 'DEFAULT', tz('Local')))
WHEN(value(1) > parseFloat(param('peak') ?? 100),
doHttp("POST",
"https://chat.googleapis.com/v1/spaces/AAAA6WEdswk/messages?key=XXXX",
strSprintf(`{"text": "%s peak alarm (%.2f)!..: more than %.2f"}`, param('axis') ?? "no tag", value(1), parseFloat(param('peak') ?? 100)),
"Content-Type: application/json",
"X-Custom-Header: notification" )
)
MAPVALUE(1, value(1) > parseFloat(param('peak') ?? 100) ? value(1) : 0)
CHART(
.. 省略
)
実際に送信されたメッセージは次のとおりです。

8. アラート履歴の可視化(alarm-peak-history.dsh)
このダッシュボードは散布図を使い、alarm_historyテーブルに保存した履歴をリアルタイムに表示します。Z軸はログテーブルへ履歴を残さないため、このダッシュボードには表示されません。

おわりに
FFTシリーズの第2回では、リアルタイムのアラートを設定・処理するデモを作成しました。今回は各軸のピーク値を対象にしましたが、RMSやその他の統計値にも、同じ方法でアラートを追加・拡張できます。
ご質問やご要望がありましたら、下のリンクからMachbaseへお問い合わせください。FFT分析とアラート処理への理解を深めるきっかけになれば幸いです。


