日記を更新すると、TDiary::Config#data_path/category/ 以下の、カテゴリごとに作られるキャッシュファイルがずいぶんたくさん更新される。全部ではないが半分近い 21のファイルが更新されていた。日記の内容はというと一つのカテゴリしか使っていない。
どこのコードが無駄にファイルを更新しているのかと絞っていくと、category.rbの中の Category::Cache#replace_sectionsだとわかった。ではこの replace_sectionsが悪いのかというとそうではない。replace_sectionsの中の
PStore.new(cache_file(c)).transaction do |db| end
に囲まれた部分をすべてコメントアウトしても 21のキャッシュファイルが一斉に更新されたのだから。一部の PStoreファイルは開いて閉じるだけで常に更新されるのだとしか考えられない。
PStoreは transactionの前後で Marshal::dump の戻り値のサイズと MD5が変化したかどうかを見て、変更があったかどうかを判断し、ファイルに書き込みをするかしないかを決めている。Marshal::load/dump が対称ではないのだろか。(そもそも Hashを Marshal::dumpした結果が一定だと仮定してよいのだろうか*)
原因が何であるにせよサーバーの pstore.rbを書き換えるわけにもいかないので、tDiaryの category.rbに対策を施した。
読み出し専用であることが予めわかっている PStore#transactionはすべて trueを引数にして(readonly=trueの意)呼び出す。実はそういう transactionはすべて PStore#abortで終わっているのでこの対策は必要ないのだが、ファイルを排他ロックすることと MD5を計算する手間が省けるので一応。
肝心の Category::Cache#replace_sectionsは transactionの開始前に変更があるのかどうかがわからないので PStore#transaction(true)は使えない。日付の削除が空振りに終わったときにだけ PStore#abortを呼ぶことにする。これで必要最低限のキャッシュファイルだけが更新されるようになった。
#
# cache each section of diary
# used in update_proc
#
def replace_sections(diary)
return if diary.nil? or !diary.categorizable?
categorized = categorize_diary(diary)
categories = restore_categories
deleted = []
ymd = diary.date.strftime('%Y%m%d')
categories.each do |c|
PStore.new(cache_file(c)).transaction do |db|
db['category'] = {} unless db.root?('category')
if categorized[c] and diary.visible?
db['category'].update(categorized[c])
else
# diary is invisible or sections of this category is deleted
db.abort unless db['category'].delete(ymd)
deleted << c if db['category'].empty?
end
end
end
if !deleted.empty?
deleted.each do |c|
File.unlink(cache_file(c))
end
replace_categories(categories - deleted)
end
end
* 追記:最近の Ruby(1.8.7だか 1.9.0)は Hashの keyの順序を保存しているみたいだけど。