PHP

muninがPostgreSQLから情報をとれなくなるほど負荷があがる

ここ1ヶ月ほどmuninがPostgreSQLから情報をとれなくなるほど負荷があがることが1日に1,2回程度発生していました。
以下の赤い四角で囲った部分です。
グラフがぽっかり抜けています。
001
スロークエリがあると想定してログを取得するも、問題となるようなクエリはありませんでした。
実行に時間がかかっているクエリはたくさんあったのですが、いずれのクエリも平常時はms単位で応答を返すものでした。
が、この事象が発生するときは、応答に5秒かかる…というような状態でした。
sarを実行すると以下のような出力が得られました。

06時40分01秒     CPU     %user     %nice   %system   %iowait    %steal     %idle
06時41分01秒     all      7.36      2.01      3.00      0.58      0.00     87.05
06時42分01秒     all      4.46      0.00      1.79      0.32      0.00     93.43
06時43分01秒     all      4.87      0.00      1.55      0.43      0.00     93.15
06時44分01秒     all      6.98      0.00      2.56      0.26      0.00     90.19
06時45分01秒     all     13.59      0.00      5.06      0.24      0.00     81.11
06時46分01秒     all     45.36      1.99     17.29      0.30      0.00     35.06
06時47分01秒     all     74.13      0.00     25.38      0.00      0.00      0.49
06時48分01秒     all     74.97      0.00     24.87      0.00      0.00      0.16
06時49分02秒     all     76.15      0.00     23.46      0.00      0.00      0.39
06時50分01秒     all     44.06      0.00     14.09      0.08      0.00     41.77
06時51分01秒     all     14.55      1.98      5.38      0.90      0.00     77.19
06時52分01秒     all      9.73      0.00      2.82      0.31      0.00     87.14
※sarはCentOSの場合、デフォルトでは10分に1回記録するようになっていますが、10分だと問題が発生したときにログを見るのがつらいので1分に変更すべきだと思います。ちなみに、この問題が発生したとき、DBサーバーは10分間隔だったで…1分に変更しました。

userとsystemでCPU時間を100%使いつくしている状態です。
負荷が高いのはPostgreSQLを動かしているサーバーで、Apacheを動かしているサーバーの負荷は問題ありませんでした。
以上から、PostgreSQLでなにか起こっているに違いないと思って、いろいろ切り分けたのですが、PostgreSQLのログを見ても何もわかりませんでした。

何か多くのアクセスがきているのが原因かもしれないと思い、Apacheのログを見たのですが、何かが起こっている06時46分と何も起こってない時間のアクセス数はあまりかわらず、多くのアクセスがきているわけではなさそうでした。

アクセス数はかわらないのですが、どこからアクセスがきているのか?を集計したところ、bingbotが多くをしめていました。
bingbotにしぼって06時45分のログを見たのが以下です。
207.46.13.34	[30/Apr/2020:06:45:27+0900] GET /~ap2/ero/toukei_kaiseki/soukan.php?game=6151 HTTP/1.1	200	49991
40.77.167.157	[30/Apr/2020:06:45:30+0900] GET /~ap2/ero/toukei_kaiseki/soukan.php?game=19208 HTTP/1.1	200	51751
40.77.167.157	[30/Apr/2020:06:45:22+0900] GET /~ap2/ero/toukei_kaiseki/soukan.php?game=2830 HTTP/1.1	200	50266
207.46.13.34	[30/Apr/2020:06:45:22+0900] GET /~ap2/ero/toukei_kaiseki/soukan.php?game=9955 HTTP/1.1	200	51289
40.77.167.157	[30/Apr/2020:06:45:24+0900] GET /~ap2/ero/toukei_kaiseki/soukan.php?game=2041 HTTP/1.1	200	50478
40.77.167.186	[30/Apr/2020:06:45:39+0900] GET /~ap2/ero/toukei_kaiseki/soukan.php?game=21480 HTTP/1.1	504	247
40.77.167.186	[30/Apr/2020:06:45:39+0900] GET /~ap2/ero/toukei_kaiseki/soukan.php?game=20671 HTTP/1.1	504	247
40.77.167.186	[30/Apr/2020:06:45:39+0900] GET /~ap2/ero/toukei_kaiseki/soukan.php?game=20632 HTTP/1.1	504	247
207.46.13.34	[30/Apr/2020:06:45:43+0900] GET /~ap2/ero/toukei_kaiseki/soukan.php?game=16545 HTTP/1.1	504	247
soukan.phpにそこそこな頻度でアクセスした結果、504を返しています。
soukan.phpはあるゲームに似た評価をされているゲームを表示するスクリプトです。
このスクリプトは作りが悪くて…SQLを1万回程度実行するスクリプトです。
SQL自体は0.006秒程度で応答を返すのですが、それを1万回実行するので…応答がかえるまでに60秒程度かかります。
実行に60秒程度かかるスクリプトをだいたい5秒おきに実行されてしまったので負荷が高くなったようでした。

