見出し画像

障害対応のための備え

プログラムが何かしらの原因によって、処理の継続が不可能になる場合があります。

小さなツールプログラムならば、プログラムを一度閉じて再度ファイルを開き直し、また作業を継続すれば済むことが多いでしょう。

もちろん、長時間セーブしておらず。作業がロストしてしまったら、それはユーザへ御愁傷様と声をかけてあげることしかできません。

こういう場合は自動保存機能を作ったりするといいですよね。

しかし、大規模システムのうち、特にバックエンド処理(以下、バックエンドシステム)ではそのようにはいきません。処理しているデータは基本的には失われてはならないからです。

夜間バッチ

バックエンドシステムでは、小さな処理をまとめあげ、一連の大きな処理として処理をします。一般的にすべての営業活動が終わった深夜帯に行われるため、夜間バッチと呼ばれています。

夜間バッチは数時間掛けて処理されるのが普通で、ほぼ朝方まで実行されます。これが途中で落ちてしまった場合にツールプログラムのように頭から全部やり直すのは時間的に考えて難しくなります。

安易に日中にやり直しまうと業務停止してしまいます。なぜなら、夜間バッチはその日に溜まった業務データを処理し、翌日以降の業務で利用されるからです。

そこで、夜間バッチ処理は障害の原因を取り除きさえすれば、途中から処理が続行できるように設計されています。

ロギング

何が原因でプログラムが落ちたのか?という事実を調査するために、手っ取り早くて堅実な手段がログを取ることです。

処理の中で逐一ログを出力させておけば、ログが出なくなったタイミングで処理が終わったことがわかります。

ログの弱点は処理時間を意外と食うことです。高速に終わらせたい繰り返し処理でログを細かく出力させてしまうと、ログの書き込みに時間が取られ、思った以上に低速な処理になります。

そのようなことを避けるため、ログにはレベル(以下、ログレベル)を設けます。デバッグ時は密に、リリース時は粗にログを出すようにするというような制御をかけます。

ログレベルはどれくらい細かく設けられているものなのでしょうか。これは実装によって異なりますが、最低でも以下の5段階はあると制御がしやすくなります。

Debug
Information
Warning
Error
Critical

Debugレベルは開発中にプログラマが見たい情報を出力します。プログラマが開発中に見たいのは、持ち回っている変数の値であったり、扱っているメモリ上のダンプイメージであったりします(これらはデバッガをプロセスへアタッチしても見ることができますが、それが難しい場合もあります。)。

Informationは処理のヒントになるような情報を出力します。これから処理するデータの件数であったり、格納先として扱うデバイス名だったりします。

Warningは処理を停止するまでもないが、ちょっと気をつけたほうがいいような状況となった場合に出力します。スレッドの処理時間が思った以上にかかっている場合であったり、通信がタイムアウトしてリトライをかけた場合であったりします。データベースに格納されなかったゴミデータを検出した場合であるかもしれません。

Errorは処理を停止してプログラムの状況を確認したほうがいい場合に出力されます。多くの場合はプログラムの処理に問題が出ているケースです。

Criticalは処理をいますぐ停止してシステムの状況を確認したほうがよい場合に出力されます。通信断が起きてしまいリカバリできない場合であったり、ファイルの操作に失敗してしまったような、プログラム起因ではない場合だったりします。

中間ファイルを残す

処理の進行を知る手がかりとして、処理中に作成されたファイルをすべて残しておくという手段があります。これを中間ファイルと呼びます。

処理が途中で落ちた場合に、中間ファイルの結果を見ればどのような処理が行われたのかがわかることがあります。

Informationレベルのログで100件のデータを処理することがわかっている場合のことを考えてみましょう。中間ファイルに出力されているデータ件数が90件であれば、91件目に処理したデータで何かがあったことがわかります。

もしログや中間ファイルに何の手がかりもない場合、もう一度同じ処理をデバッグ実行しなければならないかもしれません。これでは障害対応の初動が遅れてしまいます。

処理の細分化

バックエンドシステムでは、障害が起きた場所(以下、障害ポイント)のエラー(データエラー・処理エラー・設計エラーなのかは問わない)を解消したら、障害ポイント以降の処理を再実行するだけで処理が完遂されます。

そのために必要なのが、複雑で大規模なプログラムをひとつ作成するのではなく、細かくシンプルなプログラムをたくさん組み合わせるという設計思想です。

UNIXの設計思想と比べたら多くの職業プログラマは怒ってしまうかもしれませんが、シンプルで堅牢なプログラムでシステムを構成するために、UNIXの設計思想を参考にすることは悪いことではないと思います。

他のシステムからデータを受け取る。
受け取ったデータをクレンジングする(文字コード変換処理なども含む)。
データベースへロードする。
クエリを使ってデータを編集する。

このような小さい処理単位へ落とし込みます。

データを受け取ってクレンジングし、データベースへロードしつつ編集もかける、みたいな処理にはしません。

処理単位が大きいと再実行のコストが大きくなるためです。たとえば、一番最後のクエリを使ったデータ編集処理でエラーとなった場合、そこから処理を実行できるのがよいです。

リカバリも容易にしたい

複雑な最初からやりなおしていたのでは処理時間もかかる上、リカバリも面倒になります。データベースで再ロードするために、一度ロードされてしまったデータベースのレコードをも削除しなければならないかもしれません。

リカバリは手動オペレーションなのでヒューマンエラーが入り込む余地があります。つまり大事なデータが消えることがあるということです。

それよりはエラーとなったデータから残ったデータのみをロードしたほうが楽です。先ほどの例だと91件目でエラーとなったのですから、91件目にエラーとなった原因を解消して、残り10件だけをリカバリさせたいということになります。

これならば、リスクを冒してデータベースのレコードを削除する必要もなくなりますし、削除するとしてもエラーとなった91件目のデータの分だけで済みます。

おわりに

このような感じで大規模システムは障害へ備えています。インフラ的なヘルスチェックであるとか色々な備えもありますが、とりあえずは業務プログラム的な面から必要な備えについて書きました。

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