小野マトペの業務日誌(アニメ制作してない篇)

introduction

俺こと小野マトペが、大学生活と折り合いをつけつつショートアニメを制作していく事に関する制作日誌でしたが、今はグダグダです。始まりは9/11から。旧HP(廃棄)

レビューライン.jp稼動中。
「平面男(完全版)」20041224  ■「平面男」20041018/20041020
■Javaアプレット: Conna-Gonna20050203/ 20050208
■はてなブックマーク改造用Greasemonkey集20061002
■はてなQuickClip(firefox用機能拡張)20060906

2010年04月30日 金曜日

[][] MySQL5.5.3-m3のDATETIME型のバグ。あとMySQLのDATETIME型は本当に遅いのか検証してみた 06:00  MySQL5.5.3-m3のDATETIME型のバグ。あとMySQLのDATETIME型は本当に遅いのか検証してみた - 小野マトペの業務日誌(アニメ制作してない篇) を含むブックマーク  MySQL5.5.3-m3のDATETIME型のバグ。あとMySQLのDATETIME型は本当に遅いのか検証してみた - 小野マトペの業務日誌(アニメ制作してない篇) のブックマークコメント

バグの話

近々ふぁぼったーDBInnoDB化を企てているので、それに伴いMySQL5.0.67(Tritonn)から、先日リリースされたばかりのMySQL5.5.3-m3に乗り換えてみた。RC(リリース候補)版ということで、GA版とほぼ変わらない品質と聞いたので、割と軽い気持ちでインストールしたんだけど、いきなりバグにハマった。

バグとは、DATETIME, TIMESTAMP, DATE, TIME型と文字列定数との結合でインデックスが使われない、というもの。

以下のような、date(DATE型)の結合しかしていないクエリでも、dateインデックスが使われず昇順フルテーブルスキャンされ、20秒くらい掛かった。

select date from STATUS force index(date) where date='2010-01-19' limit 10;

この現象は、5.5.3,5.5.4での現象としてバグ報告がなされ、すでにパッチ待ちになっていた。

no title

no title

よって5.5.5がリリースされれば解消されているのだけど、バグ報告中で報告されていた回避方法を紹介。

あとDATETIME型が遅いって本当?

INT型の方がデータ取得の処理スピードが150倍高速の圧倒的効果である。INT型はINDEXを最適に使い目的の結果を返してくれるためここまでのパフォーマンス結果がでたものと思われる。面白い副産物結果として、DATETIME型ではINDEX有り・無しかかわらず処理結果値が同じということで、DATETIME型はINDEX恩恵を受ける事があまりできないのである

http://blog.fukaoi.org/2009/03/19/mysql_datetime

fukaoi.org

以前、時刻の保存形式としてDATETIME型は低速でイケていない、unix_timestamp()関数で値を設定したINT型で保存すべき、というを話を上の記事で読み、なるほどそうしておこうかなと漠然と思っていたのだけど、ちょうどいい機会なので、MySQL5.5でも通じるTipsなのか検証してみました。ちなみに、MySQLバイブル実践ハイパフォーマンスMySQL 第2版にはこうある。

3.1 最適なデータ型の選択

  • 通常は小さい方がよい

一般に、データの格納と表現を正しく行えるデータ型のうち、最も小さいものを使用するように心がける。データ型が小さいほどディスクメモリCPUキャッシュで使用する領域が少なくなるため、通常はその方が高速である。また、処理に必要なCPUサイクルも通常は少なくなる。(略)

  • 単純なものがよい

(略)たとえば、文字セットとその照合順序(並び替えルール)は文字の比較を複雑にしているため、文字よりも整数比較するほうがコストがかからない。ここに例が2つある。日付と時刻は文字列ではなくMySQL組み込み型として格納すべきであり、IPアドレスには整数型を使用すべきである。

(強調は引用者)

