A Day In The Boy's Life -47ページ目

A Day In The Boy's Life

とあるエンジニアのとある1日のつぶやき。

システムの構築が終わり、無事にリリースされても運用のフェーズの中では様々な問題が起こります。

それはプロジェクトの設計で、運用の中で起こるであろう色々なリスクに備え、対策を取っていたとしてもゼロにはなりません。

想定外という言葉で済ませればよいわけではありませんが、全てを予測することはできませんし、1人の作業のミスが大きな問題を簡単に引き起こしてしまったりもします。

 

そういったリスクに備えた対策を講じていくことに対して異論はないのですが、何でもかんでもリスクを取り除くことに注力したり、偏向しすぎているのではないかと感じることもあります。

 

 

全てのリスクに備えよ

 

システム構築の段階では、様々なリスクに備えます。

ハードウェアの障害であったり、データの保全性を担保するためにバックアップを取るでしょうし、オペレーター向けにマニュアルが用意されたりします。

ただ、それらもプロジェクトの予算については限度があるため、幾つか削られるかもしれません。

そうした時にそれに伴うリスクを示していたとして、了解を得たにもかかわらず、そのリスクが顕在化した場合はその再発防止への強い指示が出てきます。

 

この時に「じゃあ、最初からやらせろよ」とか「あの時はそれで良いといったのに」という話ではなくて、「本当にまた次回に備えて対策を打つ必要があるんですか?」という事を冷静に議論したいと感じることがあるわけです。

ちゃんと議論すべきなのは、「運用前のリスク対策では問題ない」という判断をしたにもかかわらず、運用後に問題が出ると「そのリスクへの対策は必要だ」という当初の判断を覆す決定が何故行われるのかというところです。

 

確かに問題の大きさによっては、絶対に対応しないと会社の信用に関わるというものもあるでしょう。

ただ、そこまで大きな問題ではないにしろ、何らかのリスクがそれに伴って発生するケースであれば、全てのリスクに備えよと言う、トラブル前後の温度差に疑問を持つことがしばしばあります。

この辺の温度差を一番感じたのは昨年の3.11だというエンジニアも多いかもしれません。

 

震災直後は、DR(Disaster Recovery)というのはホットな話題になりましたが、1年も経たないうちにのど元を過ぎてしまった感があります。

大震災に見舞われた際にBCP(Business Continuity Plan:事業継続計画)という観点で、必要最低限なシステムでも会社の基幹となるものに関しては何らかのDR対策が取られたかもしれませんが、売り上げの中核を担うシステムでもコスト面からその対象から外れたり、「しばらくは起きないだろうから、もう少し時間をかけて対応しよう」とか「前回どうにかなったから大丈夫でしょう」というように、時間が経つとともにその対策プロジェクトはそのまま収束してしまったというケースも多いのではないでしょうか。

 

震災直後は、全てのリスクに備えた方が良いという考えを持つ人が多かったのが、平常運転に戻れば何も言わなくなってしまうというのは、振り返ってみるとあのときの判断基準はどこから来たのかと、振り回された徒労から猜疑心が出てくるわけです。

 

トラブル時は盲目

 

トラブルが起きた状況下では、人は焦燥感から冷静さを失いやすいですから、対応への判断基準があやふやになったり、考えが変わるということはわからなくも無いのですが、起きたトラブルと影響ばかりを考えるのではなく、そのリスク自体を分析する必要があって、それによって本当にそのコストに見合う対策を取るのかということを議論しないといけないんじゃないかなと思うわけです。

また、リスク自体の分析をしてそれを分散させたり、あえて保有しておくというのも選択肢としてあるのに、全てリスクは潰せという判断に持ち込まれるのは、現場にいたら結構疑問なときもあります。

 

その乖離した答えに行き着く理由は、トラブルが想定外のものであったり、想定していたよりも大きな問題となってしまったというものであったり、リスク対策への見積もりがあまかったり、サービスレベルの問題であったり、単に責任者が上から煩く言われたがためにリスクを課題評価しすぎているというものであったりするかもしれません。

まぁ、大体この辺の理由は数珠繋ぎになってたりして絡んでるものなんですが・・・。

 

ですから、前もってリスク基準をしっかりと定義しておくのと、定期的な見直しというのが必要になってくるでしょう。

リスクを共有しておくことで有事の際に所定の対応ができるでしょうし、冷静な判断もできます。

ある意味、「これは想定内だからデータロスは致し方ないこと」という判断ができるかもしれません。

それがよいかどうかはきちんとリスク管理の中で議論すべきことですし、予算や体制の中で限界が決められてくることではあります。

