2010年8月29日日曜日

Kindle3 使ってみた

Kindle2と比較してみて

- 縦横とも1cmほど小さくなった。
これによって、片手でつかみやすくなった。
DSCN1681

- コントラストの改善
今回購入した理由の一番にこれを期待して、購入した。
家の証明は暖色系が多く、Kindleで本を読むと暗く感じるときがある。あとひっくり返ってよむとどうしても暗くなってしまうので、画面がより「白い」方が良い。ただ確かによくはなっているものの改善はわずかだった。左の画像がKindle2、右の画像がKindle3。右の画像の方がわずかに白くなっているのが分かると思う。もっと「白く」なって欲しいと思う。
DSCN1679DSCN1680

- 軽くなった
実際に計ってみたところ、Kindle2は293g、Kindle3は226グラムと67g(23%)ほど軽くなっている。実際に持ってみたところ、小さくなった関係もあり、Kindle2より大分軽く感じる。持って読むときも大分腕の負担が軽くなる。
DSCN1683 DSCN1684

- ページ送りが速くなった。
大分速くなったように感じる。iPadのiBookでPDFを見た場合、ページ送りの時表示されるまでそこそこ時間がかかるが、それよりも速いかもしれない。

- 容量が増えた。
Kindle2の1.48Gから3.09Gに増えている。自炊したファイルはサイズが大きいので大きい方が良い。

- ある程度日本語に対応した。
日本語ファイル名が表示可能になった。自炊していると非常にありがたい。ただ、PDFの日本語はフォントが埋め込みでなければ相変わらず表示できないようだ(未確認)。あとブラウザも日本語入力は出来ないが日本語の表示が出来るようになった。googleを使った場合、ちゃんとサジェストも動作するのでローマ字入力だけでもそこそこ使える。また3Gモデルであれば、ブラウザの機能は現状はExperimental(実験)とされているので、通信料も取られない。(多分。Kindle3になって変わっていたりするかもしれない。念のため。)
Evernoteが読めれば結構最高なんだが、Webからではちょっと重くて使い物にならなかった。今後に期待!

- PDFファイルのコントラストの変更が可能
自炊したファイルをKindleで閲覧していて良く困るのが、字が薄すぎて読めない!ということがよくあること。スキャナにScanSnapを使用している場合、読み取り方式を「白黒」にして、読み取り濃度を濃く、にすれば特に問題ない。しかしその場合、「白黒」のため途中に画像などが挿入されていると画像が潰れてしまう。かといって読み取り方式を「自動」にすると、今度は「画像」があるページがカラーで読み込まれKindleで見た場合、画像は見やすくなるが、字が薄くて読みづらくなる。
この場合、Kindle3ではコントラストの設定の、lightest lighter default darker darkestと選択できるなかのdarkestを選ぶと薄い文字が濃く表示され、ある程度読みやすくなる。

- まとめ
全体的に良くなったと思う。とはいえ、未だに日本語の本の電子書籍は販売されないし、その予定もない。早くそのあたり解決されるともっと良い。また、日本語対応やコントラストの変更など、ソフトウェアで対応できるものは、Kindle2でアップデートできるようにして欲しい。

2010年6月15日火曜日

iPad使ってみた

今回購入は見送ろうと思っていたが、気になってしかたないので、購入してしまった。買ったのはWiFi 16Gモデル。正直このエントリーを書くのは、iPad発売から大分時間がたってるし、もうじきiOS4が出るわでタイミングが悪い、、

結論

ここ一週間ほど使ってみた感じ、まだまだ改善点は色々見られるものの、今後のOSアップデートによるサポートもあるし、また飽きて使用済みになれば、親にでもあげて使ってもらえばいいと思う。高級なフォトフレームにもなるし、レシピブックにもなるだろう。
トイレやお風呂場にでも貼り付けておいても良いかもしれない。WLAN経由で動画も見れるし、音楽も聞ける。
自分の場合昔からタブレットPCにはあこがれのようなものがあり、以前使っていたノートPCは8.9inch液晶のコンパーチブル型のタブレットPCだった。これはこれで面白いPCだったが、ネックになったのは重さ(1.23kg)とバッテリー(二時間弱)だった。そのタブレットPCを買った当時は12万ほどした。それから3年ほどでiPadが登場し重さは680gになり駆動時間が10時間になり、価格は5万円になった。
PC(Windows)以上のことが出来るかというと、それは人によるから何とも言えないし、仮にPCを使ったことがないユーザにiPadを無条件に進めれるかというとそういうわけでもない(iTunes必須だし)。ただマウスとキーボードというインターフェースから切り離されるだけで、ベッドでごろごろしながら使ったり、車の中で使ったり、使い方は人それぞれだが可能性は広がる。興味があるのであれば買ってしまっても良いと思う。逆にあまり興味がわかないのであれば無理して買う必要も無い。多くの場合iPadは自分で使い道を探さないといけないから。

