| @技術/プログラミング

Lokka がすごく遅い

EC2 の micro で Lokka を運用してたけど、Unicorn が CPU 100% 近く使う状態が続き、リクエスト送ってレスポンスが返ってくるまでに 30 秒近くかかる状態になってて、AWS での運用を諦めざるを得なかった。さくら VPS に戻したところ、CPU 使用率もロードアベレージも落ち着いた。しかしレスポンスは遅くて、HTML が返ってくるまでに 5 秒くらいかかってる。

原因調べた

Newrelic を入れて調べてみた。Application が一番遅い。MySQL も遅いけど気になるレベルではないみたいだった。

  • EC2 に Application
  • さくら VPS に MySQL

という構成で運用していたのが遅い原因だったかも。App も DB も同じサーバーに置いたら Newrelic 上で Database が遅いと表示されなくなった。つまり Application をどうにかするしかない。

やろうとしてること

Lokka で動いててもそんなに遅くないページもある。komagata さんのブログは遅くない(heroku に置いてあるっぽいので heroku 側でキャッシュとかいろいろやってあるのもあると思う)。自分のブログに関してはテンプレートで最近の過去数ヶ月の月ごとの記事数表示したりタグクラウド出したりしてるところが遅そう。なので重い処理のところをフラグメントキャッシュしたい。

試したこと

sinatra-cache

導入できて動いた。勝手にページキャッシュする。フラグメントキャッシュできるけど、キャッシュのキーが URL のディレクトリベースのため、効率が悪い。

padrino-cache

導入できなかった。Padrino::Routing に依存してるっぽくて素の Sinatra で使いづらい。