また、あえて対応しないということも時には必要かもしれません。

 

先に書いたように震災の直後はかなり息巻いていたものが、数ヶ月で一気にトーンダウンすることがあるように、有事と平時ではその人の判断にも大きな乖離がでてきますので意図的に時間をかけてみて様子をみてみるのもよいかもしれません。

最初は「投資を惜しまない」といっていたものが、急に「お金が無いから対応が難しい」という決定に変わったりしますからね。

 

トラブルは毎日起きるわけではないですが、否応無くその繰り返しにより運用は常にリスクへの対策をすることに大きな時間をとられていきます。

運用の現場でも、常に次のステップへ今のシステムをブラッシュアップしていくという目標があるでしょうから、システムトラブルへの対策ばかりを講じていたら進むに進めない状況になります。

これも一つのリスクにはなってきます。

そのリスクの影響具合や対策を打つことの効果というものをしっかりと判断して、「何でもかんでも対応しろ」というのではなく、今そのリスク対策を行うか否かを決めていく必要があるのではないでしょうか。

 

 

 

 

動的なプログラムでコンテンツを管理している場合、HTTPヘッダを制御できるのでキャッシュコントロールがしやすいですが、静的なHTMLだけの場合はキャッシュの制御がかなり難しい状況になります。

HTMLの場合、METAタグにcache-controlやExpiresを入れるやり方が一般的ですが、この方法はブラウザによって動作にばらつきが出たりそもそも動作が安定しなかったり、プロキシなどを介するN/W上ではMETAタグは無視されるのでうまくキャッシュを制御することができません。


今回の内容は、コンテンツをキャッシュさせるというやり方ではなく、静的なコンテンツしか動いていないWebサーバーという状況下で、コンテンツをキャッシュさせたくない場合の対応方法を書いていきます。



Apacheからキャッシュを使わないことを強制する


先ほども書いたように動的なプログラムを扱っている場合はHTTPヘッダを制御できますが、静的なHTMLだけではHTTPヘッダを制御することができません。

この場合、例えばWebサーバー上のページを更新した場合とかにキャッシュが使われているクライアントとそうではないクライアントが出てきたり、一部のコンテンツだけキャッシュが聞いてしまってレイアウトが崩れてしまったりという現象を引き起こしてしまいます。


こういうときに、Apache側からHTTPヘッダを制御して強制的にキャッシュを使わないようにコントロールすることができたりします。

この設定は、Apacheのモジュールであるmod_headers を利用します。

まず、Apacheの設定ファイル(httpd.conf)に、下記のようにモジュールがロードされているかを確認します。


LoadModule headers_module modules/mod_headers.so

キャッシュをさせない設定方法は、httpd.conf(または、.htaccessを配置)に下記のように記載します。


FileEtag None
RequestHeader unset If-Modified-Since
Header set Cache-Control no-store


設定内容は上から、Etagをつけないようにし、リクエストヘッダに含まれるIf-Modified-Sinceの値をクリアし、レスポンスヘッダにCache-Controlに「no-store」をつけて送信するというものです。


Cache-Controlにno-chacheを指定した場合は、キャッシュされる可能性があるので注意。

結構、no-chacheという記述をキャッシュさせないようにするという風に解説している記事がありますが、正確にはno-chacheでもキャッシュが有効かどうかはWebサーバー側に問い合わせられ、EtagやIf-Modified-Sinceなどによりキャッシュが有効と判断されてしまう場合があります。


参考: プロキシキャッシュ対策 @ IPA ISEC セキュア・プログラミング講座


なので、Cache-Controlにno-storeを指定してキャッシュするなということをレスポンスヘッダに加えています。

逆に、キャッシュの判断基準を潰すという意味では、EtagとかIf-Modified-Sinceだけを指定していてもキャッシュが効かなくなるような気はしますが、優先順位がブラウザによって異なってきたりするみたいなので念のために全て指定しておいたほうが安全かもしれません。


今回の設定内容では、全コンテンツに対して適用されてしまうので、個別にJavaScriptファイルやCSSファイルなど対象を決めて設定したい場合は下記のようにしておきます。


<Files ~ "\.(css|js)$">
    FileEtag None
    RequestHeader unset If-Modified-Since
    Header set Cache-Control no-store
</Files>


あとは、HTTP通信の状況を確認できる状況から実際にキャッシュがされていないか(HTTPステータスコードが304ではなく200になっているか)を確認してみましょう。


A Day In The Boy&#39;s Life-HTTP通信でキャッシュが使われていないか確認


