まだプログラマーですが何か?

プログラマーネタとアスリートネタ中心。たまに作成したウェブサービス関連の話も http://twitter.com/dotnsf

タグ:cache

ふとしたきっかけがあって PWA(Progressive Web Application) を作る機会がありました。今回は PWA の中でも特にキャッシュ機能を試すことが主目的だったのですが、最低限の機能を実装して公開してみたので、興味ある方は以下を参照して使ってみてください。


【PWA とは】
主に Google 主導で進められているモバイル端末向けウェブアプリケーションの技術で、「モバイル端末でウェブアプリケーションを利用する際に、ネイティブアプリであるかのような UI や動作を可能にする」ことを主目的としています。

なお PWA 自体の説明をすることが本ブログエントリの目的ではないし、そこまで含めてしまうと長くなりそうなので、興味ある方は適当にググってください。


【アプリの内容とシステム構成】
PWA には特徴的な機能がいくつかありますが、今回は特に「キャッシュ機能」を確認するためのアプリケーション実装を行いました。実際のアプリ(パズルゲーム)を使うにはスマートフォンのブラウザで以下の URL にアクセスしてください:
https://pwa-slidegame.mybluemix.net/


正しくアクセスすると以下のような画面が表示されます:
2020020301


このままアプリの実行を続けてもいいのですが、せっかくの PWA なので「インストール」してみます。メニューから「ホーム画面に追加」を選びます(下図は iPhone Safari での場合):
2020020302


正しく追加できるとホーム画面に以下のようなアイコンが追加され、次回からはここをタップすることで同じアプリを起動できるようになります。というわけでこちらをタップします:
2020020303


先程に似た画面が表示されます。ただアドレスバーなどは表示されておらず、PWA として起動してネイティブアプリっぽく起動しています:
2020020304


「(カテゴリーを選択)」と書かれた部分をタップすると、この後選択する画像のカテゴリー一覧が表示されます。実際には色々なカテゴリーがあっていくつか試すこともできるのですが、キャッシュの説明のため以下は「こども」カテゴリーを選択したものとして(それ以外のカテゴリーは選択していないものとして)説明を続けます:
2020020305


カテゴリーを選択すると、「いらすとや」の選択したカテゴリーの画像がカルーセル表示されます。画像を右や左にフリックすることで次の画像や前の画像を表示できます:
2020020306


カルーセルから「パズルにしたい画像」を選んでタップします。では↓の画像を選んでタップしたものとして以下を続けます:
2020020307


すると選択した画像が 4x4 のスライドパズル化され、実際に遊ぶことができるようになります:
2020020308


(簡単なパズルではないのですが・・)パズルを進めていって、完成すると、・・・
2020020309


パズル完成までに要した時間と動かした回数が表示されます:
2020020310


続いて過去の(回数の少ない)トップ10が表示されます。直近の記録がこの中に含まれていれば強調表示される、というゲームです:
2020020311


ここまではネットワークに接続された状態で行いました。PWA ではデータをキャッシュすることで、オフラインでもある程度の利用が可能になります。その部分を試してみましょう。この時点で起動していた PWA アプリをいったん終了(タスク一覧から終了)しておいてください。

まずはスマホをオフライン状態にします。WiFi もモバイルデータも接続されていない OFF の状態に設定します:
2020020312


あらためてホーム画面のアイコンからアプリを起動します。先程起動した時に表示されたトップページは自動的にキャッシュされていて、ネットワーク接続がなくても表示され、カテゴリー選択状態になります:
2020020313


カテゴリーとして「こども」を選択した場合も先程の実行時にキャッシュできているので、ネットワーク接続がないにも関わらず画像含めて表示することができています。なお、ここで表示されているのはあくまでキャッシュに含まれている画面であって、ネットワーク接続時の最新状態が表示されているわけではありません:
2020020314


しかし先程選択しなかったカテゴリー(例えば「職業」)を選択すると、こちらの内容は接続状態でのキャッシュが存在していないため、カルーセルの中身を取得することができず、何も表示されない、という状況になります。ある意味でキャッシュが有効に動いていることが確認できます:
2020020315


またオフラインでもパズルクリアまではできますが、その後のデータ記録目的での HTTP POST には失敗します:
2020020401



といったオフライン時にキャッシュ済みのデータを使って一部動かすことができるようになるのが PWA のキャッシュ機能です。


この PWA アプリケーションは以下のようなシステム構成となっています。構成そのものはウェブアプリケーションのシステム構成そのもので、この中で「いらすとや」のページをリアルタイムにスクレイピングしてカテゴリー一覧やカテゴリーの画像一覧を取り出して、アプリケーションの中で利用しています。アプリケーションそのものにキャッシュの機能は含まれていません:
2020013101



【PWA でのキャッシュの実現方法】
アプリケーションを PWA 化する際にいくつか設定のためのファイルを追加するのですが、それらの準備が完了していると、以下のような仕組みでキャッシュが(自動的に)実現されます。

まず、キャッシュデータが全くない状態でオフライン操作することはできません。画像とか API の実行結果とか以前に、トップページの HTML がないと何も表示できません。つまりオフラインでキャッシュ表示するためにはオンライン時にある程度の情報をキャッシュしておく必要があります。PWA では オンライン時に HTTP GET する際のパスとその結果を自動的にキャッシュします。上述の説明で行った流れをキャッシュの中身を意識した上で再度理解しておきましょう:
2020013102


そしてスマホがオフラインとなった後に PWA を起動すると、キャッシュの中身を使いながらオフラインで動作します:
2020013103