落っこちてた gist (Simple fragment caching in 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 で Syntax Highlight するプラグイン(morygonzalez/lokka-pygmentize · GitHub)、これまで Ajax で Syntax Highlight させてたんだけど、HTTP リクエストが増えていけてないと感じたのでサーバーサイドでハイライトが完結するように変更した。Railscasts の #272 Markdown with Redcarpet - RailsCasts を参考にして、プラグインの中で Entry クラスを再オープンした。こんな感じ。

class Entry
  def body
    doc  = Nokogiri::HTML(self.long_body)
    doc.search("//pre").each do |pre|
      code = pre.css("code")[0]
      pre.replace Pygments.highlight(
        code.text.rstrip,
        :lexer   => code[:class],
        :options => { :encoding => 'utf-8' }
      )
    end
    doc.to_s
  end
end

前の実装はだいぶいけてなかったと思うので随分マシになったと思う。あと この Pull Request でレンダリングエンジンに Redcarpet が追加されたので、GitHub と同じように

```ruby

```

みたいな感じの書き方でコードがハイライトされるようになった。便利。

追記1

ちゃんと動いてなかった…。夜直します…

追記2 2013/02/04 0:45

最終的にオープンした Entry クラスのコードは以下のようになった。

class Entry
  alias_method :original_long_body, :body
  def highlighted_long_body
    syntax_highlight(self.original_long_body)
  end
  alias_method :body, :highlighted_long_body

  alias_method :original_short_body, :short_body
  def highlighted_short_body
    syntax_highlight(self.original_short_body)
  end
  alias_method :short_body, :highlighted_short_body

  def syntax_highlight(body)
    doc = Nokogiri::HTML(body)
    doc.search("//pre").each do |pre|
      code  = pre.css("code")[0]
      lexer = if pre[:class].present?
                pre[:class]
              elsif code.present? && code[:class].present?
                code[:class]
              else
                nil
              end
      begin
        pre.replace Pygments.highlight(
          code.text.rstrip,
          :lexer   => lexer,
          :options => { :encoding => 'utf-8' }
        ) if code
      rescue MentosError
        next
      end
    end
    doc.to_s
  end
end

Lokka、結構メタプログラミングが多くて、Entry クラスのインスタンスの body メソッドは単なるゲッターではなく、 index アクションのときと show アクションのときで別々にエイリアスが設定されていて、index アクションのときは Entry#short_body 、show アクションのときは Entry#long_body が呼ばれるようになっていた。アラウンドエイリアス使って力業で解決したけど他のプラグインが同じように振る舞ったら破滅を招きそうな気がする…。それにしても『メタプログラミング Ruby』読んでなかったらどうすればいいか皆目検討付かなかっただろうなー。

| @技術/プログラミング

最近、会社でリーダブルコードを輪読したり、Fukuoka.rb で Eloquent Ruby を読んだりしていて、メソッドや変数名の長さやコメントについての議論を読む機会があった。

昨日、たまたま 37signals のブログを読んでいたら、Rails の作者である David Heinemeier Hansson もこのトピックについて書いていた。

自分は WEB+DB PRESS で ukstudio さんの書いた RSpec についての記事を読んで感化されて以来、ソースコード中のコメントはすべて悪で、はっきりしたメソッド名、変数名を使えばコメントはいらないという考え方を持っている。DHH もそのような考え方のようだ。

要点をかいつまむ。

多くのプログラマーは短い変数名やメソッド名を好む。短い命名は明確性や簡潔性を犠牲にしているとみんな気づいてるんじゃないかと疑ってるんだけど、実際のところ短い命名を採用したコードが多い。特に「一行は80文字まで」教の連中に。

しかし最近のプログラミング言語は表現豊かなのだから、短い命名にこだわる必要は無い。

extension を ext と略してあるのを見かけると嫌気がさす。アプリケーション固有の略語を作るのはもっとひどい。そんなの二ヶ月後には絶対忘れてる。

過剰に明瞭な名前は一見するとバカっぽい。処理内容よりもメソッド名の方が長いとかね。でも一発でやってる内容が分かることの前ではそのバカっぽさは霧消する。

最近の Basecamp のコードにはこんなのがある。

def make_person_an_outside_subscriber_if_all_accesses_revoked
  person.update_attribute(:outside_subscriber, true) if person.reload.accesses.blank?
end

def shift_records_upward_starting_at(position)
  positioned_records.update_all "position = position - 1",
    ["position >= ?", position]
end

def someone_else_just_finished_writing?(document)
  if event = document.current_version_event
    !event.by_current_creator? and event.updated_at > 1.minute.ago
  end
end

もし十分に明確な命名をしているのであれば、コードにコメントを書く必要がない。コメントというものは往々にして不明瞭な命名をしているコードの中で必要になる。コメント付きのコードはやばい臭いを放っていると思っといた方がいい。

コメントはいらない

リーダブルコードの中にも「名前は短いコメントだと思えばいい」というくだりがあるけど、基本的にあの本には「いいコメントを書け」と書いてあるような気がする。コメントそのものを悪いとみなしていない。

先々週読んだ Eloquent Ruby の Chapter 8. Embrace Dynamic Typing の中では Ruby という言語の特性も踏まえ、コメントはいらないと書いてあった。

例えば Ruby ではメソッド名の最後にはてなをつけてあると Boolean を返すという慣習がある。だからコードの中に「○×を判定し true/false を返します」というようなコメントはいらない。

def is_longer_than?(number_of_characters)
  @content.length > number_of_characters
end

Eloquent Ruby は Chapter 1 でもコメントについて述べている。そこには「コメントを書くべき理由があるコードもある」と書いてある。そのコードの使い方を指南したコメントだ。「なぜそのようなコードを書いたのか」、「コード内で用いているアルゴリズムの説明」、「どのようにして高速化したか」というようなことは書くべきではないと言っている。この辺はリーダブルコードと正反対だ。リーダブルコードでは「なぜそのような実装にしたのか」を積極的に書くように奨励されていた。

ちょっと前にはてブでホッテントリに入ってた、ソースコード内のコメントでコードレビューをやるというやり方は最悪だと思う。売り物のコードの中でああでもないこうでもないと議論するなんて狂ってる。コードが修正されたときに議論の内容まで適切に修正されるとは思えない。下手をするとコメントとコードの内容が正反対になってしまうかも知れない。コードの背景についてはコード内に書かず、GitHub 使ってインラインコメントでやった方が良いと思う。

皆さんはどう思いますか?

| @技術/プログラミング

だいぶ前の記事では Jekyll は Git の hook を使って公開してると書いてた。

しかし記事の本数が増えてくると、 jekyll コマンドで生成した静的なファイルも Git でトラックするのがだるいと感じるようになった。ので Rakefile に以下のような task を追加して、公開先のサーバーに rsync でファイルを転送するように変更した。

desc "Publish"
task :publish do
  path = File.expand_path("_site")
  if system("rsync -avz --delete #{path}/ portalshit:sites/tech.portalshit.net/_site/")
    puts "Your blog was successfully published!"
  else
    puts "Something went wrong..."
  end
end

だいぶデプロイが手軽になった。

| @技術/プログラミング

社畜なのでVドメインからMドメインにドメインの管理業者を変更した。そしたらDNSの設定やらなんやらを忘れていて、tech.portalshit.net が死んでた。最近は技術っぽい内容も www.portalshit.net に書いてるしここの存在意義が微妙になってきた。Jekyll、便利ではあるんだけど、rbenv で Ruby のバージョン上げるごとに gem install jekyll gsl とかやるのが若干めんどくさく感じられるようになってきてしまった。ただ記事を書くためだけにそこまでやりたくないかも。

| @技術/プログラミング

自戒エントリー。

自分は Mongoid を使って開発してるんですけど、時間の境界値のテストが不十分で問題に遭遇したのでメモっておきます。

Mongoid には Date 型や DateTime 型があるけど、データベースにデータを保存するときには Time 型に変換されます(MongoDB に Date とか DateTime がないから?)。Mongo Shell で DB の中の値を見ると Time 型の値が入ってる。

だからクエリを生成するときに Date.today はなるべく使わない方がいい。Date.today では時間を 00:00:00 として扱い、月末のデータの取得に失敗する可能性が高いから。Rails console で確認すると、以下のようになります。

pry(1.9.3-p194)> 1.month.ago.end_of_month
# => Fri, 31 Aug 2012 23:59:59 JST +09:00
pry(1.9.3-p194)> Date.today.prev_month.end_of_month.to_time
# => 2012-08-31 00:00:00 +0900

上記は二つとも「先月末の月の終わり」を検索していますが、後者では時間が 00:00:00 になっています。例えば以下のような Mongoid でのクエリでは、8月31日の午前0時0分1秒以降に作成された Document を取得することが出来ません。ナンテコッタイ!!!

Model.where(:created_at.lte => Date.today.prev_month.end_of_month)

Date.today を時刻の生成の起点にしたとしても、to_time してあげればいいのでは?」と思う方もいるでしょう。確認してみましょう。

pry(1.9.3-p194)> Date.today.prev_month.end_of_month.to_datetime
# => Fri, 31 Aug 2012 00:00:00 +0000

#prev_month メソッドが呼び出された時点のオブジェクトの型は Date 型なので、Date 型に対して時間の操作を行って最後に DateTime 型に

結論

時刻まで考慮した時間の範囲でクエリを作るときは以下に留意すると良いでしょう。

  • Date.today は使わない
  • DateTime.now1.month.ago などを使う