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

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

【PHP】 filemtime関数の罠

APCでデータのキャッシュをさせつつ、色々とロジックを組んでテストしている最中にテストが通らなかったのでメモ

やりたかった事としては

・ファイルをオープンして配列化 ・配列化したデータをAPCにキャッシュ ・キャッシュさせたデータにファイルの変更日付を持たせる ・ファイルが更新されたらファイルオープンして配列化してデータをAPCに上書き

こんな処理をするmethodを作成しました。

このテストの一部に以下のテストを実施しました。

CSVファイルを設置し、配列化したデータをAPCキャッシュにstore ・ファイルの変更日時を更新 ・APCに入れているデータの日付とファイル変更をしたファイルの日付を比較 ・変更したファイルのデータを配列化して上書きする

上記のテストを実行したのだけどどうも通らない。

もちろん自分の書いたプログラムなんて信用できない。 だからテストを行っているのですが、そのテストが通らない。 テストのテストを書くか。。。いやいやいやいやいや。

ってことで書いたプログラムをにらめっこしてても解決せず。 dumpしながら確認していった結果、ファイルの時間を取得する部分がうまく動いていない。

ファイルを変更したにも関わらず、最初に入れた時の時間を返す。

もしかしたらtouch関数が何か邪魔をしているのかと思いつつ sleepさせながら確認してみたけど、やっぱりfimemtimeっぽい。

ということで困ったときのPHPマニュアル

この関数は、ファイルのブロックデータが書き込まれた時間を返します。 これは、ファイルの内容が変更された際の時間です。


返り値 ¶

ファイルの最終更新時刻を返し、失敗した場合に FALSE を返します 時間は Unix タイムスタンプとして返されます。 この関数の結果は date() 等で使用できます。

OK,言ってる事はわかるよ。 やりたいのそれですからね。

そこまで見て実装していたのが悪かったです。

注意: この関数の結果は キャッシュされます。詳細は、clearstatcache() を参照してください。

ははーんこれか。 ファイルの情報キャッシュされて変更日はキャッシュした日付を取ってきているのか。 っていうことで、処理の手前にclearstatcache(path)を入れて解決。

まさかキャッシュされているとは思わず。。。

【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の全キャッシュクリアは、同じプロセスの処理をすべてブロックするため、高負荷による影響が出てくると考えられます。

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

【PHP】 APCuでfetchに失敗する

最近はlibraryのテストしたり検証をしたり 大抵はここはこうしたほうがいいとかこういうルールでやるならこっちのほうがいいんじゃないかっていう討論だけ繰り広げている状態です。 話がまとまれば作業は早いのですが。

さてそんな中今まで意識していないかったAPCuというものを使って作業をする部分が出てきました。 やりたいことはAPCuに配列のデータを突っ込むことです。 memcachedのような使い方をしたいということですね。

プロセス間通信も発生しないので爆速で動くのではということでAPCuについて調査しているところで以下に躓きました。

適当にテストファイルを作り、コマンドラインより実行させようとした時 以下のようなエラーが発生しました。

<?php

$key = 'hoge';

$data = [
    'test',
    'hoge' => [
        'fuga',
        'foo' => 'bar'
    ]
];

apcu_store($key, $data);

print_r(apcu_cache_info());

apcu_fetch($key);

本当に単純な処理ですね。 ここから作ったファイルをコマンドラインから実行してみると以下エラーが発生しました。

Warning: apcu_cache_info(): No APC info available.  Perhaps APC is not enabled? Check apc.enabled in your ini

APCuが有効になっていないかも?ってことで、ああ、なるほどと。 ということでphpinfoを確認

php -r 'phpinfo();' | grep apc.enable
apc.enable_cli => Off => Off
apc.enabled => On => On

有効になっている・・・ ここで見つけたのがapc.enable_cliの記述 もしかしたらと思い、php.iniに以下を追加

[APCu]
; add apc
apc.enabled=1

apc.enable_cli=1 ; add cli

apc.shm_size=64M
apc.ttl=3600
apc.gc_ttl=3600

するとうまく動くようになりました。 どうやらapc.enable_cliコマンドラインから叩く場合APCuを有効にする設定だったようです。 さて、色々調べながらこれからAPCuの有用性について調べていきます。

【MySQL】 外部からVirtualBox環境のMySQLへ接続する

今開発環境はWindowsを使用しているのですが、もちろんVirtualBoxの環境はCentOSなわけで。 Mac環境にすればいくらか楽なのですが、なんでか行く先々でWindowsが用意されているので 開発についてはWindowsに慣れてしまっています。 もちろん個人で開発したり、自宅は全てMac環境なのですが。

更にデータ管理者やプランナーにデータベースの説明をしたり テーブル構造について相談するときにターミナルだととても嫌がるというか 見難いと思われるようです。 エンジニア同士だったら問題ないのですが、、、

ということで毎回IDE的なものを入れてたりするのですが、毎回接続できなくて調べるので 接続についてのポイントを書いておこうと思います。

