CRONでPHPを使ってデータベース(MySQL)の処理を計画しています。
処理内容は、1日1回、MySQL内の特定の値に対しランキングを行います。
■主な条件
・サーバー:さくらレンタルサーバー・スタンダード
・データ数:まだ不明ですが、とりあえず想定として1万件
■参考までに、CRONの使用に関する注意事項(一部抜粋)
・メモリやCPUに著しく負荷をかける処理は他のお客様にご迷惑がかかりますのでおやめください。 サーバ運用に支障をきたす場合はやむを得ない場合には、予告無く設定解除、機能制限することがあります。
・実行頻度が1時間以内に連続する場合や、CPU処理時間が60秒以上連続で利用される場合、予告なく設定解除される場合があります。
■質問
(1)上記の様な場合『CPU処理時間が60秒以上連続で利用』されるでしょうか?
(2)何かCPUに負荷をかけず、処理をボチボチやる方法があれば教えてください。
例えば朝3~5時まではデータメンテナンス中として
新たな書き込みなどのアクセスは停止して、ゆっくり処理して良いです。
以上 よろしくお願いします。
同じくさくらレンタルサーバーのスタンダードプランで、twitterのbotを運用していたことがあります。
処理の頻度は1日1回ということなので、cron自体の制限にはかからないと思います。
処理の負荷はランキング処理の内容と量次第という感じです。
自分の経験としては、もともとインストールされていないソフトをビルドしたら、処理を途中で中断されました。(コンパイルは青天井にCPU使ってしまうので……)
手作業、自動実行にかかわらず、CPU利用率が一定を超えたJOBを強制終了するようになっているものと推測します。
その際は、niceコマンドを使って優先度下げたら、中断されずに完了しました。時間がかかっても良いなら、お薦めの手段です。
ちなみに、最初 nice -n 10 cd /home/・・・ としてましたが
ちゃんと動かなかったので nice -n 10; cd /home/・・・ と
「;」を入れてみました。
実際に試してみれる環境はもう持っていないので、気になったところだけ。
cdがちゃんと動かないのは、シェルの組込みコマンドだからです。(niceで実行できるのは外部コマンドだけ)
挙げられている例のように複数コマンドをまとめて実行するなら、スクリプトを組んで、それを実行させる方がよいでしょう。
あと、個人的には、実行ログを /dev/null に捨ててしまうのは危険だと思っています。私も一時期やりましたが、障害解析が全くできないので、ちゃんとログをファイルに書き出す方法に切替えました。
ランキングの対象となるテーブルに適切にインデックスが張られていて、ランキングの結果が全件でなければ一瞬でランキングの作成が終わると見積れます。
全件に対してランキングを作成する場合、おそらく SELECT がファイルソートになるので、レコード数が多いと時間がかかります。
以下参考例です。
MariaDB [hatena_question]> SHOW CREATE TABLE item; +-------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Table | Create Table | +-------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | item | CREATE TABLE `item` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(10) NOT NULL, `count` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), KEY `count` (`count`) ) ENGINE=InnoDB AUTO_INCREMENT=204443 DEFAULT CHARSET=utf8 | +-------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 1 row in set (0.00 sec) MariaDB [hatena_question]> SELECT COUNT(*) FROM item; +----------+ | COUNT(*) | +----------+ | 204442 | +----------+ 1 row in set (0.05 sec) MariaDB [hatena_question]> SELECT * FROM item ORDER BY count DESC LIMIT 10; +--------+------------+-----------+ | id | name | count | +--------+------------+-----------+ | 102711 | item102711 | 399298036 | | 34929 | item34929 | 399297693 | | 5962 | item5962 | 399292859 | | 180578 | item180578 | 399286130 | | 40471 | item40471 | 399285684 | | 41353 | item41353 | 399282738 | | 134448 | item134448 | 399281769 | | 101184 | item101184 | 399280862 | | 202852 | item202852 | 399280606 | | 170867 | item170867 | 399277978 | +--------+------------+-----------+ 10 rows in set (0.00 sec) MariaDB [hatena_question]> explain SELECT * FROM item ORDER BY count DESC LIMIT 100; +------+-------------+-------+-------+---------------+-------+---------+------+------+-------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +------+-------------+-------+-------+---------------+-------+---------+------+------+-------+ | 1 | SIMPLE | item | index | NULL | count | 4 | NULL | 100 | | +------+-------------+-------+-------+---------------+-------+---------+------+------+-------+ 1 row in set (0.00 sec) MariaDB [hatena_question]> explain SELECT * FROM item ORDER BY count DESC; +------+-------------+-------+------+---------------+------+---------+------+--------+----------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +------+-------------+-------+------+---------------+------+---------+------+--------+----------------+ | 1 | SIMPLE | item | ALL | NULL | NULL | NULL | NULL | 204835 | Using filesort | +------+-------------+-------+------+---------------+------+---------+------+--------+----------------+ 1 row in set (0.00 sec)
[サブクエリを使ったランキング]
参考サイトで挙げられている SQL によるランキング集計は、DEPENDENT SUBQUERY になるのでスケールしません。100 件のランキングを作るのでも 3秒 もかかります。全件でランキングを作成しようとするとおそらく終わりません。
PHP でランキング計算をして、データを更新するのがよいでしょう。
MariaDB [hatena_question]> explain SELECT id, count, (SELECT COUNT(*) FROM item sub WHERE sub.count > main.count) + 1 AS rank FROM item main ORDER BY count DESC LIMIT 10000; +------+--------------------+-------+-------+---------------+-------+---------+------+--------+--------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +------+--------------------+-------+-------+---------------+-------+---------+------+--------+--------------------------+ | 1 | PRIMARY | main | index | NULL | count | 4 | NULL | 10000 | Using index | | 2 | DEPENDENT SUBQUERY | sub | index | count | count | 4 | NULL | 204109 | Using where; Using index | +------+--------------------+-------+-------+---------------+-------+---------+------+--------+--------------------------+ 2 rows in set (0.00 sec) MariaDB [hatena_question]> SELECT id, count, (SELECT COUNT(*) FROM item sub WHERE sub.count > main.count) + 1 AS rank FROM item main ORDER BY count DESC LIMIT 100; 100 rows in set (2.94 sec)
> サブクエリを使うよりも、PHPの方が処理が早いと理解してよろしいのでしょうか?
データ件数に依存しますが、ランク計算は PHP でやった方がトータルでは早そうです。「MySQL DEPENDENT SUBQUERY 遅い」などで検索してもらえば仮に理解はできなかったとしても、サブクエリでパフォーマンスが出ないのはよくあることだということは感じで頂けるかと思います。
> また、PHP内で以下を実行すれば良いと理解して良いのでしょうか?
はい。
(1) は MySQL で ORDER BY を指定すればソートまでできます。試して早い方を選べばよいと思います。きっと MySQL でソートしておく方が結果がよいはずです。
まぁ1万件程度でなら一気にやるでまったく問題ないと思いますけれど、cron に登録するバッチプログラムで 1000 件ずつ処理をするなどすれば、夜中の間に更新できると思われます。
MySQL のテーブルを更新する時は、UPDATE を繰り返さずに一度の UPDATE で複数のレコードを更新するとパフォーマンスを改善できます。他のテクニックとしては、一時的に MySQL のテーブルから index を drop してしまって、データを全部更新し終えたら再度 index をはり直す手法もあります。今回はここまでする必要はまずないと思いますが、レコード数が増えて大規模なデータを処理することになったときの参考までに。
ちなみに、PHP で大量のデータを処理する時は、php.ini で設定できる memory limit に気を付けてください。レコードを一気に取得しようとすると設定によってはプログラムが途中で止まります。そういう時は1件ずつ取得して処理するか、memory limit を上げるかします。
状況説明を補足させていただきます
データベース(DB)の項目は
・ユニークID
・ユーザー名
・パスワード
・愛称
・ポイント
・ランキング順位
となっていて、1日1回CRONでポイントの大きい順にランキング順位を決めて上書きします。
通常は(CRON処理中以外)は、DBにアクセスして、自分のポイントを変更したり
自分の順位(最新のランキング順位)を見たりします。
現在参考にしようかと思っているのは下記です。
http://qiita.com/hmuronaka/items/1afc132ddf400363efc2
http://www.system-ido.com/risouken/index.php?page=tech&num=7
http://q.hatena.ne.jp/1373303099
http://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q1110268...
申し訳ありませんが、素人なので良く理解できてません。
2015/04/19 08:42:55(1)1日1回バッチ処理で一括して順位を求めて保存しておいて、必要な時に必要な人の順位を検索して取得する。
(2)必要な時に必要な人の順位を毎回count+1で取得する。
サーバー負荷的に考えて(1)の方が良いと思っていたのですが、(2)の方が良いと理解してよろしいのでしょうか?
順位を更新するメンテナンスが大変なので、その方がいいと思います。
2015/04/19 09:50:35サーバー負荷を心配されているようですが、それほどかからないと思いますよ。