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を使用するよりこちらのやり方の方が無駄がないが、運用方法によってどちらが良いかは変わってくると思う。

以上、、

2009年12月22日火曜日

Kindle使ってみた

!!アップデートされたKindle2やKindle3ではすでに古い情報が含まれています!!

勢い余ってKindleを買ってしまった。前々から興味はあったものの、有効活用できないことは目に見えていたため、購入していなかったが、この度仮対応だがPDFが読めるようになったのと、タイミング良く円高と値下げで結構お手軽価格になっていたのもあり購入してしまった。

DSCN1272 
郵送用のダンボールと一体化したKindleのケース

Kindleの詳細については下記のサイトが詳しいと思う。
【Kindle】 Amazon Kindle FAQ – 購入前情報からJailbreakまで 【まとめ】

購入してから一ヶ月ほど立つが、かなり気に入った。もともとiPod touchで本(PDF)を読んでいた。

- iPod touchで本(PDF)を読む場合のデメリットとして、

  1. 横書きなら横書きの幅を、iPod touchの小さい画面に納めなければならないため、字数が多い本だと非常に細かくなる。480ドットだと解像度が若干足りない場合がある。(Kindleは800ドット)
  2. 図を解説しているようなページの場合、図と文章が一つの画面に収まらないため、読みづらくなる。
  3. 何かiPod touchで本を読んでいると目が疲れる。

Kindleを気に入った主な理由は3で、どうもiPod touchで本を読んでいると目が疲れてストレスを感じる。また、iPod touchを使い始めてから、平日は起きている時間は常に液晶を見ているような感じになってしまった。仕事中は液晶を常に見ているし、電車のなかでiPod touch、お風呂の中でiPod touch、、これでは正直ちょっと目が心配だ。その点Kindleは電子ペーパーということもあり、表示が自然でストレスを感じにくいと思う。この辺は個人差はあると思うが、自分の場合はそうだった。ただ、Kindleといってもやはり、紙の本と比べるとコントラストが低く、紙の方が読みやすいのには変わりない。

逆にiPhone OSの方が良いなと思う場合もある。iPhone OSに最適化された、子飼弾さんの弾言や決弾は非常に読みやすいと思うし、産経新聞等の新聞や、A4サイズの雑誌など、Kindleでも一画面に収まらないような書籍は、ズームやスクロールがスムーズなiPod touchの方が読みやすい。

DSCN1279 
同じページをiPod touchとKindleで比較
(参考:クリスアンダーソン - フリーからお金を生み出す新戦略)

紙の本の電子書籍化(PDF化)

話は変わるが、もともとKindleとは関係なく紙の本の電子書籍化を進めていた。自分の生活として出張が多いが、技術書は結構厚みがあり重く、正直持ち歩いて読むのは非現実的だった。また出張先で、あ!あの本読みたい(参考にしたい)!というときに紙の本だと不可能だ。これが電子書籍であれば、常に全て持ち運べるし、iPod touchやKindleがあれば何時でも何処でも好きな本が読める。また最近のOCRは優秀で日本語もそこそこ認識し、アルファベットについてはかなりの認識率で、紙の本から取り込んだPDFを検索可能なPDFにすることが出来る。技術書を検索するときはアルファベットが多いため便利だ。また、Windows SearchやGoogle Desktop SearchもPDF検索に対応しているため、全ての電子書籍を横断検索することも可能だ。自分の場合Windows Searchを設定してあるので、スタートメニューに単語を入れれば該当するpdfが数秒で検索される。自分にとっては紙の本よりも電子書籍の方が価値がある。

電子書籍化については、裁断機とScanSnapというドキュメントスキャナを使用して行っている。ググってみるといろいろと情報が出てくるので、興味がある方は調べてみると良い。

Kindle最適化PDFの作り方

さてここまで、べた褒め?のKindleだが、Kindleで美しくPDFを表示するにはこの最適化作業が必須だ。そのため、KindleでPDFを見てみようかな?という人もいると思うが正直オススメしづらい。