今回はHeidiSQLというGUIツールを試してみました。 ※どんなツールでも変わらないのですが

インストールも終わり、いざ接続というところで

sql error host is not allowed to connect to this mysql server

と出てくるわけです。

MySQLのVersionにもよるのですが、

Can't connect to MySQL server

というエラーを吐くこともあります。

この2点について解決していきたいと思います。

Can't connect to MySQL serverについてはmy.cnf内にbind-adressという設定をしている時に出てくることが多いです。

VirtualBox上のMySQLの設定を見ていきます。 bind-addressとは指定IPからの接続を許可するものになります。 こちらが設定されている場合、指定したIPからの接続しか許可されていないために接続ができないとエラーが出るわけです。 セキュリティの事を考えるとここのbind-addressを接続したいマシンのIPアドレスを入れる事によって解決しますが 今回のようにローカルマシンで接続する場合にはbind-addressをコメントアウトすることによってどのIPからも接続することができます。

次にhost is not allowed to connect to this mysql serverについてです。 このエラーについてはallowedつまり権限がないというエラーになっています。 なのでこちらについてはMySQLユーザに対して権限を付与してあげます。

GRANT ALL PRIVILEGES ON *.* TO root@'192.168.%' IDENTIFIED BY 'root のパスワード' WITH GRANT OPTION;

上記のコマンドについてはrootユーザに対して権限を付与しています。

これで接続が可能となるはずです。 毎回これで調べてるのに忘れているってことは実践とこういったブログなどでのアウトプットまでしないとダメですね。 最近わからないことを調べようとする度にこのブログにあったなぁと思って見るようになってきました。 毎日じゃなくても、わからないことがあったらブログ等でアウトプットする事を習慣化することは重要ですね。

【Redis】 CentOSにRedisをインストールして簡単な動作検証

今回はRedisの導入をしてみました。 最近はKVSといえばRedisみたいになってきましたね。 そんなことないかもですが。

昔はTokyo TyrantとかKyoto Tycoonとかあったもんですがね。 最近全然聞かず、話に出しても「懐かしー」って感じになってきましたね。

Redisはメモリ上で動作する高速なKVSで、メモリ上にあるってことは動作停止やハードの再起動とか電源が切れたりしたら保存されているデータが消えてしまうんじゃないかって不安があると思うのですが、データをディスクに持たせることができるのでキャッシュ用のデータベースとしても優秀なのですが、データストアとしても利用できるように設計されているみたいです。 すごいですね。

早速やっていきましょ!

makeして入れるので必要なソフトウェアが入っているか確認 必要なのは以下の3つ。 環境作っている人なら大体必須なので入っているはず?

wget, gcc, make

まずはwgetでRedisを落とします。 今回は指定があったので3.0.3を落としました。

# wget http://download.redis.io/releases/redis-3.0.3.tar.gz

ちなみに最新版で言うと現時点では3.0.5が最新になります。

http://redis.io/

インストールはお馴染みの感じなのでさくっと

# mv redis-3.0.3.tar.gz
# cd redis-3.0.3.tar.gz /usr/local/src/
# tar xzvf redis-3.0.3.tar.gz
# cd redis-3.0.3
# make
# make install

これでインストールは完了です。 次に設定ファイルを編集します。

設定ファイルは解凍したredisのフォルダ内にあるのですが、他のミドルウェアと同様に/etc配下に置いちゃいました。 わかりやすいからね。

mkdir /etc/redis/
cp /usr/local/src/redis-3.0.3/redis.conf /etc/redis/

さて、設定していきましょう。

daemonize no
↓
daemonize yes

logfile ""
↓
logfile "/var/log/redis.log"

上記2点ですね。

redisの起動方法は先程設定したconfを指定して起動させます。

# /usr/local/bin/redis-server /etc/redis/redis.conf

これで動作しているはずです。

psコマンドなどで確認してみましょう

# ps -aux | grep redis

立ち上がっていることが確認できたら 早速動作確認をしてみましょう。

# redis-cli
127.0.0.1:6379> set key "hello world"
OK
127.0.0.1:6379> get key
"hello world"
127.0.0.1:6379> exit

set して getできればOKですね。

これでRedisのインストールから動作検証までが完了となります!

【nginx】 PHP7(php-fpm)+nginxを動かすまでの設定

さて時間があったので早速nginxの設定を軽く見ていこうということでインストール後すぐの設定が以下の通りになります。

前回のnginxにおけるインストールの記事はこちら

さっそくインストール後からのnginx.confから見ていきましょう

/etc/nginx/nginx.conf