自分は、DBのネックで負荷があがる場合は、iowaitが高くなると思い込んでいたのですが、1つのスクリプトで応答時間は短いけどSQLを大量に発行して処理が追いつかなくなる場合は、userやsystemが高くなる…ということを知りました。

本格対処はsoukan.phpを書き換えることなのですが、暫定対処として、bingbotをブロックすることにしました。

phpだけ404の場合に自分で設定した404用のページが表示されず「File not found. 」とだけ表示される

Apache + php-fpmの環境の話題です。

ErogameScapeではファイルが存在しないURLが指定された場合、ルートディレクトリのindex.phpを表示するように設定しています。
ErrorDocument 404 /~ap2/index.php
PHPをApacheのモジュールとして動かしていた場合には問題が発生していなかったのですが、PHPをCGIとして動作させた場合に以下の問題が発生しました。

phpだけ404の場合に自分で設定した404用のページが表示されず「File not found. 」とだけ表示される。

https://erogamescape.dyndns.org/~ap2/ero/toukei_kaiseki/a
とすると(aは存在しないです)、自分が設定した404用のページが表示されるのに
https://erogamescape.dyndns.org/~ap2/ero/toukei_kaiseki/a.php
とすると(a.phpも存在しないです)、「File not found. 」とだけ表示されてます。
404
php-fpmを動かすのに、ErogameScapeではunixドメインソケット経由で動かしているので設定ファイルに以下の記述をしています。
<FilesMatch \.php$>
    SetHandler proxy:fcgi://php-fpm
</FilesMatch>
apacheが受けた要求をphp-fpmに渡して、php-fpmが処理した結果をapacheが受けて、apacheがクライアントに返却します。
a.phpの要求を受けたapacheはphp-fpmに渡して、php-fpmはa.phpがないので、「File not found. 」をapacheに返し、apacheはそれを見たそのままクライアントに返却するので、自分が設定した404用のページが表示されません。

php-fpmが返してきた「File not found. 」(404)をapacheが上書きする設定が以下のディレクティブです。

設定ファイルに以下の記述すると自分が設定した404用のページを表示してくれます。
ProxyErrorOverride on
※php-fpmがどうやって「File not found. 」という文字列を返しているのかは分かりませんでした…返す文字列を設定しているっぽいファイルは見つけられなかったのでソースに埋め込まれているのでしょうか…



php-pearのインストールにはepelを有効にする必要がある

この文書は2018/11/23時点の情報です。
php-pearをインストールしようとしたら、php-composer(fedora/autoloader)が必要でした。

[root@localhost localuser]# yum install php-pear
Loaded plugins: fastestmirror, langpacks, priorities
Loading mirror speeds from cached hostfile
 * base: ftp.iij.ad.jp
 * extras: ftp.iij.ad.jp
 * remi-php71: repo1.sea.innoscale.net
 * remi-safe: repo1.sea.innoscale.net
 * updates: ftp.iij.ad.jp
Resolving Dependencies
--> Running transaction check
---> Package php-pear.noarch 1:1.10.6-1.el7.remi will be installed
--> Processing Dependency: php-composer(fedora/autoloader) for package: 1:php-pear-1.10.6-1.el7.remi.noarch
--> Finished Dependency Resolution
Error: Package: 1:php-pear-1.10.6-1.el7.remi.noarch (remi-php71)
           Requires: php-composer(fedora/autoloader)
 You could try using --skip-broken to work around the problem

php-composer(fedora/autoloader)はepelにあるので、まずepelが使えるようにします。
epelのリポジトリを追加するためのrpmを持ってきます。
[root@localhost localuser]# wget https://dl.fedoraproject.org/pub/epel/7/x86_64/Packages/e/epel-release-7-11.noarch.rpm
--2018-11-18 12:28:04--  https://dl.fedoraproject.org/pub/epel/7/x86_64/Packages/e/epel-release-7-11.noarch.rpm
Resolving dl.fedoraproject.org (dl.fedoraproject.org)... 209.132.181.24, 209.132.181.23, 209.132.181.25
Connecting to dl.fedoraproject.org (dl.fedoraproject.org)|209.132.181.24|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 15080 (15K) [application/x-rpm]
Saving to: ‘epel-release-7-11.noarch.rpm’

