13. Raspberry Pi + Go 言語でWebサイト

サーバー機が Raspberry Pi 1 Model B+ になってから早3年。
OSの Raspbian は当時は最新バージョンだった wheezy のまま、ずっと使い続けてきたのですが、いいかげん更新しなければならない時期となりました。本家・Debian の wheezy のサポート期間が2018年5月末となっておりまして、派生版の Raspbian もこれに準ずることになると思われます。
もっと早く wheezy の次の jessie が出た時にさっさとバージョンアップするつもりだったのですが、 別のところで書いたとおり、 なぜか jessie では Ruby の動作に異常な負荷がかかるようになり(原因不明)、サイトを表示させるのに1分以上かかるとか、とても使用に耐えない状態でした。
いろいろ試してみたのですが、わたしの力ではまともな表示速度を実現できなかったため Ruby + Phusion Passenger + Nginx + Sinatra の構成は諦めて、他言語で代替することにしました。

一番手っ取り早いのは PHP に戻すことですが、それは後ろ向きな選択なのでヤだなと。 となると Python か、Java か、などと考えてみたのですが、Raspberry Pi で動き、かつ動作速度の点で期待できそうな Go 言語を選択することにしました。それ以前に Go 言語を使ったことがなかったので一から勉強することになり、ここまで時間がかかってしまったという次第。その間に Raspbian は jessie の次の stretch が出ちゃったけどさ・・・。
1.OS関連について
現行の Raspbian stretch には最小構成のインストールイメージ・Lite があるので、wheezy で使ったネットインストールはやめて、Lite を使いました。
インストール後の root パスワード・ロケール・タイムゾーン・ssh 有効化・GPU用のメモリ割り当て等の各種設定用に raspi-config というツールが使えます。それぞれ個別にコマンド使って設定するより楽なのでお薦め。
$ sudo raspi-config

2.Go 言語でのサイト構築
Go 言語の標準ライブラリには HTTP サーバー機能を提供するものが含まれており、これを使っています。なので Apache も Nginx も使っていません。フレームワークも使っていません。調べてみましたが、Go 言語のウェブアプリ用のフレームワークにはまだ定番と言えるようなものは無いようですし、ウチ程度のサイトでしたらなくても作るのに問題ないですし。
Go 言語は Google 謹製とはいえ、まだ普及はこれからというところで、特にウェブアプリのための日本語資料は限られていますが、基本的なところは下記のサイト・本で押さえられると思います。
機能的にはほとんど基本だけで作ったというウチのサイトですが、一点、特殊なところがあります。
開発の初期にとりあえず店頭ページだけを表示するプロトタイプを作って動作させてみたことがありました。Ruby で動かしていた時よりは早くなったのですが、期待していたほどではありませんでした。
足を引っ張っていたのはデータベースへのアクセス。当然のことながら、ここは開発言語を代えただけでは早くなりません。ならば可能なデータは起動時にすべてメモリに読み込んでキャッシュ化してしまえば、データベースへのアクセスが減り、高速化できると考えました。HTTP サーバー から呼び出されて動く Ruby や PHP では実現が難しい機能ですが、ウェブアプリの部分だけでなく HTTP サーバー機能自体を組み込むことができる Go 言語なら比較的簡単に実装できます。
ただし、読み込むデータがサーバー機が搭載しているメモリ量を超えてしまうような場合は当然やるべきではない手法なわけですが、ウチの場合は全然なので。大雑把にいって20MBくらい。メモリを512MBしか積んでいない Raspberry Pi 1 Model B+ でも、このくらいならなんとかなるようです。
また、起動時にデータを一括してメモリに読み込む、というやり方だと、起動後、稼働中にデータの変更が生じた場合、どう対応するのかという問題も出てきます。
これに対する一番簡単な解決策は、再起動すればいい、ということになるんですが、それはいくらなんでも野蛮過ぎ(でも、最初はそのつもりでした)。
うちのサイトで使っているデータベースは PostgreSQL で、Ver.9 から LISTEN/NOTIFY という非同期通知機能が使えるようになっています。これを PostgreSQL の追加モジュール tcn で提供されるトリガ関数から利用すれば、これまた比較的簡単にテーブル上の変更を通知することができます。tcn は Raspbian の場合、postgresql-contrib というパッケージに含まれていますので利用する場合はインストールしておきます。
$ sudo apt-get install postgresql-contrib
それから使いたいデータベースで下記のSQLを発行してやれば使えるようになります。
CREATE EXTENSION tcn;
使用例はこちら。
一方、Go 言語の PostgreSQL 用ドライバパッケージ pq には LISTEN / NOTIFY の通知受信機能が含まれています。
これを本体である HTTP サーバーの子スレッド(もちろん Go 言語の特徴である並列処理 goroutine を使うわけです)で動作させることで、データベースに変更が発生した場合、通知を受け取ることができます。通知を受け取ったらキャッシュを作ってチャネルを使って本体である HTTP サーバーに送ればデータの更新ができるということになります。
データベースのデータ以外に、テンプレートなども起動時にファイルから読み込んでキャッシュ化しています。こちらを更新する場合は Linux のファイルシステムイベント監視機能 inotify とその通知を受信するためのパッケージ fsnotify で、スイッチとして使うファイルを監視させ、そのファイルが更新されたらキャッシュも更新するようにしています。
概ね下図のような動きをしているとお考えください。