ただ(オフラインなので当然ですが)キャッシュにない HTTP GET は使えませんし、HTTP POST でデータをサーバーに送信することもできません:
2020013104


このアプリケーションのソースコードはこちらで公開しています。Node.js 上で動かすことができますが、PWA として稼働させるには HTTPS アクセスが必須となります。実際に自分のサーバーで PWA として動かす場合は HTTPS アクセスできるサーバーへデプロイしてお使いください:
https://github.com/dotnsf/pwa-slidegame


PHP の MVC フレームワークの1つである CakePHP は、デフォルト設定の場合はその内部キャッシュにファイルシステムが使われています。

もちろんこれでも動くのですが、このキャッシュを memcached(メモリサーバー)にすることで、比較的簡単に高速化を実現することができます。以下は CakePHP 2.x を使う前提ですが、キャッシュに memcached を利用する方法を紹介します。


まずは memcached と、PHP から memcached を利用するための pecl-memcache をインストールしておきます:
# yum install memcached php-pecl-memcache -y

このコマンドが失敗する場合は yum リポジトリが足りていない可能性が高いので、以下のコマンドを実行してから再度 yum install してみてください(RHEL 6.x x86_64 の場合):
# rpm -Uhv http://ftp.riken.jp/Linux/repoforge/redhat/el6/en/x86_64/rpmforge/RPMS/rpmforge-release-0.5.3-1.el6.rf.x86_64.rpm

memcached と pecl-memcache がインストールできたら memcached を起動し、httpd を再起動します:
# /etc/init.d/memcached start
# chkconfig memcached on
# /etc/init.d/httpd restart

これで memcached の用意ができました。では CakePHP 側の設定を変更して、この memcached を利用するようにしてみましょう。app/Config/core.php ファイルを編集し、以下の設定を加えます:
# vi app/Config/core.php

  :
  :
//. デフォルトのキャッシュ設定(Cache::config で始まっている設定)が有効になっていたらコメントで無効にする //. Cache::config( "default", array( "engine" => "File" ) );
:
: //. デフォルトでは memcached にキャッシュするよう設定を追加 Cache::config( "default", array( "engine" => "Memcache", // 保存先は memcache "duration" => 3600, // キャッシュの有効時間は3600秒 "probability" => 100, // キャッシュ再作成率は100%(全て) "prefix" => Inflector::slug( APP_DIR ) . "_", // キャッシュ名のプレフィクスは定数 APP_DIR + "_" "servers" => array( "127.0.0.1:11211" ), // memcached サーバーとポート番号 "compress" => false // キャッシュは圧縮しない ));
: :

では現在までに作成されたキャッシュを全てクリアします:
# cd app/tmp/cache
# rf -rf ./*

この後に CakePHP を使ってみて、app/tmp/cache 以下にフォルダが作成されていないことが確認できれば成功です。

cakePHP を使って業務アプリを開発しています。

ある程度使っていると色々なトラブルに遭遇しますが、タチが悪いのが「特定の環境でのみ障害が再現する」というパターン。開発環境やテスト環境では動くのに、本番環境では動かない、というやつです。

そんな現象の1つに遭遇しました。現象の特徴は以下の2つでした:
(1) DB の設計を変更(alter table)した後の内容が Model に反映されない
(2) 本番環境でのみ再現する

(1) の内容は、例えば以下の SQL で作成したテーブルがあったとします:
create table users( id int primary key auto_increment, name varchar(100), deleted int default 0, created datetime, updated datetime );

この時点で id, name, deleted, created, updated という5つの列が含まれたテーブル users が作成されます。

このテーブルのための User モデルを cakePHP に(bake コマンドなどで)用意して、アプリを開発します。


その後しばらくしてから、この SQL でテーブルに列を追加します:
alter table users add column email varchar(256) after name;

最終的には id, name, email, deleted, created, updated という6つの列が含まれたテーブル users が定義されていることになります。

モデル(Model/User.php)は変更する必要がないので、このまま使っていれば変更後の6列の値が含まれる配列変数のモデルとして使えるはずです。


が、この alter table が反映されず、Controller の中で取得したこの User モデルの配列変数には5つの値しか入っていない、というものです。


(2) の内容は、この現象がローカル環境やテスト環境などでは再現せず、よりによって本番環境でのみ再現する、という現象を意味しています。 テスト中には全く気づくことができず、本番環境を使っている利用者からの報告で気づく、という最悪のパターン。。。



今回、このような現象に遭遇した上で最終的に解決することができました。原因とその対処法を紹介します。

結論を先に言うと、キャッシュが原因でした。話をややこしくしているのは(多くの本番環境がこの設定だと思いますが)cakePHP ではデバッグモードが 0 の場合にモデルのキャッシュが長い時間(999日間)有効になる、ということです。キャッシュが有効になっている間はモデルの設計変更が反映されません。その結果、デバッグモードを無効にした本番環境でのみモデルは古い設計のまま動いてしまい、他と異なる挙動になる、という発生パターンになったのでした。

解決策としてはキャッシュを削除します。今回の例であれば users テーブルのキャッシュを削除することで強制的に更新させます。問題のファイルは app/tmp/cache/models 内の myapp_cake_model_*****_users というファイルです。まずこのファイルを削除します。

続いて、app/tmp/cache/persistent/myapp_cake_core_method_cache ファイルも削除します。これで新しいテーブルの内容をモデルに反映させることができました。


いや~、こんなトラブル、事前に想定するのが難しいですよね。でもこれを経験したことで自分の cakePHP レベルが1つ上がった気がします。


(参考サイト)
http://culdesac.verse.jp/?p=19

 

このページのトップヘ