iPod touchとの比較

  • 純正メールアプリ
    画面が大きくなり使いやすくなった。横画面状態であれば、メールのリストと内容が同時に表示できる。これとGmailがあれば、PC版のメールソフトなんか要らなくなるんじゃないかと思う。Gmailについてはアーカイブにする操作ができないので惜しいが、、
  • 純正写真アプリ
    画面が大きくなったことで、より細かくきれいに見れるようになった。特に人に見せるとき、iPod touchでは画面が小さく、出来ればPCの高解像度画面で見て欲しいときがあると思う。そのような場合に、さっと相手に渡して操作させることの出来るiPadはとても良いと思う。
  • 純正ビデオアプリ
    今回購入したのは16Gモデルなので、音楽を入れてとか、動画を入れてとかは余り考えていなかった。いつも家で動画を見るときは、主にDLNAを使ってPS3で見ている。やはり洋画などはこの方式で見た方が、映像もきれいだし、音も5.1chのAVアンプで再生されるので良い。純正ビデオアプリではDLNAは再生できないが、他のアプリを使って、主にアニメ等、映像も音もそこそこ見れれば良い場合など、ソファーにごろんと座ってDLNA経由でiPadで再生するようにすると、ファイルの一元管理も出来るし、意外と快適だった。
  • 純正iPodアプリ
    iPod touchがあるので、さすがに使っていない。少し試して見たところ、よりiTunesに近いようなインターフェースになっているように感じる。アルバムアートワークがとても大きく表示されるので、iPod touch用に用意した解像度の低い画像では粗く表示されるじゃないかと思う(ここは試してない)。
  • Webブラウザ(Safari)
    ブラウザは基本的に画面が大きくなっただけだろうか。個人的には別画面への移動や、画面を閉じるのが意外と面倒で、リンクを行ったり来たりするのに時間がかかるのを何とかして欲しい。あとiGoogle使ってると別画面に移動して戻ったときに再読み込みがかかるような挙動になるのを何とかして欲しいところ。Safari5からは拡張機能が使えるようになるという話なので期待。
  • 電子書籍
    画面が大きくなった分とても見やすい。ただ雑誌のようなA4サイズを拡大縮小なしで読もうとすると少し厳しい場合がある。拡大縮小しながら読むのはめんどくさい。
    また液晶の品質は良くも悪くもiPod touchと同じように感じる。自分の場合この液晶を長時間見ていると目が若干ちかちかして集中して読めなかったり、目が疲れたりする。この原因(推測)としては、画素が粗くドットが見える、液晶が手前のガラス面より少し奥まったところにあり、手前のガラスの写り込みと液晶面がずれる、iPadのような端末は画面を近くで見る為右目と左目で感じる輝度に違いがある。これらの原因で焦点を合わせずらくなり、結果目が疲れるんじゃないかと考えている。
    ホリエモンやその他の人達が、携帯やパソコンなど、みんなずっと液晶見てるんだから、目が疲れるなんて事はないと言ってたりするが、確かに自分も一日中PCの液晶を見ているがそう疲れを感じることはない。なのでiPadやiPod touchはこれらとはまた違うように思う。ただ次期iPhone4の液晶は凄く良さそうだ。期待。
    写真 
  • Bluetoothキーボードが使える
    今この文章の下書きはiPadに接続したBluetoothキーボードで書いているが、意外と快適。使っているキーボードは以前Windows Mobileに接続して使っていた変則配置のキー四段しかない折りたたみ式のキーボード(iGo Ultra-Slim Keyboard)。ソフトウェア(ディスプレイ)キーボードについては、慣れればそこそこ入力できるかもしれないが、今のところはそこまではためしていない。少し使ってみた感じ、画面の上に手を乗せると手が邪魔になってしまいキーが見えなくなり、また通常のキーボードとは配置が違うので、ブラインドタッチが出来ないので、結構ストレスが溜まる。慣れてブラインドタッチが出来るようになればそこそこ快適になるかも知れない。また、ソフトウェア(ディスプレイ)キーボードが意外と不便に感じるのは横画面だと画面の半分近くがキーボードで埋まってしまうこと。
    Bluetoothキーボードについては、iOS4でiPod touchでも使えるようになるかもしれない。
    DSCN1606
  • ファイルの連携が出来る。
    iPod touchではzipファイルが添付されてきた場合などどうしようもなかったが(多分)、これがiPadでは特定の関連したプログラムにファイルを渡せるようになった。これにより、zipファイルを添付されてきた場合でもGood Readerなどのアプリにファイルを渡し展開し、閲覧することが可能になった。また、Good Readerからほかのアプリにファイルをコピーして開くというようなことも可能になった。

Kindleとの比較

  • 重さ・大きさ
    まず最初に持って見た感じ「重い!」。Kindleが289グラムに対して、iPadは680グラムもありずっしりしている。これはある程度最初から予想はしていた。よくノートPCと比較して軽いと言われるが、やはりKindleや普通の本と比較すると重く感じる。またiPadは形状的に片手では持ちにくい形をしている。片手で持とうとすると淵の部分をつかむ感じになり、この持ち方だと回転方向にテコの原理が働き意外と握力がいる。Kindleはもともと軽いのもあるが、下にキーボード部分があり、その上のスペースの辺りを掴むと自然な感じで掴める。また横幅が13.5cmなので手の幅に収まりガシッと片手で掴める。
    また今回はApple純正のケースを使用しているが、これが意外と重く合計すると874グラムもある。正直重くて参ったが、他の背面につけるタイプのケースも100グラム程度あるのと、純正ケースの場合ふちの部分に引っ掛かりが出来、若干持ちやすくなるので、まぁ良いかなということにした。
    DSCN1623
    DSCN1607 
  • 見やすさ
    総合的に見れば、iPadの方が上だろう。ただKindleの方が手軽な場合があり、これは例えば、白黒でKindleの画面に1ページが収まるような、文庫本サイズや文字の大きい本など、また自分の場合、Kindleの方が目との相性がよいのでKindleで読めるものはKindleで読むスタイルにした。

Flybook(8.9inch液晶 コンパーチブル型タブレットPC Windows7)との比較

DSCN1299

  • ブラウザ(Safari)はいまいち
    やはりPCのChromeやFirefoxのブラウザと比較すると物足りない。拡張機能が使えないためAutoPagerのような拡張機能や、タブの使いやすさなど不満が残る。Windows7(Vista)のタッチパネルの場合、8方向のフリック(指をはじく操作)に機能を割り当てることが出来、これにそれぞれ、戻る、進む、ページスクロール、ページを閉じる、拡大、縮小などを割り当てて操作していたが、これはこれで快適だった。
  • IMEが頭悪い。
    時々閉口する。我慢すれば何とか使えるレベル。
  • 電子書籍
    WindowsでPDFを見ようとすると、主にAdobe Readerを使うと思うが、タブレットで操作した場合これの操作性が悪いこと、、正直スペックが下(多分)のiPadでこれだけなめらかに操作できるのに。iPad(Apple)を見習って欲しいところ。
    ただ、PCの場合Windows SearchでPDFの横断検索が出来るため、まだまだ捨てられない。なのでiPadは電子書籍を読むことには向いているが、電子書籍から何かを探そうとするのには全然向いていない。
  • バッテリーが持つ
    バッテリーが凄くもつのはとても良い。一日の使用であれば気にすることは無いと思う。ただ、寝るときに充電し忘れると、通常のUSBポートではスリープ状態にしないと充電できないし、スリープにしたとしても、満充電までは凄く時間がかかる。これは通常のUSBポートでは5Aまでしか電流が流せないためで、よくUSB HDD等に付いている、USBを2つのポートを占有して電源を確保するケーブルがあるが、あれのiPad版は発売されないだろうか。
  • 使いやすさ
    WindowsはWindows7になって大分改善されたが、元々タブレット用には作られていないため、やはり使いにくい。また、ハードウェアの使いやすさとして、車の中で使ったりする場合、iPadであればダッシュボードの上にぽんと置いておいたり、運転席でハンドルが邪魔でも操作できたりするが、ノートPCの場合ダッシュボードに置いておくのは何かと気を遣うし、運転席側でキーボードを打って操作するというのは厳しい。

