旅するえんじにあ - Engineers to Travel -

旅するエンジニアの気まま備忘録

【PHP】 APCu apc.shm_sizeを超えるとどうなるか

さてAPCuを使うようになり、まず気になるところがキャッシュと云えど限界はある。 そう、apc.shm_sizeだ。

毎回悩ましいのが、どれくらいの容量を設定していればいいのか。 実際にはttlに設定されている(実際にはプログラム上で明示的に指定するのだけど)時間でクリアされるため そこまで大きなメモリを確保する必要はない、けれども、一体どれくらいを設定したらいいのか。 更に言うと、設定したメモリを超えてしまった時、どういったことが起きるのか。

今回のAPCuはCSVデータを保存しておく(消えても問題ないようなデータ)ことが利用目的です。 どれくらい確保するかについては実際保存するだろうCSVデータなりを運用することを想定として多めに作成し、実際にAPCuに保存した上で容量を確認すれば問題ないかと思っています。 もちろんバッファも考えて多めに確保はします。

問題はその想定を超えて、オーバフローしてしまった時に何が起こるかです。

色々と検索してみたのですが、APCuについての記述が少ないため、実際に行ってみることにしました。

APCuの設定についてはphp.iniに記述をしているため、php.iniのAPCuの設定の apc.shm_sizeを変更していきます。

[APCu]
; add apc
apc.enabled=1
apc.enable_cli=1
apc.shm_size=1M
apc.ttl=3600
apc.gc_ttl=3600

このような形でshm_sizeを1Mと小さめに設定しました。

そして適当に配列を作ってAPCuに突っ込んでいきます。

とりあえず用意したのは以下methodです。

<?php

    // apcuのキャッシュクリア
    public function apcu_clear()
    {
        var_dump(apcu_clear_cache());
    }

    // apcuの情報取得
    public function apcu_info()
    {
        var_dump(apcu_cache_info());
    }

    // apcuへ追加
    public function apcu_test_store()
    {
        $loopCount = 2950;

        for ($i=0; $i < $loopCount; $i++) {
            $key = 'test' . $i;
            $data = [
                $key,
                'hoge' => [
                    'foo' => 'bar',
                    'hogehoge',
                    'true'
                ],
                'hoge2' => [
                    'foo' => 'bar',
                    'hogehoge',
                    'true'
                ]
            ];

            apcu_store($key, $data);

        }
        var_dump(apcu_cache_info());
    }

とりあえず適当にさくっと組んだのでプログラム自体は簡単なものになっています。

まずはapcu_clear_cache()を使ってAPCuを綺麗にします。 そこからapcu_test_storeを使って指定回数分だけデータを突っ込んで、shm_sizeを超えるまでループでデータを入れます。

apcu_storeはkeyが同一であれば上書きを、keyが存在しない場合は追加をします。

$loopCountを使ってapcuにデータを入れていくのですが、2950回データを入れていくと以下のような形でapcu_cache_info()が表示されます。

array (size=13)
  'num_slots' => int 4099
  'ttl' => int 3600
  'num_hits' => float 0
  'num_misses' => float 0
  'num_inserts' => float 1475
  'num_entries' => int 1475
  'expunges' => float 1
  'start_time' => int 1452565478
  'mem_size' => float 967600
  'memory_type' => string 'mmap' (length=4)
  'cache_list' => 
    array (size=1475)
    0=> ...
    ...
    ...
    ...

mem_sizeを見てみるとギリギリ1Mないくらいですね。 cache_listのarray sizeは1475です。

そしてここで一度apcu_clear_cache()して、apcu_test_store methodの$loopCountを2951に設定して再度検証してみます。 すると結果は以下のようになりました。

array (size=13)
  'num_slots' => int 4099
  'ttl' => int 3600
  'num_hits' => float 0
  'num_misses' => float 0
  'num_inserts' => float 1
  'num_entries' => int 1
  'expunges' => float 2
  'start_time' => int 1452565676
  'mem_size' => float 656
  'memory_type' => string 'mmap' (length=4)
  'cache_list' => 
    array (size=1)
      0 => 
        array (size=9)
          'info' => string 'test2950' (length=8)
          'ttl' => int 0
          'num_hits' => float 0
          'mtime' => int 1452565676
          'creation_time' => int 1452565676
          'deletion_time' => int 0
          'access_time' => int 1452565676
          'ref_count' => int 0
          'mem_size' => int 656
  'deleted_list' => 
    array (size=0)
      empty
  'slot_distribution' => 
    array (size=1)
      3049 => int 1

なんということでしょう。 cache_listのarray sizeが1になっています。

そうです。mem_sizeがオーバーフローした結果、今までのデータが吹っ飛んで、一番最後の1つだけが追加されたのです。

ということで、shm_sizeをぎりぎりとかにして運用していると大変なことになります。 基本的には全部同時に入れることはないとは思いますが、どこかでこういったことが起きる可能性はあるということです。

問題はsetしたデータがgetできなくなるだけではありません。

どういう挙動をしているかはわからないのですが、オーバーフローするときにapcu_clear_cacheのようなものが走った時、今は1Mに設定しているデータが消えているだけですが、数Gのデータがクリアされるとなるとパフォーマンスにも大きく影響を与える可能性があります。 APCuの全キャッシュクリアは、同じプロセスの処理をすべてブロックするため、高負荷による影響が出てくると考えられます。

こうならないためにも適切且つ余裕のあるメモリサイズを確保しましょう。