APCの動作 3 設計と設定

前回前々回からの続き。

APCの設定というと、FacebookでのAPC使用方法を紹介したapc@facebookがとっても有用。
内容については、id:i_ogiさんがまとめられているfacebookでのAPCの設定というエントリーを読むのがおすすめ。


といっても、APCを使うサイト全てがFacebook並みの規模があるわけでもないので、apc@facebookを横目で見ながら目の前のサイトに合った設定を探すのだ。(Userキャッシュは使ってないので無視だ)

apc.shm_size

使用するメモリのサイズ。キャッシュ対象の全ファイルがキャッシュされた状態でも余りが出るように設定する。
ただし、1KBのファイルが1KBのメモリを消費するわけではないので注意。
目安としては

 クラス > 関数 > ベタ書き処理 > テキスト

の順で元よりサイズが大きくなる。元のファイルサイズの大体5〜6倍程度で計算すれば良いと思う。
実際のサイズはapc.phpapc_cache_info()で確認できる。


割り当てられるメモリが少ないとか、PHPの量がとんでもないという場合にはapc.filtersを使って除外してみる。呼び出しが多い共通のクラスはキャッシュして、場末のSmartyコンパイルキャッシュは外すなど。
そのためには正規表現が書きやすいようなディレクトリ構成やファイル命名規則にしておく必要がある。


メモリの割り当てが少ないとexpunge(Cache full)が発生してしまう。
expungeはパフォーマンスに悪影響を与えるので可能な限り避ける。

apc.num_files_hint

ちゃんと数えて設定する。この手の数字は素数だとハッシュの衝突が少ないらしいけど、2倍されてしまうならどうやっても素数にはならない気がする。

apc.stat

これが一番悩ましい。
offにした方が通常時のパフォーマンスは良いのだろうけど、管理上の問題がある。


offの場合はファイルの更新をした後にサーバの再起動かapc_cache_clear()を実行する必要があるけど、そうすると全てのキャッシュが消えてしまう。
キャッシュが消えた後で一気にアクセスが来ると、各プロセスがキャッシュを作ろうとしてあんまり良くない*1
それに、滅多に呼ばれないファイルを更新しただけの場合でも、必ず呼ばれるファイルのキャッシュを消さなきゃならないのも嬉しくない。


サーバを切り離してapc_compile_file()する(Facebook方式)というのも手だけど、そこまでしたくないし、ファイル数が多いと現実的じゃないし*2、更新が頻繁にあるサイトだと苦しいし。


だったらonにしておけば良いかというとそうでもない。
ファイルの更新にrsync,svn upなどを使っている場合、キャッシュを消さないとどんどんメモリの使用量が増えてしまい、いつかexpungeが発生してしまう。
また、Smartyを使用していてコンパイルキャッシュをAPCでキャッシュしている場合も、テンプレートファイルを更新する度に*3メモリ使用量が増えてしまう。


折衷案としては、statはonにし、アクセスが少ない時間にapc_chache_clear()をGC代わりに実行するなど。
ちなみに、logrotateでApacheにHUPを送るとキャッシュがクリアされるよやったね!


onにする場合はshm_sizeに更なる余裕を。

apc.ttl

stat=offの場合は0でOK。
stat=onの場合は、expnugeが発生した場合に備えて大き過ぎず小さ過ぎずの値にしておくのが良いと思う。具体的には1〜3時間くらい。



APCについてはこれでおしまい。

*1:apc.slam_defense

*2:apc@facebookによるとFacebookのnum_files_hintは100

*3:更新されているか確認する設定の場合

APCの動作 2 キャッシュの削除

前回からの続き

どんどん内容が怪しくなってきているので、引き続きツッコミは歓迎しておりまする。

削除

  • apc_cache_expunge キャッシュの片付けを行う関数
    • メモリの確保に失敗すると呼ばれる
    • 呼ばれるとapc.phpでの「Cache full count」がひとつ増える
      • ttlが定義されていない(0)
        • キャッシュを全てremove_slotで削除する
      • ttlが定義されている
        • アクセス時刻からttl経過していたらremove_slotで削除する
    • 削除するキャッシュはシーケンシャルに探す 多分O(n)
  • remove_slot
    • キャッシュをdeleted_listに追加する
    • 実際の削除は後でまとめて行う*1
    • 色んな場所から呼ばれる
      • apc_cache_clear
      • apc_cache_expunge
      • apc_cache_insert
      • apc_cache_user_insert
      • apc_cache_find_slot
      • apc_cache_user_find
      • apc_cache_user_delete
  • process_pending_removals
    • deleted_listを走査する
      • ref_countが0以下
      • 削除されてからgc_ttl経過している
    • いずれかの条件に当てはまるキャッシュを削除する
      • キャッシュをfree_slotに渡して実際に削除する
    • キャッシュを追加する際に呼び出される
      • apc_cache_insert
      • apc_cache_user_insert

メモ

  • ref_countの役割がよくわからない
  • expungeは必ずしもキャッシュを削除するわけではない
    • ttlが大きかったり全てのキャッシュへのアクセス頻度が高い(メモリの割り当てが少なすぎる)場合は削除されない可能性が高い
      • PHPファイルが読み込まれる度に無駄なループがグルグル回る

*1:即座に行う場合もある

APCの動作 1 キャッシュの取得と生成

APCの動作について調べる必要があったので、調べた際のメモを公開。

今回はキャッシュの取得や格納部分について。元気があればキャッシュの削除等についても公開するかも。
設定によってどういった動作をするのかがメインなので、Opcode周辺には触れない。というかよくわからないので触れられない。
内容はAPC-3.0.14をLinuxで動かした場合を想定。
内容についてのツッコミ大歓迎。