2010年5月20日木曜日

Doctrine 関係リンク集

次のプロジェクトはDoctrineでいくことにしたのでメモ。
Doctrine2ではマジックメソッド(findBy~)が使えなくなるらしいし、そもそも処理が重いらしいので開発では使わないことにした。

初級編

上級編

2010年3月25日木曜日

symfony 1.4 + Propel 1.4 でメモリリーク?

さて、以前とあるWebサービスをsymfony 1.0からsymfony 1.4へアップデートを行ったが、バックエンドのバッチ処理は1.0のまま稼働させていた。また、とある事情でこのWebサービスを整理する必要があったため、バッチ処理の1.4化を行っていた。

いくつか、1.4化のための作業(ログ関係とヘルパー関係の修正等)を行いバッチ処理を開始!様子を見ていると1.0時代より非常に時間がかかる。さらに待っていると、良くあるメモリが足りないよ!というFatal Errorで死亡してしまった。あれ-、、と思いつつメモリの様子を見て見ると・・・

memory_leak

美しすぎるメモリリークの図

メモリリークしてる!あーこんな情報前に何処かで見たなと思いつつ検索してみるも発見できず、、

仕方ないのでソースコードを少しずつ実行して原因となる場所の特定をする。複数箇所で起きているようで、どうもPropelのオブジェクトを生成するときにリークするようだった。

この時点でもしかしたらリークしているわけではなく、なんらかのキャッシュ機構が働いているのかもなぁ、と思い検索して見るも発見できず。グーグルで「Propel 1.4」で検索するとこのブログが上から三番目に出てくるという、情報のなさっぷりを嘆く。

ここまでくるとソースを見るしかない。オブジェクトの生成部をたどっていくと、Propelが自動生成したBaseDataSourcePeer.phpで以下のようなソースを見つけた。

	public static function populateObjects(PDOStatement $stmt)
	{
		$results = array();
	
		// set the class once to avoid overhead in the loop
		$cls = DataSourcePeer::getOMClass(false);
		// populate the object(s)
		while ($row = $stmt->fetch(PDO::FETCH_NUM)) {
 			$key = DataSourcePeer::getPrimaryKeyHashFromRow($row, 0);
 			if (null !== ($obj = DataSourcePeer::getInstanceFromPool($key))) {
 				// We no longer rehydrate the object, since this can cause data loss.
 				// See http://propel.phpdb.org/trac/ticket/509
 				// $obj->hydrate($row, 0, true); // rehydrate
 				$results[] = $obj;
 			} else {
				$obj = new $cls();
				$obj->hydrate($row);
				$results[] = $obj;
        DataSourcePeer::addInstanceToPool($obj, $key);
 			} // if key exists
		}
		$stmt->closeCursor();
		return $results;
	}

注目して欲しいのはDataSourcePeer::addInstanceToPoolというメソッドで、名前からして、キャッシュしてますよ!という香りが漂ってくる。試しにPropel1.2で生成したモデルと比較してみると、1.2移行のバージョンで新しく追加されたことがわかった。また上記メソッドを使用している部分を全てコメントアウトしてみると、メモリが異常に消費されることは無くなった。

さてここまでわかれば、検索でさらにキーワードを絞ることが出来る。再度検索してみると、原因はdatabases.ymlでpooling:trueと設定していることであった!(参考)databases.ymlに書いてあるあたり基本中の基本だ!何となく、へーpoolingするんだ?速そうだからtrueにしとけ、的な感覚でtrueにしたのを思い出した、、

まとめ

マニュアルはちゃんと読もう。

少し追記。場合によっては、オブジェクトをプールした方が高速になる場合があるのはわかる。しかし自動でORMで処理してしまうのは費用対効果(メモリ消費とCPU・ディスク負荷の軽減)を考えるとあまり良い方法ではないように思う。今Propel1.4で実装されているような処理では、私が遭遇したような問題が起こるかも知れないし、キャッシュヒット率を考えると、ヒット率が良いと考えられる場合はアプリーケーションレベルで都度実装した方が多くの場合よりよいだろう。

また単純に何も考えずにアプリケーションを実装した場合、今回のようなオブジェクトプールにメモリを回すよりはDBにメモリを回した方がより効果は得られるだろう。なぜなら、各種DBはメモリをより効率的に使うため極限まで最適化され、常に開発が続けられているからである。またPHPのプロセスと違い常に起動しているものなのでよく使われるデータのみをキャッシュするなど最適化もしやすい。例外的にCPU負荷がとても高く、メモリが余っておりオブジェクト生成がボトルネックになっているような場合はオブジェクトプールをtrueにしても良いと思う。

2010年3月19日金曜日

Google App Engine + Windows

  • dev_appserver.py すると HTTPSHandlerがないよって言われる。
    ActivePythonのx64版をインストールしているならx86版をインストールする。
  • 公式のチュートリアルを一通りやる。
  • 本番にアップしようとappcfg.py updateをするとSSLモジュールがないよって言われる。
  • http://pypi.python.org/pypi/ssl をインストールしてねって言われるので、ダウンロードして
    python setup.py install
    するとこのPythonはVisual Studio 2003でビルドされてるよ!Visual Studioでコンパイルするかcygwinでためしてね!っていわれる。
  • しかたないのでcygwin版でやってみる。すでにpython2.5がインストールされていた。
  • ActivePythonはとりあえずアンインストールする。
  • cygwin版pythonで
    python setup.py install
    すると成功した。
  • appcfg.py updateも成功して無事本番にアップ完了した
    http://hanger35.appspot.com/

