こんにちはアイダです。今回は influxdb を業務適用して大変面白かった!というSさんが投稿してくれました。アイダは influxdb を全く知らなかったのですが、いわゆるIoT的な開発にもDBがあるのですね。タイトルに偽りありの使いっぷりですよ!
発端
とある製造業のお客様とIoT関連の分析業務をおこなっていて、下記のような課題に直面した。
- データ発生元や発生タイミング(つまり粒度)、種類(数値と文字)の異なるデータの特定の時間軸を串刺しで参照したい
- Web画面等からから大量の蓄積されている時系列データを任意の時間間隔でサンプリングしたい
さーて、どうするか。 サンプリングの間隔や集計は抽出ロジックでなんとなくどうにでもなる気がしたが、問題なのは特定の時間軸を基準としてデータを引っ張ろうとした場合に実データが発生していない粒度の異なるデータをどうやってひっぱってくるか??それも数値以外の属性も含めて。 いろいろ調べていくうちに、influxDBという時系列DBの集計関数にfillオプションというモノがあるようだ…というわけで今回触ってみた。
時系列DBとは、influxdbとは
そもそも、
- 時系列DBってなんなの?
- 時系列DBってどういう種類があるの?
- influxdbでは何ができるの?他にどんな機能があるの?
とお思いかとおもいますが、そこは世の中にスバラシイ記事がたくさんあるのでそちらにお任せしたいと思います。
もしどーしてもそちらが気になる方は、まずは下記サイトがとても参考になるのでご参照ください。
influxDB 開発元 influxdata サイト
今、時系列DBが熱い(熱いとは言っていない?)
https://www.slideshare.net/nmrmsys/db-94629867
時系列データベースの比較
https://qiita.com/kanegoon/items/054b53c4c15609d32d3a
サンプルデータの準備
それでは下記の例をもとにまずはinfluxDBにデータを準備していきます。 データに動きがないと面白くないので、例えば車を動かした場合のイメージでデータを考えてみました。
例
2020/4/1 15:00よりインテージテクノスフィア本社敷地内からひばりヶ丘駅ロータリへ車を走らせたが、途中の信号機待ちで諸事情によりドライバー変更したとする(あくまで仮です!)。 その時の位置情報と移動速度を別々のスマホアプリから取得し、さらに運転手の変更記録もあとから登録した。
データ種類 | データ頻度 | |
---|---|---|
A | GPS緯度 | 30秒ごとにデータ発生 |
B | GPS経度 | 30秒ごとにデータ発生 |
C | スピード | 10秒ごとにデータ発生 |
D | 運転手 | 15:01:10にAさんからBさんへ交代 |
データイメージ
time | lat | lon | spd | driver |
---|---|---|---|---|
2020-04-01 15:00:00 | 35.749103 | 139.542589 | 0 | Aさん |
2020-04-01 15:00:10 | 10 | |||
2020-04-01 15:00:20 | 30 | |||
2020-04-01 15:00:30 | 35.748833 | 139.543168 | 40 | |
2020-04-01 15:00:40 | 42 | |||
2020-04-01 15:00:50 | 25 | |||
2020-04-01 15:01:00 | 35.749486 | 139.543512 | 0 | |
2020-04-01 15:01:10 | 10 | Bさん | ||
2020-04-01 15:01:20 | 20 | |||
2020-04-01 15:01:30 | 35.750322 | 139.544359 | 30 | |
2020-04-01 15:01:40 | 40 | |||
2020-04-01 15:01:50 | 40 | |||
2020-04-01 15:02:00 | 35.751141 | 139.545014 | 40 | |
2020-04-01 15:02:10 | 40 | |||
2020-04-01 15:02:20 | 40 | |||
2020-04-01 15:02:30 | 35.751768 | 139.545582 | 22 | |
2020-04-01 15:02:40 | 5 | |||
2020-04-01 15:02:50 | 0 |
やりたいこと
- 歯抜け部分を埋めたい、埋め方は直近のデータを引き継いで持ってきてよい
- 15:02:40時点のデータを串刺しで持ってきたい、もしデータがない場合にはその中で一番新しいデータを引き継いで持ってきてよい
- 今回の走行における代表データをほしい、位置やドライバーは最初のデータを代表値としセンサーのデータは走行時間の平均値を使う
データの作成
環境構築やRDBMSのテーブルに該当するmeasurement等、時系列DB特有の機能の説明はここでは省きます。
データ登録SQL(正確にはinfluxQL)
insert test lat=35.749103 1585720800000000000 insert test lat=35.748833 1585720830000000000 insert test lat=35.749486 1585720860000000000 insert test lat=35.750322 1585720890000000000 insert test lat=35.751141 1585720920000000000 insert test lat=35.751768 1585720950000000000 insert test lon=139.542589 1585720800000000000 insert test lon=139.543168 1585720830000000000 insert test lon=139.543512 1585720860000000000 insert test lon=139.544359 1585720890000000000 insert test lon=139.545014 1585720920000000000 insert test lon=139.545582 1585720950000000000 insert test spd=0 1585720800000000000 insert test spd=10 1585720810000000000 insert test spd=30 1585720820000000000 insert test spd=40 1585720830000000000 insert test spd=42 1585720840000000000 insert test spd=25 1585720850000000000 insert test spd=0 1585720860000000000 insert test spd=10 1585720870000000000 insert test spd=20 1585720880000000000 insert test spd=30 1585720890000000000 insert test spd=40 1585720900000000000 insert test spd=40 1585720910000000000 insert test spd=40 1585720920000000000 insert test spd=40 1585720930000000000 insert test spd=40 1585720940000000000 insert test spd=22 1585720950000000000 insert test spd=5 1585720960000000000 insert test spd=0 1585720970000000000 insert test driver="Aさん" 1585720800000000000 insert test driver="Bさん" 1585720870000000000
insert文の後ろについてる数字はなにかって? そう、UNIX時間です。。
確認SQL
> select * from test name: test time driver lat lon spd ---- ------ --- --- --- 2020-04-01T06:00:00Z Aさん 35.749103 139.542589 0 2020-04-01T06:00:10Z 10 2020-04-01T06:00:20Z 30 2020-04-01T06:00:30Z 35.748833 139.543168 40 2020-04-01T06:00:40Z 42 2020-04-01T06:00:50Z 25 2020-04-01T06:01:00Z 35.749486 139.543512 0 2020-04-01T06:01:10Z Bさん 10 2020-04-01T06:01:20Z 20 2020-04-01T06:01:30Z 35.750322 139.544359 30 2020-04-01T06:01:40Z 40 2020-04-01T06:01:50Z 40 2020-04-01T06:02:00Z 35.751141 139.545014 40 2020-04-01T06:02:10Z 40 2020-04-01T06:02:20Z 40 2020-04-01T06:02:30Z 35.751768 139.545582 22 2020-04-01T06:02:40Z 5 2020-04-01T06:02:50Z 0 >
よし、イメージどおりのデータができた!!
触ってみた
じゃあ、やりたいことをどうやってできるか、やってみよう。
つべこべ言わない、見てくれ。
1. 歯抜け部分を埋めたい、埋め方は直近のデータを引き継いで持ってきてよい
> select last(driver) as driver, last(lat) as lat, last(lon) as lon, last(spd) as spd from test where time >= '2020-04-01T06:00:00Z' and time <= '2020-04-01T06:03:00Z' group by time(10s) fill(previous) name: test time driver lat lon spd ---- ------ --- --- --- 2020-04-01T06:00:00Z Aさん 35.749103 139.542589 0 2020-04-01T06:00:10Z Aさん 35.749103 139.542589 10 2020-04-01T06:00:20Z Aさん 35.749103 139.542589 30 2020-04-01T06:00:30Z Aさん 35.748833 139.543168 40 2020-04-01T06:00:40Z Aさん 35.748833 139.543168 42 2020-04-01T06:00:50Z Aさん 35.748833 139.543168 25 2020-04-01T06:01:00Z Aさん 35.749486 139.543512 0 2020-04-01T06:01:10Z Bさん 35.749486 139.543512 10 2020-04-01T06:01:20Z Bさん 35.749486 139.543512 20 2020-04-01T06:01:30Z Bさん 35.750322 139.544359 30 2020-04-01T06:01:40Z Bさん 35.750322 139.544359 40 2020-04-01T06:01:50Z Bさん 35.750322 139.544359 40 2020-04-01T06:02:00Z Bさん 35.751141 139.545014 40 2020-04-01T06:02:10Z Bさん 35.751141 139.545014 40 2020-04-01T06:02:20Z Bさん 35.751141 139.545014 40 2020-04-01T06:02:30Z Bさん 35.751768 139.545582 22 2020-04-01T06:02:40Z Bさん 35.751768 139.545582 5 2020-04-01T06:02:50Z Bさん 35.751768 139.545582 0 2020-04-01T06:03:00Z Bさん 35.751768 139.545582 0
うっひょー!!!!
うれしすぎない?!文字情報も含めて1発でできちゃったよ。
ポイントは集計関数のlastと、グループ化関数についているfillオプションです。
この指定で歯抜けを穴埋めしてくれる、それも文字情報も含めて!
ちなみに他の時系列DBも少し調べてみたのですが文字情報にも適用できるこのようなグループ集計機能をもっていそうなのはinfluxdbだけでした 。
(いやいやこっちにも似たようなのあるよ、といった場合には申し訳ございません。別にinfluxの回し者ではありません。)
2. 15:02:40時点のデータを串刺しで持ってきたい、もしデータがない場合にはその中で一番新しいデータを引き継いで持ってきてよい
> select last(driver) as driver, last(lat) as lat, last(lon) as lon, last(spd) as spd from test where time >= '2020-04-01T06:00:00Z' and time <= '2020-04-01T06:02:40Z' group by time(10s) fill(previous) offset 16 name: test time driver lat lon spd ---- ------ --- --- --- 2020-04-01T06:02:40Z Bさん 35.751768 139.545582 5
1ができれば簡単だよね・・・とおもったら、今回考えついたやり方はこのようになりました。
having句つかえないの?とかサブクエリによるorder byとlimitでやれよとかいろいろご指摘点はごもっともなんですが、とりあえずうまく動いたのはこの方法(where句による範囲指定とoffsetによる開始位置の指定)になりました。
このinfluxQL癖が強いの~
3. 今回の走行における1時間範囲の代表データをほしい、位置やドライバーは最初のデータを代表値としセンサーのデータは走行時間の平均値を使う
> select first(driver) as driver, first(lat) as lat, first(lon) as lon, mean(spd) as spd from test where time >= '2020-04-01T06:00:00Z' and time <= '2020-04-01T06:03:00Z' group by time(1h) fill(previous) name: test time driver lat lon spd ---- ------ --- --- --- 2020-04-01T06:00:00Z Aさん 35.749103 139.542589 24.11111111111111 >
これも便利だ!!
グループ集計の時間軸timeを1hとしている事がポイントです。これも文字情報に使えるというのがうれし。
これができればデータ連携処理時にサンプリングロジックとか考えなくてもいし、当然レスポンス速度のために全データを予め画面側で持つ必要もない。
見たい時間軸で集計したサンプリング結果だけを連携すればいいだけ。
まとめ
このような加工操作はpython使われる方であればpandasとかでやる人が多いと思いますが、時間間隔でのサンプリングはめんどくさかったり、そもそもビッグデータ向けに処理化しようとするとメモリ食いつぶし問題とか処理性能問題にぶち当たります。(Sparkとか使える環境をもっていれば別ですが…)
それを解決できる可能性があるのが時系列DBとなります。もし時系列が前提のデータで異なる粒度、種類の大量データをサンプリングする必要があるケースがあった場合はinfluxDBの導入検討をおススメします。
このほか、timestampをindexにしたparquetフォーマットファイルはそのままインポートできたりとか、いろいろ便利機能があります。 どうぞ、お試しあれ!!