100%[==================================================================================>] 15,080      --.-K/s   in 0.1s

2018-11-18 12:28:04 (109 KB/s) - ‘epel-release-7-11.noarch.rpm’ saved [15080/15080]

epelのリポジトリを追加するためのrpmをインストールします。
[root@localhost localuser]# rpm -Uvh epel-release-7-11.noarch.rpm
Preparing...                          ################################# [100%]
        package epel-release-7-11.noarch is already installed
[root@localhost localuser]# yum install php-fedora-autoloader
Loaded plugins: fastestmirror, langpacks, priorities
Loading mirror speeds from cached hostfile
 * base: ftp.iij.ad.jp
 * extras: ftp.iij.ad.jp
 * remi-php71: repo1.sea.innoscale.net
 * remi-safe: repo1.sea.innoscale.net
 * updates: ftp.iij.ad.jp
No package php-fedora-autoloader available.
Error: Nothing to do

epelのリポジトリを有効にするため、epel.repoのenabledを1にします。
[root@localhost localuser]# emacs /etc/yum.repos.d/epel.repo

[epel]
name=Extra Packages for Enterprise Linux 7 - $basearch
#baseurl=http://download.fedoraproject.org/pub/epel/7/$basearch
mirrorlist=https://mirrors.fedoraproject.org/metalink?repo=epel-7&arch=$basearch
failovermethod=priority
enabled=1
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-7

php-fedora-autoloaderをインストールします。
[root@localhost localuser]# yum install php-fedora-autoloader
Loaded plugins: fastestmirror, langpacks, priorities
Loading mirror speeds from cached hostfile
epel/x86_64/metalink                                                                                 | 9.5 kB  00:00:00
 * base: ftp.iij.ad.jp
 * epel: www.ftp.ne.jp
 * extras: ftp.iij.ad.jp
 * remi-php71: repo1.sea.innoscale.net
 * remi-safe: repo1.sea.innoscale.net
 * updates: ftp.iij.ad.jp
epel                                                                                                 | 3.2 kB  00:00:00
(1/3): epel/x86_64/group_gz                                                                          |  88 kB  00:00:00
epel/x86_64/updateinfo         FAILED                                                     ]  0.0 B/s |  88 kB  --:--:-- ETA
http://mirror2.totbb.net/epel/7/x86_64/repodata/4965e91e8ce6cc40f06cbce2777f2ef21bf69cae86e12e9c8d0b8c3375a4fcbc-updateinfo.xml.bz2: [Errno 14] HTTP Error 404 - Not Found
Trying other mirror.
To address this issue please refer to the below wiki article

https://wiki.centos.org/yum-errors

If above article doesn't help to resolve this issue please use https://bugs.centos.org/.

(2/3): epel/x86_64/updateinfo                                                                        | 932 kB  00:00:00
(3/3): epel/x86_64/primary                                                                           | 3.6 MB  00:00:02
epel                                                                                                            12707/12707
Resolving Dependencies
--> Running transaction check
---> Package php-fedora-autoloader.noarch 0:1.0.0-1.el7 will be installed
--> Finished Dependency Resolution

Dependencies Resolved

============================================================================================================================
 Package                                Arch                    Version                         Repository             Size
============================================================================================================================
Installing:
 php-fedora-autoloader                  noarch                  1.0.0-1.el7                     epel                  9.6 k

Transaction Summary
============================================================================================================================
Install  1 Package

Total download size: 9.6 k
Installed size: 15 k
Is this ok [y/d/N]: y
Downloading packages:
php-fedora-autoloader-1.0.0-1.el7.noarch.rpm                                                         | 9.6 kB  00:00:00
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
  Installing : php-fedora-autoloader-1.0.0-1.el7.noarch                                                                 1/1
  Verifying  : php-fedora-autoloader-1.0.0-1.el7.noarch                                                                 1/1

Installed:
  php-fedora-autoloader.noarch 0:1.0.0-1.el7

Complete!

php-pearをインストールします。
[root@localhost localuser]# yum install php-pear
Loaded plugins: fastestmirror, langpacks, priorities
Loading mirror speeds from cached hostfile
 * base: ftp.iij.ad.jp
 * epel: www.ftp.ne.jp
 * extras: ftp.iij.ad.jp
 * remi-php71: repo1.sea.innoscale.net
 * remi-safe: repo1.sea.innoscale.net
 * updates: ftp.iij.ad.jp