まとめ

Google App Engineを使うときはcygwin版pythonを使おう。

2010年3月4日木曜日

symfony1.0 から symfony1.4 への移行メモ

メモ書きなので品質低
まずsymfony1.0を1.1にアップグレードプロジェクトを1.2から1.3/1.4にアップグレードする1.3の廃止予定および削除される機能に目を通す。バージョンが違うが参考になると思う。翻訳して頂いた方々に感謝。
OpenPNE プラグイン開発者のみなさんにsymfony1.4 対応のお願いもちょっと参考になる。

pluginが読み込まれない

symfony1.4では一つ一つ手動でconfig/ProjectConfiguration.class.phpに設定する必要がある。
Symfony tutorial参照

また、フォルダの名前を何とかPluginにする必要がある。1.0はplugins/testのような名前でも大丈夫だったが、ソースを見た感じplugins/testPluginのように、Pluginという文字がフォルダに付いてないと認識しない。

Propel

PropelはPrope1.2.1-devlからPropel1.4にバージョンが上がっている。

1.4ではDB抽象化レイヤに使っていたCreoleをやめて、PDOを使うようになっているので、PDOのExtensionをインストールする。config/databases.ymlはdsnという項目が追加されている。generate:projectしたときに作成されているサンプルを参考にdsn形式で書く。

クエリを直で実行したいときに使う、Propel::getConnection()で帰ってくるインスタンスが、以前のCreoleのインスタンスからPropelPDOと変更になっているのでこれに依存してるコードはエラーになる。自分のコードの場合、ExecuteQueryみたいな関数を用意していたので、とりあえずNextやgetRow関数等を実装したアダプタを間に挟んで返すようにして対応した。また、build-modelで自動生成したモデルの何とかPeerクラスのdoSelectRSはなくなり、代わりにdoSelectStmtが追加されている。他ORM経由でクエリを処理する場合はいまのところ特に問題ないと思う。

稼働中のDBからモデルを再生成する。propelのDB設定ファイルconfig/propel.iniは1.0のものをそのままコピーしてもbuild-model時にエラーになるので、公式ページを参照して再設定する。 
symfony propel:build-schema --xml
symfony propel:build-model
以前のバージョンのPropelでもbuild-schema時に生成されるschema.xmlをそのままbuild-modelしようとするとエラーに成る場合があるが、今回のバージョンでもやはり直ってないようだ。DBはMySQL。その場合、schema.xmlのDATEやTIMESTAMPのdefault=””を削除したり、CURRENT_TIMESTAMPとあるところを'0000-00-00 00:00:00'とすると直るかも知れない。

TEXTで定義したフィールドは自分の環境だとCHARSETをbinaryにしているためか、propel:build-schemaで自動生成した場合BLOBになってしまっている。その場合Propel1.2.1-devの時は問題なかったが、単純に->getFieldName()とした場合リソースが返されるだけで、値が取得できなくなっている。その場合fgets( $resouce )等すれば取得できが、今回はbuild-schema --xml時に生成されるXMLを編集して、type=”BLOB”と成っているところを、type=”LONGVARCHAR”と変更して対応した。

web debugツールバーで実行したクエリを表示するにはログを取る必要がある。ログを取る場合は、databases.ymlでDebugPDOを指定する。

Propel1.4の新機能については下記のブログが参考になる。
Propel 1.4のWhatsNewの超訳

フォーム・バリデータ関係

1.0でのvalidater.ymlやhandleErrorメソッドなどのVlidate処理は、sfForm関に吸収されたような形になっている。

このあたりは気合いを入れて書き直すしかないと思う。フォームが多いシステムの場合移行に際して一番大変になると思う。sfFormはなれれば割と使いやすいと思うが癖が強い。ただフォーム処理なんて言うのは大体こういうものなので気にしないこととする。自分の場合表示には使わずバリデータにしかつかっていない。フォームをプログラムで自動生成するのはデザインとの関係もあるので余り好きではない。

ビュー関係

component slotを使用していて、ビューをmodule.ymlで変更しているとき、別のモジュールから他のモジュールのcomponent slotを呼ぶとビューの変更が適用されず、デフォルトのsfViewPartialで実行されエラーになる。この辺の設定はモジュールごとに設定されているためだが、カレントのモジュールの設定しか読み込まれないためエラーになる。仕方ないのであまり良い方法とは思えないが、defaultモジュールを別途設定するために適当な場所に下記のコードを入れて対応した
sfConfig::set('mod_default_view_class','hrPHPTAL');
sfConfig::set('mod_default_partial_view_class','hrPHPTAL');

また、デフォルトのビュー(sfPHPView)を使用している場合、デフォルトで自動的にエスケープされるよう修正されているので修正する必要がある。

 

ルーティング関係

クラス構成やメソッドが大きく変わっているため、依存しているコードがあれば作り直しが必要になる。大体代わりの機能は用意されているので頑張って書き換える。

YAML関係

YAMLのパーサがSPYCからsfYAMLとなり、YAML1.2の仕様となった。大きな変更点として、offやnoは文字列として扱われるようになり、修正が必要になる。一部symfony側でoffやnoの場合でも、偽となるよう処理されているところもあるが、cache.ymlでoffと指定しているのにキャッシュされてしまうなどの問題もあったので、offやnoは全部falseに書き換え、onやyesはtrueに書き換えた。
またSPYCで通っていたものがsfYamlでエラーになる。
自分のパターンでは、正規表現を指定する部分で、例えば、 type: [a-z]{1,3}と指定しているとエラーで、これをtype: “[a-z]{1,3}”と指定してやれば問題なくなった。{}はYAMLでは連想配列を指定するのに使うので微妙な扱いの変更だと思う。

キャッシュ関係

CacheHelperについては以前書いたのでこちらを参照。