完成したところで恒例の Apache Bench による負荷テストをしてみました。 ff1100 のとき や Ruby + Phusion Passenger + Nginx + Sinatra のとき と同様、リクエスト数を100、同時接続数を10、 サイトのトップページをリクエスト先にして測定。結果は以下の通り。
$ ab -n 100 -c 10 http://192.168.0.x/index2.htm
This is ApacheBench, Version 2.3 <$Revision: 1706008 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking 192.168.0.x (be patient).....done
Server Software:
Server Hostname: 192.168.0.x
Server Port: 80
Document Path: /index2.htm
Document Length: 10189 bytes
Concurrency Level: 10
Time taken for tests: 2.270 seconds
Complete requests: 100
Failed requests: 0
Total transferred: 1034400 bytes
HTML transferred: 1018900 bytes
Requests per second: 44.05 [#/sec] (mean)
Time per request: 227.000 [ms] (mean)
Time per request: 22.700 [ms] (mean, across all concurrent requests)
Transfer rate: 445.00 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 2 2 0.8 2 7
Processing: 21 218 84.3 213 472
Waiting: 16 189 73.2 193 361
Total: 23 220 84.3 217 474
Percentage of the requests served within a certain time (ms)
50% 217
66% 238
75% 255
80% 268
90% 342
95% 358
98% 473
99% 474
100% 474 (longest request)
112秒かかってた Ruby + Phusion Passenger + Nginx + Sinatra に対し、2秒そこそこ! 当社比50倍以上の高速化です。まるで詐欺のよーだ。2.4秒かかってた 先代機 より速いのでこっちもびっくりです。ほとんどのデータがキャッシュ化されているので、サイト内検索も速くなりました。
これなら当分、Raspberry Pi 1 でいけそうです。
3.小ネタ
Go 言語のクロスコンパイル
Go 言語の売りの一つにクロスコンパイルできるってのがあります。つまり x86 のPC上で ARM の Raspberry Pi 用のバイナリを作ることができるのです。
ただし、外部のライブラリを使うのでなければ。
今回は引っかかってしまいました。なぜかというと、以前から GeoIP を使ってまして、幸い Go 言語にも GeoIP を使うためのパッケージがあるので自作Webサーバーにも組み込むことができました。ただ、そのパッケージ、C 言語の GeoIP ライブラリを前提としたものなので、ARM 用のバイナリを作るなら結局 ARM の環境でコンパイルしないとエラーになってしまいます。意外な落とし穴になりそうなところですので、ご注意を。
Raspbian でのCPUの温度測定
以前は Raspbian + Raspberry Pi でも lm-sensors が使えたんですが、今はダメみたいです。代わりに Raspbery Pi の内部情報を取得してくれるコマンド vcgencmd を使うことになったようです。
$ vcgencmd measure_temp
これでCPUの温度がわかります。