みんなやりたいようで検索したらいっぱい出てきた。一番上に出てきたのは中の人が作ってるやつ。
ただソース見たら php で作られてた。あまり変なもん入れたくないのでスルーして別のを探したらこういうのを見つけた。
これは Python 製らしい。偏見かもだけど Python 製なら大丈夫そうだなと思ってこっちを使ってみることにした。
一応ちゃんと動いている。
みんなやりたいようで検索したらいっぱい出てきた。一番上に出てきたのは中の人が作ってるやつ。
ただソース見たら php で作られてた。あまり変なもん入れたくないのでスルーして別のを探したらこういうのを見つけた。
これは Python 製らしい。偏見かもだけど Python 製なら大丈夫そうだなと思ってこっちを使ってみることにした。
一応ちゃんと動いている。
フッターのキャッシュとかフラグメントキャッシュはできたので、サイトのなかで一番重いアーカイブページのキャッシュを考えてみることにした。
当初はアーカイブページも、一番重い記事一覧表示部分をフラグメントキャッシュしてみていた。しかしあまり効果がなかった。Sinatra は仕組み上、コントローラーにいろいろ書いてしまいがちになり、アーカイブページのコントローラーが Fat になっていた。そのためフラグメントキャッシュをしたところでコントローラーの重い処理はビューがレンダリングされる前に走ってしまい、キャッシュの意味があまりない状態だった。
Rails だったらアクションキャッシュとかあるけど、先日から Folk して改造を進めている sinatra-cache でできるのはページキャッシュとフラグメントキャッシュだけなため、ページキャッシュをしてみることにした。
ページキャッシュの残念な点は Nginx 側の設定も必要なことだ。せっかく Lokka は Heroku や Sqale など Rack アプリケーション置ける PaaS にならどこにでも置けるのに、Nginx の設定変更を前提とした変更を行うと CMS for Cloud ではなくなってしまう。しかしこのブログは自分の勉強の場でもあるのでえいやっとやてみた。
sinatra-cache は Lokka + Nginx + Unicorn という環境であれば、LOKKA_ROOT/lib/lokka/app.rb
を開いて以下のようにしてやれば使えるようになります。
require 'lokka'
require 'sinatra/cache' # <= 追加
module Lokka
class App < Sinatra::Base
configure do
# ...
register Sinatra::Cache # <= 追加
set :cache_enabled, true # <= 追加
# ...
end
# ...
end
end
とかやってやれば、勝手に LOKKA_ROOT/public
にキャッシュファイルを作るようになります。Nginx 側でキャッシュファイルがあれば Unicorn に proxy せずキャッシュファイルを返すようにすればページキャッシングで爆速になる。sinatra-cache は {$request_filename}.html
という名前でキャッシュファイルを作るので Nginx の設定は以下のような感じになる。
server {
location {
root /var/wwww/portalshit/public;
# ...
if (!-f $request_filename.html) {
add_header Cache-Control public;
rewrite (.*) $1.html;
break;
}
# ...
}
}
ポータルシットはトップページも重いのでトップページもページキャッシュしようかなと思ったけど、ページングとかあるのでいろいろ面倒くさいことになることに気がついた(キャッシュファイルがない状態で Google のクローラーが 35 ページ目とかをクローリングしてたら 35 ページ目の html が index.html としてキャッシュされてトップページに来た人が全員 35 ページ目を見ることになってしまう)のでやめた。
キャッシュ、レスポンスを速くしてくれるけど何でもキャッシュすれば良いわけではないし奥が深い。
Lokka でキャッシュしたいと書いてたけどキャッシュできるようになった。
結局 sinatra-cache を使った。sinatra-cache、2 年くらいメンテされてなくて全然だめかなと思ってたけどドキュメントが執拗なほど詳しく書かれてて使い方がわかりやすかったので使ってみることにした。フラグメントキャッシュがページごとに共有されなくて非効率的だったところは適当に改造した(morygonzalez/sinatra-cache · GitHub)。
footer 部分だけキャッシュするようにしてるのでトップページのレンダリングはあまり変わってないように感じる。個別記事ページは HTML が返ってくるまで 2, 3 秒かかってたのが 500ms から 600ms くらいになった。割とよいと思う。
いまのところ cache の有効期間みたいのを設定できないのでこれを任意の時間で設定できるようにしたい。しばらく使ってみて問題なさそうだったら Lokka 本体に Pull Request してもよいかも。
友達の結婚式があって、先週末は土日月と東京に行ってた。東京観光してきたけどなかなか楽しかった。ただベビーカーに子ども乗せてうろうろするのはやはり大変だった。初めて来る場所でエレベーターの場所を探すのは大変で、移動に1.5倍くらい時間がかかった。小さなスーツケース持ってるだけなのに健康な人がエレベーター乗るの止めて欲しいと思う。車いすの人やベビーカーを優先して欲しい。東京には電車内でベビーカーたたまずにいるとぶち切れ始める怖い人がいるとニュースでやってたから、いきなり刃物で刺されたりしないか心配だったけど通勤ラッシュにぶち当たらなかったので刺されずに済んだ。
1泊目は目黒のホテルに泊まった。駅から近くて建物はきれいで一階はスターバックスだったけど部屋が狭かった。出張とかで来てて泊まる分には良いけど、家族連れで泊まるにはしんどいなぁと思いながら泊まった。
結婚式が横浜の崎陽軒であったので近いところに泊まろうと思って、2泊目はみなとみらいに宿をとった。2泊目のホテルは良かった。ツインルームで部屋が広く、部屋からみなとみらいの夜景と観覧車が見えて、金持ちになった気分味わえた。日曜の夜ということと楽天トラベル様のおかげで安く良いホテルに泊まれて本当に良かった。ホテルのフロントの人とかは若干冷たい感じだったけど、貧民なのに一人一泊2万円くらいするホテルに5000円くらいで泊まれて本当に幸せだった。おまけに結婚式で食べ足りなかったので閉店間際の成城石井に押しかけて定価の半額以下になっていた弁当を買ってきて食べた。高いホテルに格安料金で泊まって高級スーパーの半額以下の弁当を食べるなんて本当に俺は下衆だなと思ったけど充分満足感あった。オススメです。
EC2 の micro で Lokka を運用してたけど、Unicorn が CPU 100% 近く使う状態が続き、リクエスト送ってレスポンスが返ってくるまでに 30 秒近くかかる状態になってて、AWS での運用を諦めざるを得なかった。さくら VPS に戻したところ、CPU 使用率もロードアベレージも落ち着いた。しかしレスポンスは遅くて、HTML が返ってくるまでに 5 秒くらいかかってる。
Newrelic を入れて調べてみた。Application が一番遅い。MySQL も遅いけど気になるレベルではないみたいだった。
という構成で運用していたのが遅い原因だったかも。App も DB も同じサーバーに置いたら Newrelic 上で Database が遅いと表示されなくなった。つまり Application をどうにかするしかない。
Lokka で動いててもそんなに遅くないページもある。komagata さんのブログは遅くない(heroku に置いてあるっぽいので heroku 側でキャッシュとかいろいろやってあるのもあると思う)。自分のブログに関してはテンプレートで最近の過去数ヶ月の月ごとの記事数表示したりタグクラウド出したりしてるところが遅そう。なので重い処理のところをフラグメントキャッシュしたい。
導入できて動いた。勝手にページキャッシュする。フラグメントキャッシュできるけど、キャッシュのキーが URL のディレクトリベースのため、効率が悪い。
導入できなかった。Padrino::Routing
に依存してるっぽくて素の Sinatra で使いづらい。
これも Sinatra が前提。view から使うフラグメントキャッシュ専用ヘルパーメソッド。なんか Sinatra が内部的に使ってるインスタンス変数を上書きするというやり方みたい そのままでは Lokka で使えず <- イマココ。
最後のやつが一番導入に近いところまで来てるっぽいけど、断片の部分だけ haml のコードを実行させて結果を取得させる、というところがなかなか難しい。Rails の ActionController::Caching
のコードを見て参考にしようとしてみたけどちょっとよく分からなかった。
実は最近、仕事で Ruby 書かないおじさんになってしまったので週末に Ruby のコード見てもすんなり頭に入ってこない。ダメだなぁ。
以前、Unicorn が暴走して困ってるという記事を書いていた。しかしよくよく調べてみると暴走してるのは Unicorn ではなく、Syntax Highlight に使っている Python の Pygments を呼び出している Ruby のプロセスだった(pygments.rb)。こいつが CPU をバカ食いしてしまい、アプリケーションのレスポンスがすこぶる悪くなっていた。
少し前、Pure Ruby の Pygments 互換 Syntax Highlighter で Rouge というのを発見していたので、Python を spawn するプロセスがなくなれば CPU バカ食いも止まるはず、と思って pygments.rb から移行してみた。しかし…
なんと逆にサイトが重くなってしまった。やはり Pure Ruby のプログラムは重いみたいである。pygments.rb は C のネイティブコードも含んでいて、速さがボトルネックになるところは C で処理しているみたい。
というわけで残念ながら EC2 のマイクロインスタンスで Rouge を常用するのは無理そうなので Pygments + pygments.rb に戻します。あとはキャッシュして誤魔化すしかなさそう。
一月くらい前から Lokka の master ブランチを自分のブログ用のブランチに merge してサイトをデプロイすると謎の白画面が出るようになっていて困っていた。現象は極めて謎で、ローカルの開発環境(RACK_ENV='development'
)では見られず、本番(RACK_ENV='production'
)だけで発生した。HTTP ステータスコードは 1054 が返ってきたりする。なんか変な gem でも入れてしまったかなと休みの日に動作検証したりしていたんだけどついぞ分からなかった。
SQL 弱者なので気がついてなかったんだけど、2月15日の commit で Site モデルに新しいフィールドが追加されていた(Add default markup in admin/site/edit · 2243dc5 · lokka/lokka ※この機能便利すね)。なので bundle exec rake db:migrate
しないといけなかったわけだった。ローカルで動いている開発環境(データベースは SQLite)では Migration なんて行ってないんだけどエラーは出なかった。本番は MySQL で動いていて、こちらでだけエラーが出るようだった。
しかしいざ migrate しようとすると失敗する。
bundle exec rake db:migrate
Upgrading Database...
rake aborted!
Invalid default value for 'updated_at'
のようなエラーが出る。updated_at
のデフォルト値がおかしいらしい。このときのテーブルの構造を見てみると以下のような感じだった。
mysql> desc entries;
+-----------------+--------------+------+-----+---------------------+-----------------------------+
| Field | Type | Null | Key | Default | Extra |
+-----------------+--------------+------+-----+---------------------+-----------------------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| user_id | int(11) | YES | | NULL | |
| category_id | int(11) | YES | | NULL | |
| slug | varchar(255) | YES | | NULL | |
| title | varchar(255) | YES | | NULL | |
| body | text | YES | | NULL | |
| type | text | NO | | NULL | |
| draft | tinyint(1) | YES | | 0 | |
| created_at | timestamp | NO | | 0000-00-00 00:00:00 | |
| updated_at | timestamp | NO | | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
| frozen_tag_list | text | YES | | NULL | |
| markup | varchar(255) | YES | | NULL | |
+-----------------+--------------+------+-----+---------------------+-----------------------------+
調べてみたところ MySQL の Mode が NO_ZERO_DATE
になっている場合、MySQL は timestamp
型のフィールドのデフォルト値に 0000-00-00 00:00:00
みたいな値を設定することを許さないらしい。 mysql - Invalid default value for 'create_date' timestamp field - Stack Overflow
検証用に別にテーブルを用意して bundle exec rake db:setup
してみたところ、以下のような構造のテーブルができた。
mysql> desc entries;
+-----------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------------+------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| user_id | int(11) | YES | | NULL | |
| category_id | int(11) | YES | | NULL | |
| slug | varchar(255) | YES | | NULL | |
| title | varchar(255) | YES | | NULL | |
| body | text | YES | | NULL | |
| markup | varchar(255) | YES | | NULL | |
| type | varchar(50) | NO | | NULL | |
| draft | tinyint(1) | YES | | 0 | |
| created_at | datetime | YES | | NULL | |
| updated_at | datetime | YES | | NULL | |
| frozen_tag_list | text | YES | | NULL | |
+-----------------+------------------+------+-----+---------+----------------+
created_at
と updated_at
は datetime
型になるらしい。なので以下のような ALTER 文を実行した。
mysql> alter table entries modify column created_at datetime, modify column updated_at datetime;
mysql> desc entries;
+-----------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| user_id | int(11) | YES | | NULL | |
| category_id | int(11) | YES | | NULL | |
| slug | varchar(255) | YES | | NULL | |
| title | varchar(255) | YES | | NULL | |
| body | text | YES | | NULL | |
| type | text | NO | | NULL | |
| draft | tinyint(1) | YES | | 0 | |
| created_at | datetime | YES | | NULL | |
| updated_at | datetime | YES | | NULL | |
| frozen_tag_list | text | YES | | NULL | |
| markup | varchar(255) | YES | | NULL | |
+-----------------+--------------+------+-----+---------+----------------+
これで最新のコードをデプロイしても真っ白画面になることはなくなった。以前遭遇した更新時に created_at
の値が更新されてしまう問題 もフィールドの型が timestamp
だったのが原因なのだと思う。SQLite から MySQL への移行は一筋縄では行かないことが分かった。
DataMapper はソースコード内の記述内容から動的に Migration を行えるけど、ActiveRecord みたいに $APP_ROOT/db/
ディレクトリに Migration ファイルを作ってくれたりしないので DB スキーマの変更が必要なことに気がつきにくい。便利だけど不便な感じがする。Rails で $APP_ROOT/db/
以下にアホみたいにファイルが出来ていくの嫌だと思っていたけど、スキーマ変更に気がつかずコードをデプロイしてウェブアプリケーション停止みたいな自体は防げると思った。
ブログ書こうと思ってパソコン開いて「ついでに最新版の変更を取り込むか」とかやるとデプロイできなくなったりして書きたかった記事が書けず残念な感じになる。はてなブログでブログ書いてて画面が真っ白になったらひとでくんさんに不具合報告して直してもらえば良いので楽だと思う。