アクションキャッシュについては、sfViewCacheManager->setSuffix()(違うかも)が無くなってしまったため、ログインが必要なページでログイン前とログイン後で別々のキャッシュファイルを生成するようにしていが、それが出来なくなってしまった。そのため別途FileCacheクラスを作成して対応した。

まとめ

今回symofny1.4へ移行した理由は、symfony1.0のサポートが切れ、今後も長く続くプロジェクトでシステムの拡張・修正が必要だったため。Propel関係はうまくやれば修正にそれほど時間はかからないが、フォームの修正、キャッシュ関係の修正、動作確認にかなりの時間が必要だった。特に理由がない限り既存のプロジェクトをsymfony1.4に移行するのはあまりオススメできない。
そういえば上記には書いていなかったが、admin generatorも移行に相当時間が必要そうだったため、symfony1.0のまま運用してお茶を濁している、、これも追々移行したい。

2010年2月18日木曜日

symfony 1.4 CacheHelper(cache関数) のその後

前回のまとめ

symfony 1.4のCacheHelperには余分なキャッシュを保存してしまう問題がある(多分)。sfViewCacheManagerのソースを見た感じでは、module名とaction名のみを基準にキャッシュ管理をしており、独自の内部URI(default/indexのような)をもたないcacheは制御が行えず、action cacheも同時に保存してしまうような仕様になっている。

対策

結局別にCacheHelperを作成する事で対応した。元々symfony 1.0の時からCacheHelperでは対応できない機能があったので、別途CacheHelperを作成して対応していた。

ソース github ライセンスはMIT

追加した機能は

  1. 前回のブログで書いた余分なキャッシュを保存しないようにした。
  2. 別途引数にinternalURIを追加して、複数のページで同一のキャッシュを使用できるようにした。
  3. すでにキャッシュ済みか調べるための関数の追加した。

使い方

  • cache関数の代わりにhr_cache関数を使用する。
  • cache_save関数の代わりにhr_cache_save関数を使用する。
  • hr_is_cached()関数はactionですでにキャッシュ中か調べて余分な処理を行わないようにするようなときに使用する。hr_is_cachedを実行した際の結果はstatic変数に保存され、その後hr_cacheが使用されたときと結果が同じになるよう保証する。(実行されるタイミングが異なるため、lifetimeが切れて結果が異なる場合があるため)
  • 第三引数のinternaURIは例えば、URIが値を取る場合、’default/index?a=123’と’default/index?a=abc’では別のキャッシュが保存されるが、internalURIに’default/index’と指定することで共通のキャッシュが使用される。

効果

  • ディスク領域の節約になる。
  • internalURIを適切に設定するとキャッシュヒット率が上がり各種負荷が下がる。

本来であれば、componentを使えば良いが、componentを使うとtemplateファイルが分離され、設定も多少煩雑になるため、自分の場合どちらかというとCacheHelperを好んで使っている場合が多い。

ただ、殆どの場合通常のCacheHelperで問題はないと思う。効果があるのはページ数が数万~数十万単位あるサイトで、キャッシュ書き込みによりディスク負荷があがり、キャッシュファイルの増大によって(Linux等のOSの)ページキャッシュが圧迫されパフォーマンスが急激に低下するような場合それなりの効果があると思う。

2010年2月12日金曜日

symfony 1.4.1 fragment cache(cache関数)のよくわからない挙動について

 

テスト用のapp testを追加

symfony generate:app test
symfony generate:module test default

apps/test/config/settings.ymlのprodにcache:true追加。apps/test/modules/default/actions/actions.class.phpのexecuteindexをreturn sfView::SUCCESS;に書き換え。

1 この状態でアクセスしてみる。

ブラウザで/test.php/を開く。

キャッシュされたファイルを調べる。
find cache/test/prod/template/ -type f

(何も表示されない)

2 indexSuccess.phpでfragment cacheを使って見る。

例えばapps/test/modules/default/templates/indexSuccess.phpを下記の内容にする。

<?php if( !cache('cachename', 600) ):?> 
cache time - <?php echo date('Y-m-d H:i:s')?> 
<?php cache_save()?> 
<?php endif;?>

ブラウザで/test.php/を開く。

キャッシュされたファイルを調べる。
find cache/test/prod/template/ -type f

cache/test/prod/template/localhost/all/default/index/_sf_cache_key/cachename.cache - これは良いとして、、
cache/test/prod/template/localhost/all/default/index.cache - これは何?

 

3 GETで値を投げてみる。

ブラウザで/test.php/?a=1を開いてみる。
ブラウザで/test.php/?a=2を開いてみる。

キャッシュされたファイルを調べる。
find cache/test/prod/template/ -type f

cache/test/prod/template/localhost/all/default/index/_sf_cache_key/cachename.cache
cache/test/prod/template/localhost/all/default/index/a/1.cache - 増えてる!
cache/test/prod/template/localhost/all/default/index/a/2.cache - 増えてる!
cache/test/prod/template/localhost/all/default/index.cache


4 まとめ

もしかしたら根本的に勘違いをしているのかも。そもそも1.4だとhttp://www.symfony-project.org/jobeet/1_4/Propel/ja/21 に特にcache関数の説明がないからサポートしてない?

追記 1

symfony 1.0で同じ事をすると、
cache/test/prod/template/localhost/all/test.php/symfony/index/_sf_cache_key/cachename.cache
というファイルしかできない。個人的にはこれが理想。
現状だと、symfony1.4の挙動が仕様なのか、バグなのわからない。

cache()関数使用後に、sfViewCacheManager->isCachable(‘default/index’)するとこれがtrueを返す。symfony1.0だとそもそもcache()関数後(action実行後)にisCachableは呼び出されない。lazy_cache_key(ビューキャッシュのために遅延キャッシュキージェネレーション)の関係で後から呼び出されるのかもしれない。lazy_cache_keyの意味はわからないが、、

追記2

今のsfViewCacheManagerだと、複数回cache関数を実行すると一番最後に実行したcache()関数のlifetimeでdefault/indexのキャッシュが作成される。これは仕様としてどうなのか。(注:後から気付いたがこれは間違い)