DATETIMEないしTIMESTAMPを推奨されました。じゃあ、日付表現のための最も小さくて単純なデータ型ってなんでしょう。DATETIME型とTIMESTAMP型の解説は以下のようになってます。

3.1.4 日付と時刻型

DATETIME

 1001-9999年までの値を格納する事ができ、精度は1秒である。タイムゾーンとは無関係に、日付と時刻をYYYYMMDDHHMMSS形式で整数にパックする。これには8バイト記憶域が使用される。(略)

TIMESTAMP

 1970年1月1日午前0時(グリニッジ標準時)からの経過時間を秒数で格納する。つまり、UNIXタイムスタンプと同じである。記憶域を4バイトしか使用しない(略)

はい、DATETIMEもTIMESTAMPも、形式が違うだけでどちらも整数管理されているんですね。ここら辺は公式ドキュメントに詳しいです。unix_timestamp()したINT型とTIMESTAMPはデータの格納方式として等価と考えていいということでしょうか。

特殊な振る舞いはともかく、TIMESTAMPはDATETIMEよりもストレージ効率がよいため、TIMESTAMPを使用できる場合一般にそれを使用すべきである。UNIXタイムスタンプ整数値として格納することもあるが、通常はそうしたところで何の特もない。その形式は何かと扱いにくいので、お勧めしない。

(強調は引用者)

うーん、INTのTipsフルボッコです。でも実際のところはどうなのでしょう。計測してみました。

