Dynoと再起動
Dynoが再起動するタイミングには、デプロイや環境変数の変更などがあります。以下に、Dynoが再起動する主なケースを列挙します。
- デプロイ(git push)
- 環境変数の変更(heroku config:set)
- Add-onの追加・削除(heroku addons:add、heroku addons:remove)
- コマンドによる手動再起動(heroku ps:restart)
- 起動時間が24時間を超えたDynoのサイクリング
- DynoManagerによる異常検知
他はともかく、起動時間が24時間を超えたDynoが定期的に再起動されることを不思議に思う方がいるかもしれません。
これはHerokuの特徴的なリスクヘッジ戦略によるものです。
どんなハードウェアでもいつかは必ず壊れます。このリスクを回避するために、オンプレミスでは定期的にハードのリプレースを行ったりするわけですが、ではどの程度使ったらリプレースするのが適切でしょうか? 2年? 1年? それとも半年でしょうか?
この疑問に正解はありませんが、どんな選択をしたにせよ、リプレースの前にハードが故障するリスクはあるわけです。そしてこの問いに対するHerokuの回答は「1日」です。
そんな選択は、実際に物理的なハードウェアを用意するオンプレミスでは絶対にできません。大規模にホストを運用するPaaSならではの戦略といえます。
もちろん、この戦略にはトレードオフがあります。一番大きな注意点は、いつサーバが再起動してもアプリケーションが不整合を起こさないようにする。言い換えれば、アプリケーションをステートレスに保つ必要があるという点です。
具体的にはOnMemorySessionやStickySession(コラムを参照)を、Herokuアプリケーションで使ってはいけません。
この制約には不自由を感じる人もいるかもしれませんが、リリースサイクルの短くなっている近年のWebアプリケーションでは、アプリケーションをステートレスに保つことは、どんな環境であっても常に心がけるべき習慣であると、筆者は考えています。その立場から言うと、Herokuがステートレスであることを強要する点はむしろメリットの1つです。
OnMemorySessionとは、ServletAPIにあるようなセッション情報をメモリ上に保持する仕組みのことです。 この仕組みには次のような問題があります。
- 複数サーバ構成の場合にセッション情報を共有できない
- うかつに再起動できない
前者の問題の解決策の1つがStickySessionで、これはLoadBalancerが同一ブラウザからのリクエストを常に同じホストに回すことで、複数のサーバが同じセッション情報を保持する必要性自体をなくすというものです(AWSのELBではStickySessionを使うことができます)。
しかし、StickySessionを使った場合でも、後者の問題は解決しません。このため、OnMemorySessionを使ったシステムでは、リリース時などに毎回現在接続中のユーザの状態に気を使う必要があります(この問題の典型的な解決策は利用者の少ない真夜中にリリースすることです!)。
これらを排除したHerokuアプリケーションでは、いつでもコマンド1つで新しいバージョンをリリースできることも素晴らしい点です。
preboot
再起動に関して、Dynoを2台以上起動しているアプリケーションではprebootという機能を使うことができます。prebootを有効にするには、コンソールから次のコマンドを実行します。
heroku features:enable preboot
prebootを使わない場合の再起動のシーケンスは次のようになります。
- ルーティング停止
- 旧Dynoシャットダウン
- 新Dyno起動
- ルーティング再開
これがprebootを使用した場合は次のように変わります。
- 新Dyno起動
- ルーティング切り替え
- 旧Dyno停止
prebootを使わない場合、再起動中のリクエストはルーターのキューに溜まることになるので、その間のリクエストでは若干の遅延が発生しますが、prebootを使用することでリクエストの停滞がなくなります。
可能であれば、常にprebootを使用することをお勧めします。
画面の修正やサーバーサイドのちょっとしたロジックの変更などの場合、prebootはとても有益ですが、データベースのスキーマ変更を伴うようなリリースでは、prebootは実質的に機能しません(新バージョンのアプリが旧スキーマで動いてもまずいし、旧バージョンのアプリが新スキーマで動いてもまずいため)。
この場合は、メンテナンスモードを利用して一切のユーザアクセスを遮断した上で、DBの変更と新バージョンのリリースを同時に行う必要があります。
prebootの場合はリリースコマンド(git push)の実行から、実際にアプリが切り替わるまでに若干のタイムラグがあるので、事故を防ぐため、一時的にprebootを無効化することが推奨されています。
以上、今回はHerokuの中心的概念であるDynoについて、かなり細かいところまで解説しました。次回はよく使うHerokuコマンドについて解説する予定です。