プログラムを見た感じ、moduleとactionでisCachableを切り分けていて、cache関数を実行すると、currentのmoduleとactionでcacheがonになり、actionがキャッシュされてしまう。

追記3

sfCacheFilterのexecute()を$filterChain->execute();return;としてもキャッシュが生成される。どのタイミングで生成してるんだろう。余分に作られている(と思っている)キャッシュはactionキャッシュと同じものだが、上記のテストの方法で作った場合default/indexのcacheはenabled:falseになるので、sfCacheFilterがキャッシュチェック時(sfExecutionFilterの実行前)にはisCachableはfalseとなり、キャッシュを使用することはないし作ることに意味がないように見える。

追記4

sfFileCache::write()で書き込み状態を調べてみると、余分に作られている(と思っている)キャッシュは、ブラウザでリロードするたびに書き込まれる、、

追記5

余分に作られている(と思っている)キャッシュは、最終的にsfPHPView::render()の、
$content = $viewCache->setActionCache($uri, $content, $this->isDecorator() ? $this->getDecoratorDirectory().'/'.$this->getDecoratorTemplate() : false);
上記の部分で書き込まれている。setActionCache()はソースを見た感じ、そのアクションがisCacheableでwithLayoutの場合のみキャッシュとして保存するようになっている。本来であれば、cache.ymlで特にenabled: trueとしているわけでもないので、キャッシュとして保存されるはずはないが、cache関数を実行した時に実行されるaddCache関数がactionキャッシュのキャッシュ情報を上書きするような動作になっており、action cacheも保存ししまっているように見える。

 

全体的に見た感じバグっぽいが、、対応は後日検討。新たにcache関数の代わりのヘルパーを作るか、sfViewCacheManagerの動作を変えるか、sfPHPViewの動作を変えるかして対応することにする。

追記6

cache.ymlにてdefault/indexをenabled:trueを指定した場合cache/test/prod/config/module_default_config_cache.yml.phpに下記の内容が追加される。

$this->addCache($moduleName, 'index', array('withLayout' => false, 'lifeTime' => 600, 'clientLifeTime' => 600, 'contextual' => false, 'vary' => array ()));

ヘルパーのcache関数を実行した場合、下記のような内容が実行される。

$cache->addCache($params['module'], $params['action'], array('withLayout' => false, 'lifeTime' => $lifeTime, 'clientLifeTime' => $lifeTime, 'vary' => $vary));

この場合両者とも、module = default, action = index,となるため、cache関数を実行した場合もcache/indexをenable:trueとした場合と同じになり、default/indexのaction cacheが保存される事になる。

 

まとめ2

sfViewCacheManagerのソースを見た感じでは、module名とaction名のみを基準にキャッシュ管理をしており、独自の内部URI(default/indexのような)をもたないcacheは制御が行えず、action cacheも同時に保存してしまうような仕様なのかも知れない。
ただキャッシュファイルは保存されるが、sfCacheFilter実行時にはまだcacheヘルパーは実行されておらず、addCacheされていないためそのキャッシュファイルは使用されない。ようするにキャッシュファイルがあってもsfExecutionFilterは実行されるし、保存されたaction cacheは特に意味はない。

対策!

現状1.0から1.4へ移行しようとしているサイトは、1.0で運用している現在のキャッシュファイルだけでも数十ギガあるため、このまま1.4に置き換えるとそれだけでさらにキャッシュファイルのサイズが数倍になってしまうことが予想される。

対策として、CacheHelperの動作を変更した。ソースは後日アップ予定。

2010年1月21日木曜日

twitterでキーワードを検索して、その結果を出力するPHPスクリプト

<?php  
$keyword = 'ソフトバンク';  
$query = 'http://search.twitter.com/search?q='.urlencode($keyword);  
$dom = new DomDocument('1.0','UTF-8');  
@$dom->loadHTML( file_get_contents( $query ) );  
$xpath = new DomXPath( $dom );  
foreach( $xpath->query('///span[@class="msgtxt ja"]') as $element ){  
 echo $element->nodeValue."\n\n";  
} 

2010年1月20日水曜日

DELL PowerEdge 2970にdebian lennyでLAMP(Linux Apache PHP MySQL)設定メモ

主に自分用なので読みにくいのはごめんなさい。また下記の設定だけで設定終了というものではありません。

サーバスペック

メモリ16G Six Core Opteron 2.2GHz x 2 SAS 146G 3.5inch 15000rpm x 2 (RAID 1) X25-E 64G x 2 SATA 1T 3.5inch 7600rpm x 2(RAID 1) X25-EとSATA 1Tは後付けした。3.5inchのHDDを選択した場合、もともとSASx6のバックプレーンが付いているので、そこにSAS-SATA変換ケーブルでSATAのHDDをつないだ。この場合、BIOS画面でバックプレーンにケーブルが刺さってないとエラーが表示され、この状態だとIPMIの設定画面にいけなくなるので注意する。X25-EはSSDのためハードウェア障害はないものとしてRAID 1の設定はしていない。ただ、何らかの原因で壊れることも予想されるので、予備にもう一つつけてある。SATA 1Tはデータ保存用。

ディスク、パーティションの設定など

全HDDでLVMを設定。その上でSAS 146Gはext3。X25-Eはxfs。SATA 1Tはext3で設定。RAIDコントローラにバッテリーが付いているので、より高速化させるためにext3はdata=writeback、xfsはnobarrierでマウント。

各ディスクのスケジューラがデフォルトでcfqになっているので全てdeadlineに変更。HDDはデフォルトのキューサイズが128になっているので1024に変更。SSDはデフォルトのまま。 echo deadline > /sys/block/sda/queue/scheduler echo 1024 > /sys/block/sda/queue/nr_requests

Xen

前書いたDebian GNU/Linux 5.0(Lenny)でのXenの設定メモを参照

DELL OMSA、IPMI関係

IPMIでSOL(Serial Over LAN)の設定。ネットワーク越しにBIOSの操作ができたりする。
BIOSで以下の値を設定。 Serial Communication を On with Console Redirection via COM2 External Serial Connector を COM2 Failsafe Baud Rate を 57600