Resolving Dependencies
--> Running transaction check
---> Package php-pear.noarch 1:1.10.6-1.el7.remi will be installed
--> Finished Dependency Resolution

Dependencies Resolved

============================================================================================================================
 Package                   Arch                    Version                                Repository                   Size
============================================================================================================================
Installing:
 php-pear                  noarch                  1:1.10.6-1.el7.remi                    remi-php71                  357 k

Transaction Summary
============================================================================================================================
Install  1 Package

Total download size: 357 k
Installed size: 2.1 M
Is this ok [y/d/N]: y
Downloading packages:
php-pear-1.10.6-1.el7.remi.noarch.rpm                                                                | 357 kB  00:00:01
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
  Installing : 1:php-pear-1.10.6-1.el7.remi.noarch                                                                      1/1
  Verifying  : 1:php-pear-1.10.6-1.el7.remi.noarch                                                                      1/1

Installed:
  php-pear.noarch 1:1.10.6-1.el7.remi

Complete!

php7.1で複数のRedisサーバーをsession.save_handlerに設定しても冗長構成にはならない

原因は分からないのですが、待機系サーバーのmemcashedの動作がおかしい気がする…ので、session.save_handlerとして使っているmemcashedの別の候補として、PHP7.1において、session.save_handlerとして、Redisを使用したいと思いました。

session.save_pathには複数のRedisサーバーを設定すれば、Redisサーバーが1つ落ちてもサービスを継続できるかな…と思いましたが出来ませんでした。

環境は以下の通りです。
  • CentOS6および7
  • PHP 7.1.0
  • Redis server version 2.4.10
  • phpredis 3.1.1RC2
RedisをSession handlerとして使うには以下の設定が必要です。

/etc/php.iniに以下を記述
session.save_handler = redis
session.save_path = "tcp://localhost:6379"
extension=redis.so

さて、PhpRedisにREADMEによるとsession.save_pathには複数のサーバーを指定できるようになっています。
https://github.com/phpredis/phpredis/blob/develop/README.markdown#user-content-php-session-handler

そこで以下のように記述すれば1つサーバーが落ちてもサービスを継続できると思いました。
session.save_path = "tcp://localhost:6379, tcp://192.168.0.14:6379"

localhostと192.168.0.14にRedisで起動 → 192.168.0.14にセッションの情報が記録されることを確認
localhostのみRedisを起動 → NG、以下のエラーメッセージが出る