- Kindleで本(PDF)を読む場合のデメリットとして、

  1. 容量が2Gしかない。ユーザが使える領域は1.4G(多分)しかない。全ての書籍は入りきらないため、iPod touch(64G)のように、何も考えずに同期すればよいわけではない。
  2. 拡大縮小が行えない(画面を横にローテートすることは出来るが)。単純に取り込んだPDFがヘッダやフッタなど含めて全画面で表示されてしまう。さすがにKindleの画面が広いとはいえこれでは肝心の文章が小さく表示されしまい、まだiPod touchの方がましだ!という状況になってしまう。
  3. カラーページなどで薄く取り込まれたページは、そのままKindleで表示しようとすると薄すぎたり、表示ががたがたになってしまったりする。

1はある程度しかたがない。どうしても容量を節約したい場合は、最適化PDFを作成する際に、圧縮率を高めにすると容量を節約できる。
2,3のデメリットを克服するためにPDFをKindle用に最適化する作業が必要になる。2,3に関してはソフトの問題が大きいと思う。Kindle2にはARM系のプロセッサが搭載されており、またクロック数は前世代のiPhone 3Gより速いらしいので、PDFを処理するには十分なスペックだと思う。今後のファームウェアのアップデートに期待したい。
今後のファームウェアのアップデートの内容によっては、この方法は最適では無くなる可能性があるので注意して欲しい。

さてKindle最適化PDFの手順は以下。

  1. まず、PDFを用意する。
    - ScanSnapで取り込む場合は、白黒のスーパーファインで文字をクッキリに設定して取り込むと良い。PC用にカラーで取り込みたい場合はカラーでも良い。
  2. Adobe Acrobatが使える場合は、あらかじめ文章の部分のみおおざっぱにトリミングすると良い。特に色が薄いページがなく文字が大きめの書籍であれば、このままKindleにコピーしても十分読める。
  3. いったん画像として保存する。Acrobatを使うか、なければフリーのPDF-XChange Viewer等で保存する。Jpeg等で保存する場合、画質を最高にしておく。
  4. この項目は手順が煩雑なので飛ばしてもかまわない。文字を可能な限りクッキリさせるため3の手順で作成した画像を、ImageMagicで変換する。5で使用するeTilTranというソフトでも画質の調整はできるので殆どの場合それで事足りるとは思う。
    - ImageMagicをインストールする。自分の場合Windows版を使っている。
    - コマンドラインから各画像をconvert –modulate 110 –gamma 0.3/0.3/0.3ぐらいで変換する。modulateは光を当てる処理。gammaはガンマ補正をかける。
    参考までにカレントディレクトリの画像を変換するRubyスクリプトは下記。convertの引数は日本語が通らないので「各画像のファイル名」を「ファイル名に含まれるページ数」に変換している。convertディレクトリに出力する。
    require 'fileutils'
    Dir.open('.').each do |file|
      if file =~ /(\d\d\d)\.jpg/
        num = $1
        puts file
        FileUtils.mv( file, num + '.jpg' ) if file !~ /^(\d\d\d)\.jpg$/
        `convert  -modulate 110 -gamma 0.3/0.3/0.3 #{num}.jpg convert/#{num}.jpg`
      end
    end
  5. あとはKindleDX用小説レシピ最終版?を参考にeTilTranというソフトで3,4で作成した画像を変換する。このソフトは、例えば画像の文章の部分を自動で認識して、その部分を拡大するという処理を自動でやってくれる。すばらしい!ただこのソフト使い勝手に癖があるため色々試してみて欲しい。上記ブログにはKindleDXの場合のDot-by-Dotは784x1142とあるが、Kindle2では色々試してみた結果560x742のようだ。最終的に出力する画像のサイズを、縦742ドット、横560ドットに設定するとKindle上で拡大縮小が行われないためきれいに表示することが出来る。
  6. あとは変換された画像を、PDFとしてまとめる。自分の場合Acrobatを使っている。

DSCN1290-1291
写真の例だとわかりづらいが最適化した場合(右)は読みやすさが違う。
(参考:可知 豊 - ソフトウェアの基礎知識)

 

まとめ