BIOSでIPMIを有効にして静的IPアドレスを割り振る。パスワードは記号を使うとうまく認証できない場合があった(DELL SC1435とか、、)ので、最初はアルファベットだけのほうが無難かも。

ここまでの設定で、ipmitoolからsolでBIOSの設定が出来るようになる。実験するにはサーバ再起動直後にipmitoolからsolでつないでみる。さらにipmitoolのsolからLinuxのログインコンソールまで表示させるには

/boot/grub/menu.listのXen以外のkernel行の後ろに、rhgb console=tty console=tty console=ttyS1,57600n8を追記。 Xenをインストールしたり、カーネルのアップデートをしたりすると消えるので注意。 後はdebianインストール後/etc/inittabに下記の行を追加。 co:2345:respawn:/sbin/getty 57600 ttyS1 vt100-nav これでipmitoolからsolを使ってログインすることが出来る。Xenカーネルについては設定が面倒そうなのでやり方を追求していない。緊急時はとりあえず通常カーネルが動けば良いのでよしとする。ipmitoolのsolはよくSegmentation faultするので、した場合、一回deactivateしてからactivateする。solから抜けるには~.と入力する。

debian設定関係
公開鍵の設定 wget http://ftp.sara.nl/debian_sara.asc apt-key add debian_sara.asc /etc/apt/sources.listに追加 deb ftp://ftp.sara.nl/pub/sara-omsa dell sara 時間帯によっては重くてアクセス出来ないため注意。 apt-get update apt-get install dellomsa apt-get install ipmitool これで各種ハードウェアの状況を確認するための、omreportとipmitoolコマンドが使える。 omreport chassis ipmitool sesnor nagiosやcactiで監視するときは以下のスクリプトを使用する。

- omreport chassisのOKを数える。/etc/snmp/omreport.sh #!/bin/sh omreport chassis | awk '/^Ok/ {LINE+=1} END{print LINE}'

- 指定した項目の値(Sensor Reading)を取得する。/etc/snmp/ipmi.sh #!/bin/sh /usr/bin/ipmitool sdr get "$@" | grep 'Sensor Reading' | awk '{ print $4 }'

SNMP(XenのホストOS上での設定)

apt-get install snmpd /etc/default/snmpd の127.0.0.1を削除(ローカルからのみのアクセス制限) /etc/snmp/snmpd com2secのpublic行をコメントアウトして、private行のコメントアウトを解除。ファイヤーウォールは適切に設定すること。 disk / 10000 のコメントアウトを解除。

omreportやipmitoolの設定をして、外部から値をとれるようにする。下記サンプル。/etc/snmp/snmpd.confに追記。 extend .1.3.6.1.4.1.2021.54 Temp /etc/snmp/ipmi.sh 'Temp' extend .1.3.6.1.4.1.2021.55 Temp /etc/snmp/ipmi.sh 'Ambient Temp' extend .1.3.6.1.4.1.2021.60 FAN /etc/snmp/ipmi.sh 'FAN 1 RPM' extend .1.3.6.1.4.1.2021.61 FAN /etc/snmp/ipmi.sh 'FAN 2 RPM' extend .1.3.6.1.4.1.2021.62 FAN /etc/snmp/ipmi.sh 'FAN 3 RPM' extend .1.3.6.1.4.1.2021.63 FAN /etc/snmp/ipmi.sh 'FAN 4 RPM' extend .1.3.6.1.4.1.2021.70 DELL_OMSA /etc/snmp/omreport.sh

MySQL関係(XenのゲストOS上での設定)

今までTritonnを使用してきたがMySQLのバージョンが古くなってきたため、最新が使いたいのものあり、MySQL最新版とTritonnを併用することにした。

MySQL最新版インストール

MySQL5.5最新版ダウンロード 解凍後、/usr/local/mysqlに移動。 support-filesコピー cp support-files/myなんとか.cnf /etc/mysql/my.cnf cp support-files/mysql.server /etc/init.d/mysql

必要に応じてDB初期化 cd /usr/local/mysql; ./bin/mysql-install-db –-user=mysql /etc/init.d/mysqlを編集 basedir=/usr/local/mysql datadir=/var/lib/mysql startの処理をしているところの、$bindir/mysqld_safe の直後に—defaults-file=/etc/mysql/my.cnfを足す。- あと何故か/etc/init.d/mysql stopがエラーになるので、 /etc/init.d/mysqlのstopの処理をしているところの、 if (kill -9 $mysqld_pid 2>/dev/null) を if trueに置き換える どうもkill –9 $mysqld_pidするとmysqlのpidが変わってその後のkillがうまくいってないようだ。原因不明。

ユーザ、グループの追加 /usr/sbin/groupadd mysql /usr/sbin/useradd -g mysql mysql データベース初期化 scripts/mysql_install_db --user=mysql cp -a data /var/lib/mysql /etc/init.d/mysql startで起動。 特に問題なければサービスとして登録して、自動起動させる。 update-rc.d –f mysql defaults
Tritonn最新版インストール

Tritonnは通常ポート3306のところを、3307にして起動する。

Tritonn最新版ダウンロード 解凍後、/usr/local/tritonnに移動。

support-filesコピー cp support-files/myなんとか.cnf /etc/mysql/tritonn.cnf cp support-files/mysql.server /etc/init.d/tritonn

必要に応じてDB初期化 cd /usr/local/tritonn; ./bin/mysql-install-db –-user=mysql ※/usr/local/mysql以外の場合mecabが正しく動かないため下記の操作が必要。 cp –a /usr/local/tritonn/etc/ /usr/local/mysql/etc/ cp –a /usr/local/tritonn/lib/mecab/ /usr/local/mysql/lib/mecab/ /etc/init.d/tritonnを編集 basedir=/usr/local/tritonn datadir=/var/lib/tritonn startの処理をしているところの、$bindir/mysqld_safe を書き換え./bin/mysqld_safe —defaults-file=/etc/mysql/tritonn.cnfにする。 その直前の行に、cd $basedirを足す。こうするとmecabにパスが通るようになる。

/etc/mysql/tritonn.cnfを編集 portを3307に。socketを/var/run/mysqld/tritonnd.sockに修正 /etc/init.d/tritonn startで起動。 問題があれば、起動時のオプションに—senna-log –senna-log-level=DEBUGを追加して、様子を見る。