今回の内容は、静的コンテンツしか扱えない状況という前提ですので、動的なプログラムを使っている場合は、そこから出力されるHTTPヘッダの状況もみて変更した方がよいでしょう。


それと、この設定によりキャッシュが使われなくなるということはサーバー側の負荷やクライアント側の動作にも多少なりとも影響が出ることにはなるので、コンテンツを更新する前後など期間を決めて運用するという方がよいかもしれません。





CakePHP2系でキャッシュを使いたい場合、ファイルキャッシュやメモリキャッシュなどいくつかの方法で実現することができます。

サイトの規模が大きくなってきてパフォーマンスを考慮するようになってきたら、キャッシュの導入を検討してみてもよいかもしれません。



CakePHPでファイルキャッシュを利用する


CakePHPのファイルキャッシュ(Viewキャッシュ)はデフォルトで提供されているキャッシュの仕組みで、その名の通りファイルにデータを一時的に保存して利用する仕組みです。

コントローラで様々な処理をし、テンプレートと組み合わせて出力する最終的なHTMLファイルをキャッシュさせることで、パフォーマンスを向上させるというようなことができます。

厳密に言うと、静的なHTMLを吐き出してキャッシュさせてというのではなく、CakePHPの構造上コントローラを通してキャッシュファイルが読込まれるため、コントローラがすばやく処理できるように必要最低限の処理をHTMLにくっつけて出力します。


肝心のファイルキャッシュの利用方法ですが、まずはキャッシュを利用する設定を行います。

設定変更するのはcore.phpです。


Configure::write('Cache.check', true);


デフォルトでは、コメントアウトされているので解除します。

次に、キャッシュを利用したいコントローラファイルを開きます。


<?php

App::uses('AppController', 'Controller');

class PagesController extends AppController {

    public $name = 'Pages';
    public $helpers = array('Common', 'Cache');
    public $cacheAction = TRUE;

    public function foo() {
    }
}


コントローラファイルでは2箇所編集し、1つ目はキャッシュのヘルパーを利用できるように$helpersの配列にCacheを追加、もう1つは$cacheAction変数にTRUEをセットし、このコントローラでキャッシュを生成することを定義します。


基本的には、この設定でキャッシュが生成されるようになります。

作られるキャッシュファイルは、以下のキャッシュディレクトリに保存されます。


$ ls -la /path/to/cakephp/app/tmp/cache/views/
合計 16
drwxrwxr-x 2 apache apache 4096 10月 18 04:47 .
drwxrwxr-x 5 apache apache 4096  7月  1 15:55 ..
-rw-r--r-- 1 apache apache 4297 10月 18 00:15 pages_display.php

キャッシュファイルは、「コントローラ名_アクション名_パラメータ.php」という形式で作られます。

もし特定のアクションを通ったときだけキャッシュを作りたい場合や、キャッシュの有効期限を細かく設定したい場合は、下記のようにコントローラーの$cacheActionに指定することで制御することもできます。


<?php

App::uses('AppController', 'Controller');

class PagesController extends AppController {

    public $name = 'Pages';
    public $helpers = array('Common', 'Cache');

    public $cacheAction = array("foo"  => "1 day",
                                "bar"  => "1 hour",
                                "hoge" => "3 minutes");

    public function foo() {
    }

    public function bar() {
    }

    public function hoge() {
    }

    public function fuga() {
    }
}


上記の場合、fooアクションのキャッシュは1日に、barアクションのキャッシュは1時間、hogeアクションのキャッシュは3分間、そして指定していないfugaアクションはキャッシュが行われません。

細かくキャッシュをする・しないを切り分けられるので、それぞれの処理内容に応じて変えることができます。


これは、ファイルキャッシュのやり方になるので、テンプレートファイルを加工した最終的な結果がキャッシュされます。

ですので、テンプレートをいじったりコントローラ内の処理を変えてもキャッシュが有効期限内であれば当然前回と同じ内容が表示されてしまうので注意が必要です。

キャッシュを無効にしたい場合は、キャッシュディレクトリの中身を消してしまうか(再度アクセスしたらキャッシュが作られますが)、core.phpで変更した「Cache.check」をコメントアウトするか、FALSEに変更します。



CakePHPでメモリキャッシュを利用する


例えば、DBから何度も同じ値を取得するような場合は、その値をキャッシュさせておきたいという場合があります。

こういった場合に、そのキャッシュデータをメモリ上に保存しておき、いつでも書き込み・読み込みができるようにしておければ、パフォーマンス的にもかなりメリットが出てきます。


