taslamの日記

>>mizincogrammerに移転しました。こちらは、今後更新されません。<<

2008-07-30

[][]並列処理でActiveRecordを使う

※ドキュメントを読みながらこんなもんかな?とやってみたやつなので問題あるかもしれません。何かあればコメント頂けると嬉しいです。

例えば、DBからデータを取り出して逐次メールを送信する場合。

よく知られているようにメールの送信はコネクションの確立やSMTPサーバの処理などの待ち時間が長く、逐次処理をしていると無駄が大きすぎる。

処理を並列化して、あるメールの送信待ち時間を他のメールの構築等に充てて無駄をなくすことを試みる。

環境

処理の並列化

Thread.newを使う。単純に実装すればこんなかんじ。

# 未送信のレコードをそれぞれ別々のスレッドで処理するサンプル
threads = []
mails = find(:all, :conditions => ["sent = ?", false])
mails.each do |mail|
  threads << Thread.new do
    Thread.pass
    Mailer.deliver_mail(mail)
    mail.update_attributes(:sent => true)
  end
end
threads.each { |t| t.join }  # すべてのスレッドの処理が終わるのを待つ

ActiveRecordの並列化

処理は並列化できたが、これだとDBへのコネクションは1つしかなく、例えばスレッド毎にトランザクションを開始するようなことはできない。ActiveRecordのコードを追ってみたところ、どうやら

ActiveRecord::Base.allow_concurrency = true

とすれば良いようだ。

こうすることで、スレッド毎にコネクションを保持するようになり、スレッド毎に別々のトランザクションを開始することができる。

これらを踏まえたサンプル

コネクションが増えすぎないように、あらかじめ決めておいた数のスレッドで並列処理する。メールの重複送信を防止するため、PostgreSQLの行レベルロックを活用した。

ActiveRecord::Base.allow_concurrency = true  # マルチスレッド対応
class Mail < ActiveRecord::Base
  validates_presence_of :recipients, :from, :subject, :body

  # 送信時に最大何個のスレッドを作るか
  cattr_accessor :max_threads
  @@max_threads = 20

  class << self

    # すべての未送信メッセージを送信
    # 送信に成功したメールモデルのインスタンスの配列を返す
    def send_all!
      sent_mails = []
      threads = []
      mails = find(:all, :conditions => ["sent = ?", false])  # 未送信のメールを取り出す
      # max_threads個のスレッドで並列処理
      max_threads.times do
        threads << Thread.new do
          Thread.pass
          while mail = mails.shift
            sent_mails << mail if mail.send!
          end
          clear_active_connections!  # 処理が終わったのでコネクションを切断
        end
      end
      threads.each { |t| t.join }  # すべてのスレッドの処理が終わるのを待つ
      sent_mails
    end

  end
 
  # メール送信
  # 送信前には行レベルロックを行い二重送信を防ぐ
  def send!
    self.class.transaction do
      if new_record? || find(:first, :select => 'status', :conditions => ["id = ?", id]).status
        false
      else
        sended_mail = Mailer.deliver_mail(self)
        update_attributes(:sent => true)
        true
      end
    end
  end
  
end
# ひとつだけ送信
mail = Mail.new(:from => 'hoge@mizincogrammer.com',
                :recipients => 'fuga@mizincogrammer.com',
                :subject => 'Hello',
                :body => 'Rails World!!')
mail.save
mail.send!
# CRON等でまとめて送信
Mail.send_all!

2008-07-29

[]ActionCacheについてきっちり理解する

FragmentCache等に比べ、活用されることの少ないActionCacheですが、正直よく理解してないという方も多いのではないだろうか。

Rails 2.1のActionCacheのコードをざっと眺めて、簡単に特徴をまとめたいと思う。

「ActionCacheはフィルタ

ActionCacheは、キャッシュが存在すれば、キャッシュの内容を表示してfalseを返す(処理を止める)」というフィルタとして実装されている。

具体的には、caches_actionを呼び出すと、around_filterでActionCacheFilterのインスタンスが設定される。

これがどういうことかというと、

  • キャッシュが存在すれば、アクションは実行されない。つまり、アクション内にボトルネックとなる処理がある場合、有効なキャッシュ手段となる。
  • フィルタは実行されるので、「ログイン済みのユーザのアクセスのみページ(キャッシュ)を表示」といった、フィルタを活用する処理は可能
  • ただし、caches_actionよりも前にbefore_filter等を呼び出すか、prepend_before_filterを使用することが必要。(キャッシュが見つかった時点で処理を中断してしまうので、その後に実行されるべきフィルタは実行されない)

2008-07-28

[][]PS3HTMLレンダリングエンジン開発者「PS3ブラウザに対応しました」と言われたい

http://www.itmedia.co.jp/news/articles/0807/28/news016.html

ソニーが開発した独自のエンジンを搭載したタブブラウザで、「PCと同等に使える、テレビに特化したWebブラウザ」を目標に改善を進めている。

Web開発者はInternet ExplorerFirefoxはもちろん、WiiiPod touchなども意識し、それぞれに最適化したコンテンツをリリースしている。だがPS3はまだまだ。最適化したいと思ってもらえるぐらいの存在感を打ち出していきたいという。

ふざけんな(゚Д゚)

そっちがIEFirefoxに合わせろ、といいたい。できればFirefoxで。

ただでさえ各主要ブラウザでのチェックと修正に手間掛かるのに、これ以上無駄な仕事を増やすんじゃねーよ、と。

2008-07-22

[]朝鮮弾入れはなぜ摘発・禁止されないか

パチンコで現金を得るのは違法賭博だと思うのですが、検察が摘発しないのはなぜですか?警察が摘発しないのは、利権関係だと分かるのですが

腐ってるなぁ、日本。

近所にも数件あるけど、百害あって一利なし

昼間っからパチ屋に行く連中は運転も荒っぽいの多いし。

2008-07-18

[]こんなキャッシュ機能が欲しい

パラメータクエリストリング)を考慮したアクションキャッシュが欲しい。

あるのかな?

ないのならつくろうかな。

[]So-net ブログまじめにやれ

今日顧客から「ブログの記事をちゃんと取得できてない」と苦情が入った。

RSSフィードを登録しておいて、数時間毎にクロールして新着記事10件を表示するというもの。

ところが、記事に抜けがあるというのだ。

調べてみると、So-netブログのAtomフィードを登録しているようだったが、このAtomフィードが曲者で、全ての記事のmodifiedフィールドが最新記事と同じになっていた。

このため、新しい順でのソートが正しく行えず、適当な並び順で登録されてしまっていたわけだ。