特に問題なければサービスとして登録して、自動起動させる。 update-rc.d –f tritonn defaults

Apache関係(XenのゲストOS)

今回はコンテンツ配信用とプログラム処理用にApacheのインスタンス二つを一台のサーバ上で起動するように設定する。コンテンツ配信用はapache2lightとし、プログラム配信用のポート番号は8080とする。二つに分ける理由は、同時アクセス数が極端に増えた場合に、プログラム処理用のインスタンスだけではmod_phpでメモリが大量に消費され効率よく同時アクセス数を増やせないため。

Apache2台構成設定

apt-get install apache2

cp –a /etc/apache2 /etc/apache2light cp –a /etc/init.d/apache2 /etc/init.d/apache2light

/etc/apache2/ports.confのポート番号を8080にする。 /etc/apache2/sites-available/defaultのポート番号を8080にする。

/etc/apache2light/apache2.confの/etc/apache2/のパスを/etc/apache2light/に置換する。Pidファイルを/var/run/apache2light.pidとする。

/etc/apache2light/envvarsのPidファイルを/var/run/apache2light.pidとする。

/etc/init.d/apache2lightの/etc/apache2/のパスを/etc/apache2light/に置換する。$APACHE2CTLでconfigtest以外のコマンドを実行している行を、$APACHE2CTL –k コマンド名 –f /etc/apache2light/apache2.confに修正する。

起動テスト等行ってみて、特に問題がなければ、 update-rc.d –f apache2light defaults

Apache設定(プログラム処理用)

主にプログラム処理用。KeepAliveをオフにする。

mod_statusを入れる。ExtendedStatusをOnにする。 mod_deflateを切る(PHPが処理するため) mod_rpafを入れる(リバースプロキシを使った際の環境変数HTTP_HOSTの自動書き換え) mod_php5を入れる。 mod_rewriteを入れる。

Apache Light設定(コンテンツ配信用)

主にコンテンツ配信用。こっちはKeepAliveをオン。PHPの処理はプログラム処理用のApacheに投げる。動的に作成した画像などは最初だけプログラム処理用にアクセスし、その後はコンテンツ配信用から生成されたキャッシュを送る。キャッシュ機構自体はPHPのフレームワークSymfonyのものを使い、Apacheのモジュールは使わない。キャッシュフォルダにすでに画像が作られていれば、プログラム配信用にアクセスせずにその画像を送るというような設定にする。

mod_statusを入れる。ExtendStatusをOnにする。 mod_deflateを切る(PHPで処理するため) mod_expireを入れる(cssや画像などに適切にExpireを入れる。YSlowやPage Speedで見ながら調節する) mod_rewiteを入れる。
Apache二台体制の考察

現在すでに本稼働中で、その様子を。 コンテンツ配信用はサーバは17.9リクエスト/秒、プログラム処理用は8.73/秒となっている(ピーク時はこれの二倍ぐらい)。ただ処理中のプロセス数は、プログラム処理用はKeep AliveをOFFにすることにより、プログラム処理用はコンテンツ配信用の1/5程度のプロセス数しかない。 http://d.hatena.ne.jp/naoya/20080212/1202830671 また上記ブログを参考に、各Apacheプロセスの消費メモリは下記の通り。 # ./apache_shared_memory_size.sh PID RSS SHARED 22825 63028 35756 (56%) 22827 63520 36824 (57%) 22836 53004 35076 (66%) 22848 45608 36936 (80%) 22850 45224 34760 (76%) 全7プロセス。 (MaxRequestsPerChild 100のためSHARED数値が安定していない多分)

# ./apachelight_shared_memory_size.sh PID RSS SHARED 22129 3892 3524 (90%) 22583 3628 3240 (89%) 22597 3676 3256 (88%) 22726 3896 3536 (90%) 22789 3612 3252 (90%) 22790 3620 3212 (88%) 全33プロセス。 上記のように、コンテンツ配信用は1プロセスあたり大体360kしか消費していないのにたいして、プログラム処理用は1プロセスあたり9M~28Mほど消費しているのがわかる。これがもしプログラム配信用のみで運用しているとした場合ざっとした計算で3~4倍ほどメモリを消費することになる。

簡単にApacheの全プロセスの合計消費メモリ調べたい場合は下記ブログが参考になる。 http://d.hatena.ne.jp/kazuhooku/20091221/1261392787 上記ブログのスクリプトを何回か走らせると、150M~180Mと表示される。 現状ではサーバに大分余裕があるので、今の10倍ぐらいの同時接続数までならなんとかなると思う。

Apache二台体制とキャッシュについて

このApache二台体制の場合、いかにプログラム処理用を使用しないかで、メモリ消費量が大きく変わってくる。たとえば、動的に生成する画像が一つのWebページに10枚はってあるとすると、これだけでプログラム処理用のプロセスを10も起動しなければならなくなり大量のメモリを消費する。この場合、この動的に生成する画像がリアルタイムに生成する必要がなければ、キャッシュすることで、コンテンツ配信用のみで処理することが出来、より高速化できメモリの節約にもなる。 この場合、Apacheのレベルで処理するのであれば、mod_cache、mod_disk_cache、mod_mem_cache等のmoduleをコンテンツ配信用サーバに組み込んでキャッシュ処理を行う。 またPHPのフレームワークsymfonyでsfSuperCachePluginつかうことにより、一回目のアクセスのみ、プログラム処理用サーバで画像を生成しキャッシュすることで、二回目のアクセスからはコンテンツ配信用サーバからそのキャッシュされたファイルを直接配信するよう設定することも出来る。symfonyに詳しくない方もいると思うので、簡単に内部処理を説明すると、特定のキャッシュ保存用ディレクトリにファイルがあるかないかをmod_rewriteのRewriteCond -fで調べ、もしファイルがあればそのファイルをクライアントに送り、無ければPHPで処理する。Apacheのmoduleを使用するよりこちらのやり方の方が無駄がないが、運用方法によってどちらが良いかは変わってくると思う。

以上、、