Warning: session_start(): connect() failed: Connection refused
Warning: session_start(): Failed to read session data: redis (path: tcp://localhost:6379, tcp://192.168.0.14:6379)

ちなみにsession.save_path = "tcp://localhost:6379"と書くと、localhostにセッションの情報が記録されるのでlocalhostの設定は問題ありません。

振り分けロジックは分からないのですが、逆のケースもNGです。

localhostと192.168.0.14にRedisで起動 → localhostにセッションの情報が記録されることを確認
192.168.0.14のみRedisを起動 → NG


memcashedとmemcasheの場合、session.save_pathに複数のサーバーを記述すれば、すべてのサーバーに同じ情報を書き込むため、サーバーが1台落ちても問題ないのですが、PhpRedisの場合はsession.save_pathに複数サーバーを書くと負荷分散するだけで、サーバーが落ちた場合の対処は別に仕組みを整えないといけないです。
具体的にどうするかというと以下のドキュメントにいろいろな構成が書いてあります。
http://tech.gmo-media.jp/post/48748908427/introduce-redis-sentinel

お手軽に冗長構成をとれたらいいなあ…と思っていたのですが、どの構成もそこそこ手間がかかって、ここまでして冗長構成を取る必要も無いかなあ…と思ったのでRedisは導入を見送ろうと思いました。

php7とphp5のlist関数の挙動の違いについて

ErogameScapeでは以下のようにPostgrelSQLのdate型の列から取り出した日付を年月日に分解するスクリプトがあります。
$ php -r 'list($year,$month,$day) = sscanf("2016-02-23","%d-%d-%d"); var_dump($year);'
Command line code:1:
int(2016)
上記をタイポして以下のように記述しているところがありました。
list($year,$month,$year) = sscanf("2016-02-23","%d-%d-%d");
PHP5までは問題なく…動いていました。
$ php -r 'list($year,$month,$year) = sscanf("2016-02-23","%d-%d-%d"); var_dump($year);'
Command line code:1:
int(2016)
しかし、PHP7で挙動がかわって以下のように日付が抽出されるようになりました。
$ php -r 'list($year,$month,$year) = sscanf("2016-02-23","%d-%d-%d"); var_dump($year);'
Command line code:1:
int(23)
タイポを直して解決しました。

PHP5.6にあげたらpear Net_SMTPからメールが送れない

PHPを5.6にあげたら
port 587番
auth true
の設定でpear Net_SMTPからメールが送れていませんでした。
結論から書くと、Net_SMTPを最新のVer(2015/12/21時点で1.7.1)にあげたら送れるようになりました。
※ちなみに当時使用していたNet_SMTPのVerは1.6.0でした…
$ pear list
Installed packages, channel pear.php.net:
=========================================
中略
Net_SMTP         1.6.0      stable

#pear install -a Net_SMTP

$ pear list
Installed packages, channel pear.php.net:
=========================================
中略
Net_SMTP         1.7.1      stable

pear Net_SMTPからメールが送れなくなる | 東京ネット工務店に「最新版では直っているかも知れません。」と書いてありますが、おそらく直っています。
おそらくと書いたのは、何が良くなかったのかを把握していないから…です。

以下、pearのMail.phpのdebugをtrueにしたときのメッセージです。
Net_SMTPが1.6.0のときは以下のようなメッセージでした。
DEBUG: Recv: 220 ***.***.***.*** ESMTP Sendmail 8.14.5/8.14.5; Mon, 21 Dec 2015 10:38:50 +0900 (JST)
DEBUG: Send: EHLO localhost

DEBUG: Recv: 250-***.***.***.*** Hello ***.***.***.*** [***.***.***.***], pleased to meet you
DEBUG: Recv: 250-ENHANCEDSTATUSCODES
DEBUG: Recv: 250-PIPELINING
DEBUG: Recv: 250-8BITMIME
DEBUG: Recv: 250-SIZE 209715200
DEBUG: Recv: 250-DSN
DEBUG: Recv: 250-AUTH CRAM-MD5 DIGEST-MD5 LOGIN PLAIN
DEBUG: Recv: 250-STARTTLS
DEBUG: Recv: 250-DELIVERBY
DEBUG: Recv: 250 HELP
DEBUG: Send: STARTTLS

DEBUG: Recv: 220 2.0.0 Ready to start TLS
DEBUG: Send: RSET
なぜかRSETを返します。
Net_SMTPを1.7.1にあげた際のメッセージは以下のとおりです。
DEBUG: Recv: 220 ***.***.***.*** ESMTP Sendmail 8.14.5/8.14.5; Mon, 21 Dec 2015 11:28:38 +0900 (JST)
DEBUG: Send: EHLO localhost

DEBUG: Recv: 250-***.***.***.*** Hello ***.***.***.*** [***.***.***.***], pleased to meet you
DEBUG: Recv: 250-ENHANCEDSTATUSCODES
DEBUG: Recv: 250-PIPELINING
DEBUG: Recv: 250-8BITMIME
DEBUG: Recv: 250-SIZE 209715200
DEBUG: Recv: 250-DSN
DEBUG: Recv: 250-AUTH CRAM-MD5 DIGEST-MD5 LOGIN PLAIN
DEBUG: Recv: 250-STARTTLS
DEBUG: Recv: 250-DELIVERBY
DEBUG: Recv: 250 HELP
DEBUG: Send: STARTTLS

DEBUG: Recv: 220 2.0.0 Ready to start TLS
DEBUG: Send: EHLO localhost

DEBUG: Recv: 250-***.***.***.*** Hello ***.***.***.*** [***.***.***.***], pleased to meet you
DEBUG: Recv: 250-ENHANCEDSTATUSCODES
DEBUG: Recv: 250-PIPELINING
DEBUG: Recv: 250-8BITMIME
DEBUG: Recv: 250-SIZE 209715200
DEBUG: Recv: 250-DSN
DEBUG: Recv: 250-AUTH CRAM-MD5 DIGEST-MD5 LOGIN PLAIN
DEBUG: Recv: 250-DELIVERBY
DEBUG: Recv: 250 HELP
DEBUG: Send: AUTH DIGEST-MD5

DEBUG: Recv: 334 ****************************
DEBUG: Send: ********************************

DEBUG: Recv: 334 ****************************
DEBUG: Send:

DEBUG: Recv: 235 2.0.0 OK Authenticated
DEBUG: Send: MAIL FROM:<***.***.***.***>

DEBUG: Recv: 250 2.1.0 <***.***.***.***>... Sender ok
DEBUG: Send: RCPT TO:<***.***.***.***>

DEBUG: Recv: 250 2.1.5 <***.***.***.***>... Recipient ok
DEBUG: Send: DATA

DEBUG: Recv: 354 Enter mail, end with "." on a line by itself
DEBUG: Send: From: erogamescape@ap2.sakura.ne.jp
to: erscape@apost.plala.or.jp
Subject: =?ISO-2022-JP?*****************************

+`*?>+`*
+`*?>+`* ← 暗号化されたメッセージ
+`*?>+`*
DEBUG: Send:
.

DEBUG: Recv: 250 2.0.0 *************** Message accepted for delivery
DEBUG: Send: QUIT

DEBUG: Recv: 221 2.0.0 ***.***.***.*** closing connection
CentOSの方はこれで解決したのですが、スクリプトを書いているWindowsマシンでは、Net_SMTPを1.7.1にあげてもメールが送信できませんでした。
ファイアウォールを無効にしたらメールを送れるようになりました。

Net_SMTPは暗号化したメールを送るためにNet_Socketでソケットを生成します。
ソケットの生成部分から先に進めていなかったのでファイアウォールのせいかな…と思ってファイアウォールをとめたらメールを送れるようになりました。

PHPのセッションデータの保存先をmemcachedにする場合の設定について

以下の文書は各ソフトが以下のverのものです。
  • memcached-1.4.15-2.el6.remi.x86_64
  • php55w-pecl-memcache-3.0.8-2.w6.x86_64

実現したいことは以下の通りです。

192.168.0.1と192.168.0.2の両方にPHPのセッションデータを保存して、どちらかが落ちても、どちらに接続してもセッションが保たれるようにしたい。


実現したい理由は以下の通りです。

ErogameScapeのPHPのセッションの保存先は各サーバーのHDDのみでした。
セッションの保存先が各サーバーのHDDのみですと以下の問題を抱えます。
  1. ユーザーさんからのサーバーの接続先を切り替えた場合、切り替え先はユーザーさんのセッション情報を持っていないので、ユーザーさんがログインした状態だった場合にサーバーを切り替えると強制的にログアウトされた状態になる。具体的には長文感想を1時間かけて書いて登録ボタンを押したら、ログイン画面に飛ばされる等の被害がでます。
  2. 高負荷時、または定常的にロードバランスしたい場合、あるユーザーさんが最初に接続したサーバーが192.168.0.1、次に接続した場合は192.168.0.2だった場合、セッションが引き継げないためログアウトした状態になる。
    ※ロードバランスをIPアドレスベースでやればIPアドレスがかわらない限り問題ないじゃないか!と思う方もいらっしゃると思いますが、スマホ等からの接続の場合、接続のたびにIPアドレスがころころかわります。
    結果、1.で書いた具体例が起こる可能性があります。
現用系のサーバーから待機系サーバーに切り替えると各ページのキャッシュがないため2時間ほど高負荷となることが確認されました。この対策として、常にキャッシュを更新し続けるのがベストであると思いました。
常にキャッシュを更新し続けるには、定常的にロードバランスするのが手間がかからないなと思いました。

定常的にロードバランスするため、192.168.0.1と192.168.0.2の両方にPHPのセッションデータを保存することとしました。
 

memcachedのインストールと/etc/php.iniと/etc/php.d/memcache.iniの設定について

以下の文書がよいと思います。

はまったのは
;Redundancy : When enabled the client sends requests to N servers in parallel
;memcache.redundancy=1
;memcache.session_redundancy=2
memcache.session_redundancyを3と設定しないといけないことでした。
 
2のままだと、
session.save_path="tcp://192.168.0.2:11211,tcp://192.168.0.1:11211"
と書いても、192.168.0.1の方にしかセッションデータが記録されません。

ちなみに
session.save_path="tcp://192.168.0.2:11211"
と書くとちゃんと192.168.0.2にセッションデータが書き込まれます。

memcache.session_redundancyのパラメータが追加されたのはphp-pecl-memcacheのVerが3系になってからですので、3系を使う場合は気をつけましょう…

ここらへんは英語になりますが
に書いてあります。
※ぐぐっても私と同じようにはまった日本語の文書が全然なくて(やっぱり3系はbetaだから使われていないのでしょうか…)解決に3時間くらいかかりました…プログラマーに必要な言語は英語だとよく言ったものだと思います…

CentOS6.4におけるPHP5.3からPHP5.5への移行方法について

CentOSのバージョン確認方法についてはCentOSのバージョン確認コマンドとアーキテクチャ確認コマンド | mawatari.jpを参照

$ cat /etc/redhat-release
CentOS release 6.4 (Final)
PHP5.5への移行方法についてはPHP 5.5 on CentOS/RHEL 6.4 and 5.9 via Yum | Webtatic.comを参照

# yum install php55w
とすると、 php-commonがconcliftしていると怒られますので、私は
# yum remove php-common

========================================================================================================================
 Package                     Arch                  Version                               Repository                Size
========================================================================================================================
Removing:
 php-common                  x86_64                5.3.19-1.el6.remi                     @remi                    6.3 M
Removing for dependencies:
 php                         x86_64                5.3.19-1.el6.remi                     @remi                    7.7 M
 php-cli                     x86_64                5.3.19-1.el6.remi                     @remi                    6.7 M
 php-devel                   x86_64                5.3.19-1.el6.remi                     @remi                     10 M
 php-gd                      x86_64                5.3.19-1.el6.remi                     @remi                    672 k
 php-ldap                    x86_64                5.3.19-1.el6.remi                     @remi                    105 k
 php-mbstring                x86_64                5.3.19-1.el6.remi                     @remi                    4.1 M
 php-mysql                   x86_64                5.3.19-1.el6.remi                     @remi                    448 k
 php-pdo                     x86_64                5.3.19-1.el6.remi                     @remi                    353 k
 php-pear                    noarch                1:1.9.4-12.el6.remi                   @remi                    2.2 M
 php-pgsql                   x86_64                5.3.19-1.el6.remi                     @remi                    315 k
 php-xml                     x86_64                5.3.19-1.el6.remi                     @remi                    630 k
 phpmyadmin                  noarch                2.11.11.3-2.el6.rf                    @rpmforge                 13 M

Transaction Summary
========================================================================================================================
Remove       13 Package(s)
で、php-common他5.3のphpのパッケージを消してから
# yum --enablerepo=remi,epel,rpmforge install php55w-devel php55w-pear php55w-xml php55w-pgsql php55w-gd php55w-ldap
php55w-opcache php55w-mbstring php55w-mysql php55w-pdo php55w-snmp php55w-soap php55w-xmlrpc phpmyadmin ======================================================================================================================== Package Arch Version Repository Size ======================================================================================================================== Installing: php55w-devel x86_64 5.5.3-1.w6 webtatic 2.5 M php55w-gd x86_64 5.5.3-1.w6 webtatic 134 k php55w-ldap x86_64 5.5.3-1.w6 webtatic 31 k php55w-mbstring x86_64 5.5.3-1.w6 webtatic 930 k php55w-mysql x86_64 5.5.3-1.w6 webtatic 106 k php55w-opcache x86_64 5.5.3-1.w6 webtatic 85 k php55w-pdo x86_64 5.5.3-1.w6 webtatic 91 k php55w-pear noarch 1:1.9.4-7.w6 webtatic 389 k php55w-pgsql x86_64 5.5.3-1.w6 webtatic 83 k php55w-snmp x86_64 5.5.3-1.w6 webtatic 29 k php55w-soap x86_64 5.5.3-1.w6 webtatic 193 k php55w-xml x86_64 5.5.3-1.w6 webtatic 142 k php55w-xmlrpc x86_64 5.5.3-1.w6 webtatic 41 k phpmyadmin noarch 2.11.11.3-2.el6.rf rpmforge 2.7 M Installing for dependencies: php55w x86_64 5.5.3-1.w6 webtatic 2.5 M php55w-cli x86_64 5.5.3-1.w6 webtatic 2.4 M php55w-common x86_64 5.5.3-1.w6 webtatic 1.2 M Transaction Summary ======================================================================================================================== Install 17 Package(s)
で、全部入れ直したのですが、
# yum replace php-common --replace-with=php55w-common
というコマンドで置き換えができるのですね。
私は試していないのでなんとも言えないのですが。

インストールが終わったら/etc/php.iniを書き換えます。

PHPにおいてPOSTしたデータが欠ける

ユーザーさんから「ここ半年以内に発売されたゲーム」欄から得点を入力しようとすると得点が入力できないと申告がありました。

ErogameScapeの得点入力画面は、1つの画面でなんでもかんでも入力できるのが特徴で(入力項目が多すぎてユーザーさんに敬遠されるという負の面も強いのですが…) 以下画面イメージのように、入力項目が多いです。

無題1

1つのゲームに対してinputタグが10くらい(hidden属性のものも含めて)、一月に発売されるゲームが30本あったとして、 半年分のゲームを一度に入力できる画面を作ると、inputタグが1800個必要です。

PHP5.3.9以降はphp.iniにmax_input_varsという項目があります。
こちらのパラメータの意味は以下の通りです。
入力変数 を最大で何個まで受け付けるかを指定します (この制限は、スーパーグローバル $_GET、$_POST そして $_COOKIE にそれぞれ個別に適用されます)。 このディレクティブを使うと、ハッシュの衝突を悪用したサービス不能攻撃を受ける可能性を軽減できます。 このディレクティブで設定した数を超える入力変数があった場合は E_WARNING が発生し、 それ以降の入力変数はリクエストから削除されます。
max_input_varsのデフォルトの値は1000で、デフォルトのまま運用していたので、この制限に引っかかってしまっていました。

apacheのerrorログには以下のように記録されます。

[Sun Oct 13 20:33:22 2013] [error] [client 192.168.0.12] PHP Warning:  Unknown: Input variables exceeded 1000. To increase the limit change max_input_vars in php.ini. in Unknown on line 0, referer: http://192.168.0.2/~ap2/ero/toukei_kaiseki/contents_tokuten_ichiran_default.php?mode=half_a_year_released

※ちなみにローカルのテスト環境では問題なかったのですが…理由はローカルのテスト環境のPHPのverが低かったから…が理由でした。

※他の理由でPOSTが欠けることがあります。HTTPのPOSTで送信できるデータのサイズ at softelメモの文書が参考になるかなと思います。

max_input_varsは10000くらいあればいいかな?と思って10000に引き上げたのですが、「ア行」のゲームのデータをまとめて入力する画面は10000では足りず、100000にしてOKとなりました。

私はまとめて入力できる画面が好きなのですが、max_input_varsが追加された理由が「このディレクティブを使うと、ハッシュの衝突を悪用したサービス不能攻撃を受ける可能性を軽減できます。」ですので、うーむ…、さすがに「ア行」を全部表示して入力できるような画面は廃止すべきなのかな…思いました。

FuelPHPのForm::inputの第二引数は自動的にhtmlentitiesが適用されます

FuelPHP Ver1.5.1の話題です。

FuelPHPのForm::inputの第二引数は自動的にhtmlentitiesが適用されます。

ですので、 
<?php echo Form::input('name', Input::post('name')); ?>
とInput Classの値をそのまま渡しても大丈夫です。

ソースは以下のようになっています。
\core\form.php
public static function input($field, $value = null, array $attributes = array())
{
return static::$instance->input($field, $value, $attributes);
}

\core\form\instance.php
public function input($field, $value = null, array $attributes = array())
{
if (is_array($field))
{
$attributes = $field;
! array_key_exists('value', $attributes) and $attributes['value'] = '';
}
else
{
$attributes['name'] = (string) $field;
$attributes['value'] = (string) $value;
}

$attributes['type'] = empty($attributes['type']) ? 'text' : $attributes['type'];

if ( ! in_array($attributes['type'], static::$_valid_inputs))
{
throw new \InvalidArgumentException(sprintf('"%s" is not a valid input type.', $attributes['type']));
}

if ($this->get_config('prep_value', true) && empty($attributes['dont_prep']))
{
$attributes['value'] = $this->prep_value($attributes['value']);
}
unset($attributes['dont_prep']);

if (empty($attributes['id']) && $this->get_config('auto_id', false) == true)
{
$attributes['id'] = $this->get_config('auto_id_prefix', 'form_').$attributes['name'];
}

$tag = ! empty($attributes['tag']) ? $attributes['tag'] : 'input';
unset($attributes['tag']);

return html_tag($tag, $this->attr_to_string($attributes));
}

public function prep_value($value)
{
$value = \Security::htmlentities($value, ENT_QUOTES);

return $value;
}
 


ControllerからViewに渡す値はset_safe等で渡さない限り、自動的にhtmlentitiesが適用されます。

Controllerで
$data['name'] =  Input::post('name');
View::forge('user/contents', $data);

Viewで
 <?php echo Form::Input('name', $name); ?>

とすると、Viewにnameが渡されたときにhtmlentitiesが適用され、Form::Inputに渡されたときにまたhtmlentitiesが適用されてしまいます。
しかし、 FuelPHPのデフォルト設定は「double_encodeをしない」なので、一度HTMLエンティティに変換された文字が再度変換されることはないので、特殊なケースを除いて問題ないです。

特殊なケースとは「&amp;」等を入力した場合です。
こちらについては、前の記事を参照ください。

※「&amp:」等が入力されることがないのであればいい気もするんですが、例えば「&は&amp;に変換されます」という記事を書きたいとかいう場合は困る気がしました。


記事検索