初期化

  • apc.num_files_hintはキャッシュされるファイル総数のヒント
    • この値を2倍した数(num_slots)だけslot(1件のキャッシュをあらわすデータ)の領域が確保される
    • ただし、この数がキャッシュ数の上限ではない

キャッシュの取得と生成の動作

  • zend_compile_file関数を上書きしたmy_compile_file関数が呼ばれる
  • apc_cache_make_file_key キャッシュ検索用にapc_cache_keyを作る関数
    • stat=on
      • type: APC_CACHE_KEY_FILE
      • path_translatedと与えられたファイルのパスが同一(URLに対応するファイル)の場合はサーバ(Apache)のstat結果を使用する
      • 同一ではないが絶対パスの場合はstatする
      • どちらでもない場合はincludeパスから探してstatする
      • max_file_sizeを超えないかのチェックもここでする
      • キャッシュの識別はデバイスIDとinode番号
    • stat=off
  • apc_cache_find -> apc_cache_find_slot
    • APC_CACHE_KEY_FILEの場合
      • (デバイスID+inode番号)%num_slotsでslotの配列からslotを取得する
    • APC_CACHE_KEY_FPFILEの場合
      • string_nhash_8(絶対パス)%num_slotsでslotを取得する
    • slotが探しているファイルのキャッシュかを調べる
      • APC_CACHE_KEY_FILEの場合
        • バイスIDとinode番号が一致するか
        • 更新日時が一致しない場合はキャッシュを削除
      • APC_CACHE_KEY_FPFILEの場合
    • 探しているファイルのキャッシュではない(衝突)場合は次のslotを調べる(slotはリンクリスト)
    • キャッシュが見つかった場合は処理を終了
    • キャッシュが見つからなかった場合は生成処理へ
  • apc_cache_insert
    • apc_cache_find_slotと同じ方法でslot取得
      • slotが見つかった場合
        • APC_CACHE_KEY_FILEの場合
          • バイスIDとinodeが一致すれば削除
          • 一致しなければttlに従い削除
        • APC_CACHE_KEY_FPFILEの場合
          • 絶対パスが一致すれば削除
          • 一致しなければttlに従い削除
    • キャッシュを生成しslotに格納

メモ

  • stat=onの場合はデバイスIDとinode番号でキャッシュを識別するため、例えばファイル名を変更してもinode番号が変わらなければ同じキャッシュを使い続ける事になる
    • 逆に言うと、ファイルパスが一緒でもinode番号が変われば別のキャッシュとして扱われる
      • rsyncsvn upはアトミックなファイル操作を行うため、更新されるファイルのinode番号が変わる
      • 更新されたファイルの分だけキャッシュ使用量が増える(古いキャッシュはアクセスされないまま残る)
  • max_file_sizeはstat=offの場合無視されるっぽい
  • PHPでrequireとかに渡すパスは絶対パスじゃないと相当遅くなる
  • stat=offの場合はincludeパスを調べてくれないの?
  • stat=onの場合ハッシュの計算方法は(デバイスID+inode番号)%num_slots
    • TECHNOTES.txtによればrealpathを呼ぶと遅くなるのでinodeを使っていると書いてあるけど、なんでデバイスIDと足しているのか不明
  • apc.num_files_hintが少なすぎる場合は微妙に遅くなる
  • ttlに達したからといってそのキャッシュが使われなくなるわけではない

NEC Aterm WL5400AP交換

NEC無線LANアクセスポイントに不具合が見つかったそうで、家のが該当する機種だったのでモクモクする前に交換の申し込みをしてみた。

18日の夜にWebから申し込みをしたら、21日の午前中にクロネコで交換機が届いた。到着まで時間がかかるみたいな事を書いてあった気がするけど、意外と早い。

とりあえず、新旧並べて写真を撮ってみる。

古いAPの管理画面をScrapBookで保存して、それを見ながら新しいAPを再設定。意外と簡単。
古いAPを返送用封筒に入れ、ドナドナを歌いながらヤマトの営業所へ。なんか切ない。

mod_auth_hmacのBeta版リリース

ちょっと前に、ようやくmod_auth_hmacのBeta版がリリースできた。
同時にプロジェクトホームページの内容も増やし、使用する上で必要となる最低限の情報は載せたつもり。

実はApacheのモジュール自体は4月頃から手を付けていなくて、クライアントのスクリプトとかドキュメントが一通り揃ったという事でBeta版にしたのだ。


クライアントスクリプトPHPに加え、RubyPython用のクラスも作ってみた。RubyPythonもまとも(?)に書くのは初めてなので楽しい。RubyにはHMACを計算する標準的なクラスが無いらしいMacの1.8.2では見つからないので再発明。

時刻を表現する部分にUNIXタイムスタンプでは無くRFC 1123形式*1を使用しているのは、UNIXタイムスタンプをすぐには取得できない環境*2への優しさだったのだけど、Pythonにはどうやら逆効果だったみたいだ。
Pythonタイムゾーンを扱う時はアプリケーションが責任を持って処理を行わなければならないらしく、ローカルの時刻からUTCに変換するのが少々面倒。ツンツン言語め。

*1:例えば Sun, 01 Jul 2007 22:39:52 のような形式

*2:ASP.NETとか

SET NAMES

大垣さんのblogより

SET NEMESだと文字エンコーディングを利用したSQLインジェクションに脆弱となる可能性があります

http://blog.ohgaki.net/index.php/yohgaki/2007/06/05/php_5_2_3a_oa_oa_fa_s

工エエェ(略)
知らんかった…


SET NAMESを実行するとMySQLのサーバとクライアントでエスケープの処理が一致しなくなる場合があるという事だと思うけど、何でそんな仕様なんだろう。