user  nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    include /etc/nginx/conf.d/*.conf;
}

どうやらserverの設定についてはconf.dの中でやってるみたいですね。 ということで次は/etc/nginx/conf.d/default.confあたりを見てみることに。

server {
    listen       80;
    server_name  localhost;

    #charset koi8-r;
    #access_log  /var/log/nginx/log/host.access.log  main;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }

    #error_page  404              /404.html;

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

    # proxy the PHP scripts to Apache listening on 127.0.0.1:80
    #
    #location ~ \.php$ {
    #    proxy_pass   http://127.0.0.1;
    #}

    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
    #
    #location ~ \.php$ {
    #    root           html;
    #    fastcgi_pass   127.0.0.1:9000;
    #    fastcgi_index  index.php;
    #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
    #    include        fastcgi_params;
    #}

    # deny access to .htaccess files, if Apache's document root
    # concurs with nginx's one
    #
    #location ~ /\.ht {
    #    deny  all;
    #}
}

なるほど。今回はphp-fpmを動かしたいので少しずついじっていきましょう。

まずはphp-fpm.confをいじります。 今回はphpenvとphp-buildを使ってインストールしたので 自分の環境では ~/.phpenv/versions/7.0.0RC8/etc/php-fpm.d/www.conf 上記のPathになります。 間の7.0.0RC8は今使っているバージョンです。

ここで変えたのは以下の通り

user = nobody
group = nobody
↓
user = nginx
group = nginx

pm = dynamic
↓
pm = static

pm.max_children = 5
↓
pm.max_children = 3

[追加]
php_admin_flag[expose_php] = off

実行ユーザ及び実行グループをnginxに指定

pmでプロセス生成方法を指定します。 動的にプロセス数を増減するならdinamicに(デフォルトdynamicです)、設定したプロセス数に固定するならstaticを指定します。

次に生成するプロセス数をpm.max_childrenで設定します。 今回は3としました。

pmについてはローカルで作る場合はdynamicで良いかと思います。 今回はstatic使ったことなかったのでいじっていますが笑

staticについては固定値でメモリを確保するので処理速度が早いです。 dynamicの場合は動的にメモリを制御するので、低負荷であればメモリ使用量を抑えることができます。 pm.max_childrenで設定している値はstaticについては設定した数値分Processが立ち上がり、dynamicであれば最大数となります。

最後にexpose_phpをoffを追加しました。 これはphp.iniでもいいのですが、こちらで指定しちゃいました。 HPPTレスポンスヘッダに含まれるPHPバージョン情報を出力しないようにする設定です。

次にnginxの設定です。 /etc/nginx/conf.d/default.conf をいじっていきます。

    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
    #
    #location ~ \.php$ {
    #    root           html;
    #    fastcgi_pass   127.0.0.1:9000;
    #    fastcgi_index  index.php;
    #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
    #    include        fastcgi_params;
    #}

↓

    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
    #
    location ~ \.php$ {
        root           /usr/share/nginx/html;
        fastcgi_pass   127.0.0.1:9000;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include        fastcgi_params;
    }

コメントアウトしている部分を以下のように変更しました。 ここでハマりやすいのはfastcgi_paramの部分でしょうか。

デフォルトの設定だと以下のようになっている部分を変更してあげないと、phpinfoの出力とかしても真っ白のままになります。

fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
↓
fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;

この設定が終わったらnginxを再起動させればlocationのPathに設定されたindexが表示されるはずです。

【nginx】 CentOSにnginxを簡単にインストールする

仕事が軽く変わり、言語はPHPなのですが、ちょっと研究開発っぽいこともやるようになりそうなので 新たに環境構築しつつPHP7を入れたりして遊んで(仕事して)います。

最近はApacheで環境を組むことが少なくなってきましたね。 ってことで今回は簡単にnginxのインストールをしていこうと思います。

余談ですがnginxって名前かっこいいですよね。センス感じます。

さて、話を戻してインストールなのですが、実は結構簡単なんです。

現在リリースされている最新は1.9.7なのですが、mainline versionなので、stable versionである1.8.0をインストールすることに。

インストールの前にmainline versionとstable版の違いについて少し。

mainline version このバージョンについてはnginxに新しい昨日やバグフィックスが一番最初に取り込まれるバージョンのことです。 気をつけなくてはいけないのはAPI非互換な変更が入ることがたまにあるということかな。

stable version stableにはバグ修正のみが取り込まれるので、安定版みたいな感じになるのかな。

ただしstableだからといって安定しているというわけではないのだ。 nginxの中の人によればmainline versionを使ったほうがいいとのことも書いてある。

要は細かいバグ修正等は先にmainline versionに適用され、その後にstable versionに入ってくる為 実は安定しているのはmainline versionではないかとの見解もあったりする。 そこはケースバイケースで選んでください!

今回は上記にも書いたようにstable版の1.8.0を入れることにします。

まずはyumリポジトリを登録します。

# rpm -ivh http://nginx.org/packages/centos/6/noarch/RPMS/nginx-release-centos-6-0.el6.ngx.noarch.rpm

そしてyumでインストール。

yum -y install nginx

これだけなんです。

次はnginxの簡単な設定とか書いていけたらいいなと思います。