見出し画像

O2Oリアルタイム・エッジセンサーの技術

Oxyzenでは『店舗ぐるぐる』のリリースと同時にO2Oリアルタイム・エッジセンサーを開発しました。リアルタイム、エッジ処理系のデバイス開発時にこの記事が一助になれれば幸いに思います。

もともとのセンサーは1次加工データはリアルタイムにサーバーに上げていましたが、重たい解析処理は1日1回の夜間バッチ処理で行っていました。が、いくつかの課題が生じてきました。

1. 台数が増えると処理の負荷が増える

これは半ば当然のことで想定範囲内のことではあり、今までもロジックの見直しなどである程度は対応してきました。またサーバーのスケールアップやスケールアウトなども慎重に重ねてきました。しかしながら、サーバーを増強すると原価が増します。「なんでIoTビジネスはSaaSのようにワンコイン的な料金設定ができないのか?」などと言われますが、その理由のひとつはビッグデータビジネスはサーバー側の料金がめちゃくちゃかかるからです。湯水(というか、石油か?)のごとく計算資源は湧いてはこないのです。

2. オフライン広告やマーケティングには「今」が重要

今までは観光地やモール、施設の人々の流動分析が中心で、どちらかというとある一定期間データを蓄積してそのビッグデータから詳細な分析を行う、というニーズが高く、リアルタイムは「できたらより良い」という感じでした。それが、オフライン領域での広告やマーケティングにおいては「今店舗内がどうなっているのか?」、「広告の前に今どういう人々がいるのか?」という瞬間風速の把握が必須であり、既存のやり方では物足りなさが否めませんでした。

3. 海外展開を考えるとそもそも「夜間」にバッチ処理できなくなる

これは想定外でしたが…日本国内では大体深夜0時〜翌朝7時くらいまでがいわゆる「夜間バッチ処理」の可能な時間帯だと思います。が、国外への設置を考えると国によって「夜間」がそもそも異なるのです。アジア周辺だったらまだマシですがアメリカとか欧州とかを考えると〆の時刻がなくなってしまうわけです。これは大きな課題でした。

地球が丸いことに気づかされました!

このように、いざ具体的な設計・開発フェーズを迎えると一筋縄ではいかないことがいくつかあり、一つひとつ解決していきました。

使用言語をphpからGoへ

なぜphpを?と思われるかもしれませんが、単に慣れてたからでした。私はもともとWeb系のエンジニアだったので、perl, js, tcl/tkそしてphp周辺で暮らして来ました(c, c++, javaとかも業務でやってたこともありましたが)。既存のセンサーはWi-Fiのシグナルをほぼそのままサーバーに投げるだけでしたのでphpでも事足りてたのですが、サーバーでやっていた解析処理をエッジ側(センサー側)でやろうとすると速度とメモリーの使用量などが気になりました。そして、いくつかのOS環境やCPUのアーキテクチャの違いを乗り越えるためのクロスコンパイルが容易にできることもGoを選択する魅力となりました。かくして、phpからGoへの完全移植が敢行されました(意外と大変でした…)。

ラズパイからRock Piへ…

これはコロナや紛争などの理由からの半導体不足による流通の滞りと、ラズパイの人気が高まり一時期は入荷まで「1年待ち」という酷い状況でした。これでは安定的なビジネスが成り立ちません。そこで海外の企業ともいろいろ相談した結果、「Rock Piというラズパイに似た基盤が流通しているらしいよ」とのことでその頃はまだ日本での代理店はなかったのですが海外から取り寄せてテストを重ねました(技適などはクリアしています)。似たような価格帯のラズパイ2Bに比べスペックも良いし、格段に速い!また連続稼働にも問題もなくRock Pi(Rock 3 Model A)を選定することにしました(ラズパイ2B : CPU "900MHz, Quad-core ARMv8 Cortex-A53"), Rock 3 Model A : "2GHz, Quad-core Cortex-A55")。更には放熱も(正確に計った訳ではないですが)ラズパイに比べ格段に低いです。

開発を進めるうちに分かったことですが、OSをDebian系のUbuntuを用いていますがRock Piの提供企業の独自カスタマイズにより無線LANネットワークのインターフェース名が場合によって"wlan0"になったり、"wlx[Mac address]"となったり、時にはどちらも出たり…とかなりイケてない仕様となっていて、この現象の回避には手間取りました。

分析元データの同期方法