CakePHPでメモリキャッシュを実装する方法は、幾つかのキャッシュエンジンを選択することができますが、ここではAPC(Alternative PHP Cache) を使うやり方を書きます。

他のキャッシュエンジンに関しても、導入さえすればおそらく同様のやり方で扱えると思います。(設定方法などは、CakePHPの設定ファイルであるbootstrap.phpも確認してみてください)


まず、APCをメモリキャッシュのエンジンとして利用する方法(APCの導入方法は割愛します)ですが、bootstrap.phpにて利用するキャッシュエンジンをAPCで動かすように設定します。

デフォルトは、ファイルキャッシュになっているため、そのまま使っていると思っていたパフォーマンスのメリットは得られないので注意が必要です。


Cache::config('default', array(
    'engine' => 'Apc', //[required]
    'duration'=> 3600));


bootstrap.phpには、各キャッシュエンジンごとの設定ファイルのサンプルがあるので、それをそのままコメントアウトを解除して使うのが手っ取り早いと思います。

ちなみに、durationはキャッシュの有効期限(秒)です。

次に、デフォルトの設定になっているファイルキャッシュを無効にする(コメントアウト)しておきます。


//Cache::config('default', array('engine' => 'File'));


あとは、キャッシュの書き込みや読み込みとして使えます。


<?php

App::uses('AppController', 'Controller');

class PagesController extends AppController {

    public $name = 'Pages';

    public function display() {
        if (($str = Cache::read("cache_str_name")) === FALSE) {
            $str = "no cache";
            Cache::write("cache_str_name", $str);
        } else {
            $str = "cache";
        }
        $this->set("str", $str);
    }
}


Cache::readでキャッシュの中身を読込み、Cache::writeでキャッシュへデータを書き込みます。

それぞれ、第一引数で指定しているのはキャッシュ名です。

キャッシュは設定ファイルで指定したdurationの秒数だけ有効になりますが、意図的に削除したい場合はCache::delete("cache_str_name");のように指定して削除もできます

今回はAPCでメモリキャッシュしているので、Apacheを再起動してもキャッシュデータはクリアされてしまいます。


ここまでは、メモリキャッシュのお話でしたが、APCなどのキャッシュエンジンを導入していなくても、ファイルキャッシュとして同様にデータを一時的に保存することも可能です。

これは、デフォルトのキャッシュエンジンをそのままFileにして利用するだけです。

この場合、キャッシュディレクトリの直下にキャッシュファイルができ、キャッシュの読み込みはのこのキャッシュの中身をunserializeして復元されます。


/path/to/cakephp/app/tmp/cache/cake_cache_str_name

中身は、下記のようになっています。


1350831674
s:8:"no cache";


APCのようなキャッシュエンジンを使っていても使っていなくてもプログラム自体の処理は変わりません。

逆に、APCを導入していてメモリキャッシュされているつもりがファイルキャッシュで動いててパフォーマンスが出ないというようにはまってしまう可能性はあります。

そうした場合は、きちんとキャッシュの設定を確認しておいた方がよいでしょう。

キャッシュの設定の確認は、設定ファイルを確認する以外に、以下のようにして設定を見ることができます。


$config = Cache::config('default');
[ 出力例 ]
array(2) {
  ["engine"]=>
  string(3) "Apc"
  ["settings"]=>
  array(5) {
    ["engine"]=>
    string(3) "Apc"
    ["duration"]=>
    int(3600)
    ["probability"]=>
    int(100)
    ["prefix"]=>
    string(4) "app_"
    ["groups"]=>
    array(0) {
    }
  }
}


また、かなり余談ではありますが、利用するキャッシュエンジンをコントローラーの中で切り替えて利用することもできます。


<?php

App::uses('AppController', 'Controller');

class PagesController extends AppController {

    public $name = 'Pages';

    public function display() {
        Cache::config('apc_cache', array(
                      'engine' => 'Apc',
                      'duration'=> 100,));
        if (($str = Cache::read("cache_str_name", "apc_cache")) === FALSE) {
            $str = "no cache";
            Cache::write("cache_str_name", $str, "apc_cache");
        } else {
            $str = "cache";
        }
        $this->set("str", $str);
    }
}


Cache::configで、任意の識別子(apc_cache)に利用するキャッシュエンジンを指定し、Cache::readの第二引数やCache::writeの第三引数でその識別子を指定して利用します。

デフォルトで、ファイルキャッシュを利用していてもこの識別子の指定があれば、その部分だけがAPCでメモリキャッシュが動作するという具合です。