現状電子書籍はまだまだ発展途上であり、Kindleについてはまだ買い!とはいえないが、色々と可能性を感じさせてくれる。現状でも様々な方法で電子書籍が販売されているが、自分的に必須な、横断検索が可能、様々なデバイスで使用できるというようなものは無い。このあたりが満たされない限りは、電子書籍は自分で作り続けると思う。ただ作るの面倒なので、仮にちょっと高くても良いのでこれらの機能を満たすような電子書籍が早々に販売されることを願う。


防水ケースはお風呂用に。Kindle用ではないがサイズがぴったし。フィルムは丁度なサイズなので長い縦の辺から合わせて張るとうまくはれると思う。ケースはKindle用に買ったがちょっと大きかった。Kindle用に良いサイズのケースを探し中。

2009年12月17日木曜日

読書メモ - ソフトウェアの基礎知識

  1. コピーレフトの意味を今まで知らずにすませていた。GPLのような修正した場合も同じライセンスを適用しないさいよ的なライセンスをコピーレフトという。逆にBSDのようにそのような条項がないライセンスはコピーレフトではない。
  2. 修正BSDライセンスの「修正」は広告条項(著作者の名前を載せなさい!)を外したもの。
  3. まず著作権があり、その上で利用規約(ライセンス)がある。それとは別に特許がある。それぞれを分けて考えるとわかりやすいかなと思った。
  4. それにしてもやはりライセンスの文章は理解しがたい、、
  5. フリーウェア(無料ソフト)と宣言しただけでは、著作権の範囲内での使用が可能なだけであり、無断で配布したり、改変した場合は違法となる。なのでソフトウェアの公開者は可能な限り正しくライセンスの宣言をしなければならない。
  6. MySQLをPHPから使っても(動的ライブラリによる接続)コピーレフトの条項を守らなくても大丈夫というような、特別なライセンスの記述がなかったのがちょっと気になった。
  7. ラスト100ページぐらいはライセンスの文章が載っているが、やはり難しい、、

ネットでちょくちょくGPLライセンスや、BSDライセンスの記述などは読んだことはあったが、やはりライセンス関係は難しく、その場では何となくわかった気になるが、一度まとまった情報を読んでおくと理解が深まると思った。

読書メモ - フリーからお金を生みだす新戦略

  1. 無料化することで、生まれる価値と失う価値がある。
    - 需要が急激に増える。見えない新たな価値を生み出す。
    - 価値が無いものとして乱雑に扱われる。
  2. どんなものでもコモデティ化(その辺に良くあるもの化)すると価値を失う。情報はコモデティ化する。
  3. フリーの中での有料とはある意味動的な金額
    - (iTunes)自分でファイルを整理するのが大変ならば、購入すればよい。「ファイルを整理する」という価値は人によっていくらか異なる。学生ならばただに近いし、時間が無い人であればそれなりに価値がある。他の多くのフリーと有料版(フリーミアム)もこのようなバランスで成り立っている。
  4. オンラインゲームの課金方法もある意味iTunesと同じ。 LV上げするのがいやであれば購入すれば良い(価値がある)。移動するのが面倒であれば購入すれば良い(価値がある)。アバターの見た目を変えたければ購入すれば良い(価値がある)。
  5. 中国、ブラジルの事情(音楽がただ同然)。その上でのビジネスモデル。価値が無料に流れやすい文化。 今後コンテンツはフリーへと向かっていくが、そのような意味では中国、ブラジルは時代の先端を行っていると言って良い。
  6. フリーミアム - 多くの無料ユーザ(フリー)と、一部の有料ユーザ(プレミアム)から成り立つ市場。
  7. 人に信頼されること。たとえフリーだとしても多くの人から信頼を得られれば、その信頼をお金に換える方法はなにかしらある。また、ソーシャル~のように、人と人との信頼で築くメディアも同様。
  8. たとえあるコンテンツが無料になったとして、GDPは下がるわけでなくむしろ増える場合もある。その分の価値は他のところへと流れていく。

現場で今まさにフリーと付き合っている人には良い本だと思います。翻訳文章独特の読みにくさはありますがオススメです。