既存サービスである『Digital東京』や『店舗ぐるぐる』では大きく分けて2つのビッグデータ処理を行っています。「属性分析」と「ユニークカウント数や移動、滞在時間分析用データの生成」です(詳しくはこちら)。これらの処理を今まではサーバー側の夜間バッチ処理で行っていました。そして、分析には「分析元データ」が存在します。つまり属性分析で言うと、「"docomo1234"というSSIDは「ドコモ」である」という分析の基礎となるデータです。この分析元データは日々変化します…(ここがミソです)そう、「常に変化しています」。正直ここは悩みました。一言でいうと「どのくらいか分からないが精度が落ちる」という意味です。いっそのこと、「リアルタイムに同期すればいいんじゃ?」とも頭をよぎりましたが、そうすると結局はサーバーに負荷がかかり「エッジ側に寄せた意味」がなくなります

リアルタイムとは精度とのトレードオフの側面も現段階では否めないと思っています。結果的に夜中に一度「昨日との差分」のみを得る方法にしました(オンメモリ上のSQLiteに展開されているため、再起動をすると初回は全部取ってきます(ちなみにSQLiteはデータベース単位でロックが掛かるため、テーブル毎にデータベースを別に作成しています:ご参考))。

また、これも地味に重要な部分なのですが、cronで1日1回(何時でも良いのですが)夜中に分析元データを取得する際には明確に"0 0 * * *"のように実行時刻を決め打ち指定するのではなく、"0 0 * * * sleep `expr $RANDOM % 300`; /your/path/program"のように実行時刻にランダムな幅を持たせてサーバーに負荷が集中しないようにしています(ご参考)。

また、ちなみに、ですが、結果としてそれほど精度が落ちることはありませんでした(以下のグラフをご参考)

時間帯別のユニーク数分析の比較
滞在時間分析の比較

データを欠損させないためのデータストリーム

次にセンサーからサーバへのデータ送信についてですが、こちらも相当試行錯誤しました。

まず前提として、データを欠損させてはいけません。原理的に、不具合は0にはなりませんから100%欠損しないシステムはありませんが、最大限の努力は当然尽くすべきです。Rawデータに近い1次加工データはAWSのKinesisを使って既存のセンサーもリアルタイム・エッジセンサーも同様にサーバーに上げています。このデータはKinesisでキューイングされ順次S3へ格納されます万が一何かあってもこのデータから再現が可能となっています)。

また、リアルタイムにエッジ側(センサー側)で処理された結果データはWeb APIを介してサーバーに上げています。なぜKinesisではなくてWeb APIを使っているかというとKinesisはリアルタイムな処理には向かないからです。Kinesisからリアルタイムに後段のシステムが受け取ることはもちろん可能ですが、データ数が多いと遅延が起き、その遅延を回避しようとすると結局は冒頭に述べたようにサーバー側のコストが掛かってしまいます

また、死活監視やセンサーの制御はWeb Socketを用いて実現しています。これは双方向のやり取りが必要なためで、サーバー側とセンサー側とで独自実装しています。

SDカードにがしがし書き込まない

最後に、これは既存のリアルタイムではないセンサーでも対応していたことなのですが、重要な要素ですので触れておきたいと思います。結論から申し上げると「SDカードにがしがし書き込んではいけません!」最近流行りのラズパイなどはSDカードにOSを書き込み、SDカード上にストレージ領域も作れたりデータベースをインストール出来たりしますが、特にIoTセンサーなど「いつ電源が落ちてもおかしくない」状況下でサービスを行う場合は要注意です。SDカードへの書き込みの最中に瞬断などが起きると高い確率でSDカードが壊れます(OSがカーネルパニックになり起動できなくなります)。Oxyzenでも初期のバージョンのセンサーが設置後に頻繁に故障しクレームになってしまったりしました。

回避策としては「logの抑制(停止)」、「tmp領域をtmpfsで確保する」、「swapの停止」です。この3つを着実に実施すればまず壊れませんご参考)。しかしながらメモリを食うのでその点は注意が必要です。


以上がリアルタイム・エッジセンサーについての解説となります。特に難しい、最新の技術を使っている訳ではないのですが、要は、技術的な観点から「カネ掛ければできるけどビジネスになる(ペイできる)サービスに仕上げるためのベストプラクティスは何か?」という点をサービス毎に見極めることが重要だと思っています。

また、今回は触れませんでしたが、センサーにはデジタルサイネージ機能も付いています!この辺りについてはまた別の機会に解説したいと思います。


この記事が気に入ったらサポートをしてみませんか?