計測条件
CREATE TABLE `STATUS` (
  `id` bigint(20) NOT NULL DEFAULT '0',
  `user_id` int(11) NOT NULL,
  `created_at` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  `date` date NOT NULL DEFAULT '0000-00-00',
  `text` varchar(256) NOT NULL DEFAULT '',
  `point` int(11) NOT NULL DEFAULT '0',
  `created_at_timestamp` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
  `created_at_int` int(10) unsigned NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`),
  KEY `c_` (`created_at`),
  KEY `ct_` (`created_at_timestamp`),
  KEY `ci_` (`created_at_int`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

スペック

OSCentOS release 5.3

DB :MySQL5.5.3

CPUAMD Phenom(tm) 9350e Quad-Core

Mem:8GByte

インデックス無し

DATETIME型
mysql> select SQL_NO_CACHE * FROM STATUS ignore index(c_) where 20090701000000 <= created_at and created_at < 20090701235959 limit 1;
1 row in set (5.92 sec)
TIMESTAMP型
select SQL_NO_CACHE * FROM STATUS ignore index(ct_) where 20090701000000 <= created_at_timestamp and created_at_timestamp < 20090701235959 limit 1;
1 row in set (8.40 sec)
INT型
mysql> select SQL_NO_CACHE * FROM STATUS ignore index(ci_) where unix_timestamp(20090701000000) <= created_at_int and created_at_int < unix_timestamp(20090701235959) limit 1;
1 row in set (6.13 sec)

DATETIME型とINT型がほぼ同程度の速度で、TIMESTAMP型だけなぜか一周り遅いという結果に。えー、上でTIMESTAMP型とINT型はデータ長以外等価って言い切ったばかりなのに…。実際には等価ではなく、扱うロジックに差があるために速度差が出ているんでしょう。でもTIMESTAMP型よりもデータ長が長く格納方式の複雑そうなDATETIME型がTIMESTAMP型よりも速いっていうのが謎…。何故に…

インデックス有り

DATETIME型
mysql> select SQL_NO_CACHE * FROM STATUS where 20090701000000 <= created_at and created_at < 20090701235959 limit 1;
1 row in set (0.00 sec)
TIMESTAMP型
mysql> select SQL_NO_CACHE * FROM STATUS where 20090701000000 <= created_at_timestamp and created_at_timestamp < 20090701235959 limit 1;
1 row in set (0.00 sec)
INT型
mysql> select SQL_NO_CACHE * FROM STATUS where unix_timestamp(20090401000000) <= created_at_int and created_at_int < unix_timestamp(20090401235959) limit 1;
1 row in set (0.00 sec)

えーと、どのデータ型でもきちんとインデックスが適用されていて、データ型の差異が計測出来ない程度に高速ですね。2000万行でこの速度ならどれ使ってもパフォーマンス上全く問題ないといえるでしょう。インデックス無しでは速度に差がありますが、どっちみちインデックス無しではどんなデータ型だろうと実用的なクエリ実行は行えません。これなら実践ハイパフォーマンスMySQLの教えに従って組み込み時間型を使ってもなにも大丈夫そうです。自前のINT型、必要なし!

しかし深追いさんのベンチマークとの差はどこから来たんでしょう…。MySQL5.0.67から 5.5.3へのアップデートのどこかでDATETIMEが改良されたのでしょうか。ただ、深追いさんのベンチマークは、そもそもDATETIMEに対してインデックスが効いている気配がないので、今回僕がハマったようなバグかCardinalityの破損であのような結果になったのではないかという気も少ししています。自分環境では以前の5.0.67でもDATETIMEのインデックスは効いていたような気がするので…。ちなみに今回のテストはInnoDBで行いましたが、MyISAMテーブルでもdatetimeインデックスは適用されました。


まとめ

時間データは素直にDATETIME型かTIMESTAMP型を使おう!*1

*1:TIMESTAMP型は環境のタイムゾーンに依存し、4byteとコンパクトだが2037年問題を抱えている

ono_matopeono_matope 2011/04/02 17:51 削除依頼はメールにてお願いします。

ono_matopeono_matope 2011/07/10 22:43 matope.ono [ at ] gmail.comになります。

ono_matopeono_matope 2011/11/15 04:15 だいたい月一間隔で対応してます。

r3333r3333 2011/11/16 16:22 14日と本日削除依頼をGmailに送らせていただいた者です。ご多忙な中誠に申し訳ないのですが、何卒早急なご対応をお願いできませんでしょうか。不利益を被っております。何卒、お早いご対応を切にお願い申し上げます。

yukitasoyukitaso 2011/12/26 09:13 何度もふぁぼったー削除以来をGmailに送らせていただいております。
早急な対応をお願いいたします。

2010年03月14日 日曜日

[]LVMスナップショットバックアップのためのシェルスクリプトを作った 23:47 LVMスナップショットバックアップのためのシェルスクリプトを作った - 小野マトペの業務日誌(アニメ制作してない篇) を含むブックマーク LVMスナップショットバックアップのためのシェルスクリプトを作った - 小野マトペの業務日誌(アニメ制作してない篇) のブックマークコメント

今までmysqldumpを使ったデイリーバックアップを行っていたんですが、 いざ障害時に復旧しようとすると、SQLの実行のための時間がかかりすぎる、MERGEテーブルに対応していない(のでSQLをsedで書き換えるハメになった)等の理由から、LVMスナップショットバックアップに移行することにしました。


というわけでさっき

というスクリプトを書きました。何かの参考にするために上げておきます。LV消したりとか危ない事を自動化しているので、これを使ったり参考にしたりした事によるいかなる損害も保障しません。また、このスクリプトを実行する前に、バックアップしたいVolumeGroup上に、スナップショット用の空き容量(今回は1GB)を作る必要があります。


やってること

  1. MySQLをFLUSH TABLEする(書き込みバッファの内容をファイルに書き出す)
  2. /dev/ssdvg VolumeGroupのlv01 Logical Volumeのスナップショットを /dev/ssdvg/lvSnapに1GBで作成
  3. /var/lib/mysql/favotterを /バックアップディレクトリ/<日付>-<DB名>にコピーする
  4. /dev/ssdvg/lvSnap LVを削除する
  5. コピーtar.gz圧縮する。
#/usr/bin/sh
DATE=`date '+%Y-%m-%d-%a%H:%M:%S'`;
SRC_LV="/dev/ssdvg/lv01";
SNAP_LV="/dev/ssdvg/lvSnap";
SNAP_SIZE=1G;
DB_NAME="favotter";
DB_PATH="lib/mysql/$DB_NAME/";
DB_PASSWORD="<your db password>";

DEST_PATH="/home/matope/Documents/favotter/dbbackup/hotcopy/";
DEST_FULL=$DEST_PATH$DATE-$DB_NAME;

sudo mkdir /mnt/snap;
sudo /sbin/modprobe dm_snapshot;
sudo /sbin/lsmod | grep snapshot;

mysqladmin -u root -p$DB_PASSWORD flush-tables;
sudo /usr/sbin/lvcreate -s -L $SNAP_SIZE -n lvSnap $SRC_LV;
sudo mount $SNAP_LV /mnt/snap;
sudo cp -rpv /mnt/snap/$DB_PATH $DEST_FULL;
sudo umount /mnt/snap;
sudo /usr/sbin/lvremove $SNAP_LV --force;
sudo tar -cvzf $DEST_FULL.tar.gz $DEST_FULL;

この本を参考にしました。すごく実用的でおすすめです。ふぁぼったーもめきめき高可用性ゲットやでー

Linux-DB システム構築/運用入門 (DB Magazine SELECTION)

Linux-DB システム構築/運用入門 (DB Magazine SELECTION)

七波七波 2010/04/01 09:17 こんにちは。ツイッターネーム七波と申します。
ふぁぼったーを使ってみました。とても面白いのですが、私が実際お気に入りに入れているものと、ふぁぼったーに表示されているものが全然一致していないのが気になりました。なぜでしょう。

2009年11月01日 日曜日

[]ふぁぼったーの負荷分散の計画をあれこれ考えてみた。 02:22 ふぁぼったーの負荷分散の計画をあれこれ考えてみた。 - 小野マトペの業務日誌(アニメ制作してない篇) を含むブックマーク ふぁぼったーの負荷分散の計画をあれこれ考えてみた。 - 小野マトペの業務日誌(アニメ制作してない篇) のブックマークコメント

今日一日調べたこととかまとめてみる。

問題点

  • 次回リリースの新機能はDB select負荷がかなり掛かるはずである。
  • 現状でもデータLOADによる負荷が10に達し、selectにスロークエリ、接続エラーが出ている。DBの負荷分散が急務である。

参考: Load Average

f:id:ono_matope:20091102022001p:image:w400

参考: Slow Query

f:id:ono_matope:20091102022000p:image:w400

  • あと、STATUSテーブルが2GBに達しようとしているので、デフォルトのサイズ制限4GBが見えてきた。再定義できるけど。

垂直分割対応案

日本語版DBと英語版DBデータベースとクローラを別ホストに格納する
レプリケーションする
  • Webが参照するスレーブDBから、クローラの実行負荷を分離する事が出来る。
  • しかし、INSERT処理はマスタにもスレーブにも等しく走るので、このDBへの挿入負荷をスレーブDBから分離する事はできない。
  • 問題はINSERT時のテーブルロックだと思われるので、クローラ負荷が分離したところで効果はやや疑問。

水平分割対応案

MERGEテーブルを利用する
  • 複数のテーブルをマージするMERGEテーブルを利用する
  • INSERTのロック範囲を狭められるので効果はありそう。
  • 欠点
    • INSERT時の主キー同一性チェックがINSERT_METHODで指定した挿入テーブルに対してしか行われないので、挿入する対象テーブルはロジック側で完全に制御する必要がある。ぶっちゃけ面倒。
    • TritonnがMERGEテーブルに非対応。ふぁぼったー検索をMySQL以外で実装する必要がある。
パーティショニングを利用する
  • MySQL5.1の新機能。一つのテーブルを複数のパーティションに分割する。
  • MERGEテーブルと比べて、主キー同一性チェックの問題がないらしくてうれしい。より透過的?
  • 欠点

結論

こうして考えると、水平分割はふぁぼったー検索がTritonn依存している関係上、一筋縄ではいかない。

ふぁぼったー検索をTritonn以外の方法(例えばGroonga単体)で実装すれば、ふぁぼったーはTritonn(=MySQL5.0.45)の呪縛から解き放たれ、MySQL5.1にアップグレードしモダンなパーティショニングを使う事ができる(MERGEテーブルは使いたくない)。

しかし、ふぁぼったー検索は、通常の全文検索だけでなく

  • 動的に増加するfav数によるフィルタリング
  • ユーザーによるしぼりこみ
  • ふぁぼりユーザー(fav_by)によるしぼりこみ
  • ユーザーのprotect状態変更への追従
  • 発言削除への追従

と、DBから分離して管理するには面倒な要件が盛りだくさんなので、片手間で独自実装するのは現実的とは思えない。つまり、Groonga MySQLストレージを待ちたい。


一方垂直分割に関しては、レプリケーションとDB分離、どちらがメリットがあるかすぐには分からないが、どちらも仕組みはとても簡単なので、どちらとも評価して決めればよい。総論として、ふぁぼったーDB負荷分散戦略としては、


「Groonga MySQLストレージエンジンを待望つつ、レプリケーションまたは日本・英語DBの分離を行う」


で決まりかと思われる。と結論が出たところで寝ます。おやすみなさい。

le_000le_000 2011/02/24 13:35 ふぁぼったーの削除お願いします

匿名匿名 2011/05/14 15:08 さきほど、メールで削除のリクエストをした者です。管理人様、申し訳ありませんが、早急に削除をお願いします。

zaczac 2011/05/18 18:07 ふぁぼったーを利用させて頂きありがとうございます。
実はツイッターアカウントを削除したことにより、ふぁぼったーに残されたものを削除したいのですが、どのようにしたら削除できますでしょうか?
よろしくお願いします。

mm 2011/06/05 20:52 ふぁぼったーのふぁぼられの削除をお願いいたします。
マイページにアクセスも削除もできないので困っております。

mm 2011/06/05 20:52 ふぁぼったーのふぁぼられの削除をお願いいたします。
マイページにアクセスも削除もできないので困っております。

匿名匿名 2011/06/24 17:32 ふぁぼったーの削除お願いします。
Twitterのアカウントを消したのに ふぁぼられてるので すごく気持ちがわるいです。

匿名2匿名2 2011/06/30 05:32 プロフィールを更新したいのですが,マイページにアクセスできず困っています。
マイページにアクセスできるようにしてもらえませんか?

NeguinNeguin 2011/08/29 12:37 http://favotter.net/user/SomaYoshimura
ふぁぼったー削除依頼です。
Twitterでも@ono_matope宛にツイートも送っております。
よろしくお願いします。

2009年10月10日 土曜日

[]MySQLクエリキャッシュヒット率を計算するワンライナー書いた 19:07 MySQLのクエリキャッシュヒット率を計算するワンライナー書いた - 小野マトペの業務日誌(アニメ制作してない篇) を含むブックマーク MySQLのクエリキャッシュヒット率を計算するワンライナー書いた - 小野マトペの業務日誌(アニメ制作してない篇) のブックマークコメント

MySQLパフォーマンスチューニングをしていて避けては通れない要素、key_buffer_size設定。

参考サイトによると、MyISAMインデックスに割り当てられるキャッシュサイズであるkey_buffer_size値は、以下のように、インデックスを利用するリクエストにおける、ディスクread数の割合から求められる"キャッシュヒット率"によって増減を決定されるらしいですが、

キーキャッシュのヒット率 = 100 - ( key_reads / key_read_requests × 100 )

DSAS開発者の部屋:5分でできる、MySQLのメモリ関係のチューニング!

この値は特に起動直後はかなり変動し、毎回SHOW STATUSして手動で計算するのが面倒くさいので、これを一発で求めるワンライナーを作りました。

キャッシュヒット率(単位:%)

mysqladmin -u root -p extended-status | grep -e Key_read_requests -e Key_reads |sort -f|paste -s|awk '{print 100-100*$4/$9}'

出力結果

94.3086

キャッシュミス率(単位:%)

mysqladmin -u root -p extended-status | grep -e Key_read_requests -e Key_reads |sort -f|paste -s|awk '{print 100*$4/$9}'

出力結果

5.6928

うむ。キャッシュミス率は0.3%以内に収めるのが理想的だそうなので、まだまだヒット率が小さいですね。key_buffer_sizeを増やした方がいいのかもしれません。

参考サイト

DSAS開発者の部屋:5分でできる、MySQLのメモリ関係のチューニング!

[ThinkIT] 第5回:key_buffer_sizeの違いによるパフォーマンス比較 (2/3)

通りすがり通りすがり 2009/11/03 11:30 クエリキャッシュじゃないですよね?

matsubobomatsubobo 2009/11/17 16:17 クエリキャッシュじゃないですね。キーキャッシュですね。

2009年08月05日 水曜日

[]ふぁぼったーで使ってるPHPのプロファイリング用の関数 01:34 ふぁぼったーで使ってるPHPのプロファイリング用の関数 - 小野マトペの業務日誌(アニメ制作してない篇) を含むブックマーク ふぁぼったーで使ってるPHPのプロファイリング用の関数 - 小野マトペの業務日誌(アニメ制作してない篇) のブックマークコメント

PHPでパフォーマンスチューニングをする時、特定のロジックの実行にどれくらい時間がかかっているのか知りたいことがよくある。よくあるというか、チューニングにプロファイリングは必須だ。世の中には高度なプロファイリングツールがあれこれあるみたいだけど、高機能すぎて僕にはよく分かんないです。

という訳で多分みなさんそれぞれ自前でパフォーマンスを計測してると思うんだけど、自分が普段ふぁぼったーのWeb開発で使っている、PHPの任意の部分の処理時間を計測するスニペットを公開してみます。やっつけですが。

使い方

getMS()関数は、前回getMS()が呼ばれた時刻からの経過時刻をミリ秒で返します。

すなわち、処理時間を計測したい部分の開始地点と終了地点でそれぞれgetMS()を呼び、終了地点での戻り値をprintするなりすれば任意の部分の処理時間を計測できます。

<?php
function getMS(){
    static $oldTime=0;
    list( $micro, $time ) = explode(' ',microtime());
    $newTime=$time+=$micro;
    $diffTime=$newTime-$oldTime;
    $oldTime=$newTime;
    return $diffTime;
}

getMS(); //計測開始

//
// 処理step 1
//

print ("step 1. ".getMS()."s<br>");  //step 1処理時間出力

//
// 処理step 2
//

print ("step 2. ".getMS()."s<br>");  //step 2処理時間出力
?>

結果

step 1. 0.0012s

step 2. 0.0003s

static変数を利用している関係上、入れ子構造で利用できないのが難点ですが、そんなときはgetMS2()を定義して(ry

あわせて読みたい

no titleにインスパイヤされて作った、デバッグモードでのみ表示されるprintみたいな関数も使ってます。みたまんま。DEBUG定数の状態で出力を制御します。

<?php
define('DEBUG',true);

function preprint($var){
    if(defined('DEBUG') && DEBUG){
        print $var;
    }
}
?>

さらに横着版

<?php
function preprint($var){
    if(true){
        print $var;
    }
}
?>

ぎょはぁ!!!!!ぎょはぁ!!!!! 2009/08/18 06:12
ヘイヘイ!!あひひひほはぁwwwwwww ちょwwいきなりごめwwwwww
寝てるだけで5 万もらっちゃって真面目な自分がヴァカらしくなってさwwwww
はぁーいま女シャワー浴びてんだけど、もう1ラウンドでまた5 万くれるってYO!wwwwww
またマグロでさっさと中 出 しするわwwwwwwwww

http://kachi.strowcrue.net/bTLUbz1/

hts1004hts1004 2009/09/28 17:52 PEARのBenchMark(Timer)とか便利ですよ〜
http://paranoids.sakura.ne.jp/kaworu/2007-03-10-1.php
http://tech.ironhearts.com/blog/archives/151

2009年07月25日 土曜日

[]PDO::MySQLプレースホルダにバインドしたinteger変数がexecute後にstring化した 00:07 PDO::MySQLでプレースホルダにバインドしたinteger変数がexecute後にstring化した - 小野マトペの業務日誌(アニメ制作してない篇) を含むブックマーク PDO::MySQLでプレースホルダにバインドしたinteger変数がexecute後にstring化した - 小野マトペの業務日誌(アニメ制作してない篇) のブックマークコメント

整数の$count変数をSQLプレースホルダにバインドしてexecuteして、結果行数と$count値を比較する、こういうコードを書いた


  1 <?php
  2 
  3   $pdo = new PDO("mysql:host=localhost;dbname=xxx",xxx,xxx);
  4   $count = 10;
  5   $sql = "select * from SEEDUSER limit :count;";
  6 
  7   echo "count is $count\n";
  8   echo "before prepare ".gettype($count)."\n";
  9   $stmt = $pdo->prepare($sql);
 10   echo "after prepare ".gettype($count)."\n";
 11   $stmt->bindParam(':count',$count,PDO::PARAM_INT);
 12   echo "after bind ".gettype($count)."\n";
 13   $stmt->execute();
 14   echo "after execute ".gettype($count)."\n";
 15   $result = $stmt->fetchAll();
 16 
 17   echo "result length is ".count($result)."\n";
 18   if(count($result) === $count){
 19     echo "count(result) === count\n";
 20   }else{
 21     echo "count(result) !== count\n";
 22   }
 23 ?>

出力はこうなった

count is 10

before prepare integer

after prepare integer

after bind integer

after execute string

result length is 10

count(result) !== count

SQL文のプレースホルダにバインドした整数型の$count変数が、$stmt->execute()後にstring型に変化していることがわかるだろうか。

このため、18行目の条件文では結果行数が$countと同じ時に真を期待していたが、実際には偽となっている。


なにこれこわい


実行環境

  1. CentOS 5
  2. php 5.2.6
  3. pdo_mysql 5.0.67

774774 2009/11/04 13:50 bindParamじゃなくてbindValue使えばいいんじゃね?
bindParamは変数そのものを参照としてつかえるから変化するはず

ももみももみ 2010/03/12 17:35 おぉーそういう手があったか!
私もさっそくやってみます。

2009年06月06日 土曜日

[]Safari3のリロードは強制リロード 04:29 Safari3のリロードは強制リロード - 小野マトペの業務日誌(アニメ制作してない篇) を含むブックマーク Safari3のリロードは強制リロード - 小野マトペの業務日誌(アニメ制作してない篇) のブックマークコメント


この一週間、Webページのブラウザキャッシュについて少し調べてた。

Webコンテンツを出来るだけブラウザキャッシュさせて転送量を削減しようと目論んでいて、PHPアプリケーション側でIf-Modified-SinceヘッダとDB最終更新時間を照らし合わせて、更新がなければ304を返す、という一般的なブラウザキャッシュ利用実装を行ったんだけど、テストしてみたらSafariが全然キャッシュを使ってくれない。と言うわけでApacheのログを取って色々調べた結果、表題のことが分かった。


Safariのリロードは、いわゆる強制リロード相当である


一応簡単に説明すると、IEなどの一般的なブラウザはページの読み込みシチュエーションによって、ブラウザ内のキャッシュを使うかどうかのポリシーを変化させている。IEやFxでは、

  • 通常ロード
    • ブックマークやロケーションバーからページにアクセスした場合
    • リンクや「戻る」によるページ遷移の場合
  • 通常リロード
    • リロードボタンやいわゆるF5ボタンによる再読込の場合
  • 強制リロード
    • ブラウザによって異なるがShift+F5やControl+F5などで、キャッシュを利用せずに強制的に再読込する場合

の三つの読み込みパターンがあるらしく、これらの種類によって、ブラウザはサーバーに対して主にCache-Control、If-None-Match、If-Modified-Sinceなどのキャッシュ利用ポリシー関係のヘッダを出し分け、キャッシュの使い方をサーバーに申告する。


で、その出し分け方がブラウザによって少しずつ違う。この辺りは404 Not Foundの記事が分かりやすいです。

このOgawaさんの作成された表に、自分が調査したSafariの振る舞いを勝手に追加させてもらった。


ヘッダ If-Modified-Since If-None-Match Cache-Control Pragma
Firefox 通常ロード
Firefox リロード max-age=0
Firefox 強制リロード no-cache no-cache
IE7 通常ロード
IE7 リロード
IE7 強制リロード no-cache no-cache
Safari 通常ロード
Safari リロード max-age=0
Safari 強制リロード

Safariに強制リロードインターフェイスは存在しないっぽいので空白。

このように、Safariにおけるリロードは、If-Modified-Since無し、If-None-Match無し、Cache-Controlはmax-age=0と、全方位スキ無しでブラウザキャッシュ利用を拒否していることが分かる。これはFx、IEにおける強制リロードと同等のレベルである。よって、Safariでリロードしたときに、ブラウザキャッシュを利用させることは出来ないと結論できる(通常ロード時には他のブラウザと同様にブラウザキャッシュが利用される)。ちなみに、If-*系ヘッダが無いのに強引に304 headerと空白のレスポンスを返したところ、Safariは白紙をレンダリングした。


補遺

ちなみに上記のSafariは3.2.1を用いたが、Safari 4 Public Beta(5528.17)では、リクエストしたページからリンクされている各種画像やcssファイルに関してはきちんとIf-Modified-Sinceヘッダを送信し、304レスポンスを受け入れるようになったようだ。いいことだと思う。

関連エントリー

404 Not Found

no title

っていうかこの下のエントリーに答え書いてあった…

プロフィール

ono_matope

ono_matope

もうなんか全然アニメ描いてません。もっぱらプログラミングばかりしてます。ごめんなさい。レビューライン.jp管理人

    << 2018/08 >>
    1 2 3 4
    5 6 7 8 9 10 11
    12 13 14 15 16 17 18
    19 20 21 22 23 24 25
    26 27 28 29 30 31
    統計情報
    pv:1665384
    counter:
    この日記のはてなブックマーク数
    08/15 23:30 Yahoo!の近傍探索ツールNGTを使って類似商品APIをつくる - BASE開発チームブログ
    08/15 21:03 「子どもは上に上がると思った」 2歳児発見のスゴ腕捜索ボランティア - FNN.jpプライムオンライ
    08/15 20:05 Copysets and Chainsets: A Better Way to Replicate
    Copysetsを拡張したChainsetsでは、チェーンレプリケーションと相性の良い動的ノード変更への対処が記述されている。
    08/15 20:03 PWL Denver: Copysets
    CopySetsってクラスタノードの動的変更にどう対処するべきかは言及されてないのか。
    08/15 12:17 発見時は成人男性と一緒 - 共同通信
    ゴンさん状態かと思った
    参照した本/DVD
    • 実践ハイパフォーマンスMySQL 第2版
    • Wacom Cintiq 21UX DTZ-2100D/G0
    • Linux-DB システム構築/運用入門 (DB Magazine SELECTION)
    • ふしぎの海のナディア VOL.01 [DVD]
    • WEB+DB PRESS Vol.45