preload
2月 24

ブログ Persistence is Power を、こちらのサイトに移管しました。

長らく Livedoor Blog にてお世話になってきましたが、さくらのレンタルサーバで使用できるディスク領域がいつの間にか増えていて、だいぶ余裕ができたので、こちらに持ってくることにしたのでした。

以前のエントリは、すべてこちらに移管しました。DNS もこちらのサーバに向けましたので、1 ヶ月ほどしたら Livedoor Blog の方は削除してしまおうと思っています。

ブックマークや検索エンジンなどで、旧サイトへたどり着いた方には、ちょっとご不便をおかけしてしまいますが、サイト内検索などをご利用いただければ幸いです。あと、データ移行時に、記事内のソースコードのインデント情報が消失してしまったり、サイト内リンクがリンク切れになってしまったりしてます。ごめんなさい。たぶん、そのままです。

ではでは、こちらでもよろしくお願いいたします。

12月 27

先日 GitHub というのを初めて使ってみて、テキトーな小物をちょいちょい晒していくにも都合が良さそうだということがわかったので、今回、標記のプログラムも晒してみる事にしました。使用許諾条件は、基本的には the MIT License に基づきますが、「(迷惑メール送信に用いるなど)悪用・乱用を禁止する」という条件を付加したいと思います(っていうほど作り込んでないので、まあ、あんまし意味のない付加条件だと思いますが)。



MbSmtp – GitHub



MbSmtp は、Net::SMTP の拡張クラスで、日本の携帯向けにできるだけ効率よくメールを送る事を目的とした SMTP クライアントです。



MbSmtp は以下の機能を提供しています。

  • SMTP セッションあたりの送信通数が設定値以下になるよう、セッションを自動分割制御します
  • キャリアからの通信ブロックが発生した際、 設定時間待機した後、
    設定回数、失敗したメールのみ再送リトライします
  • メール送信処理中にエラーとなるアドレスが含まれていた場合、同一セッションで未送信のアドレスリストからエラーアドレスを除去します
  • セッション終了時、 送信リポートの構造体を返します
    • リクエストメッセージ数, 送信完了メッセージ数, エラーメールアドレスなど

各種設定値は lib/mb_smtp.rb 内で指定されていますが、必ずしも適正値ではありません。適宜設定を変更してご利用ください。



以下、簡単な使用例です。基本的には Net::SMTP と同じように使えますが、キャリアメールサーバは :docomo, :au, :softbank などといったシンボル指定で open できます。また、以下のようにセッション内である程度まとまった数を送信したとしても、1 セッションでの送信通数が設定値を超えないよう、勝手にセッション分割してくれます。その他、キャリアのメールサーバからの応答に応じて、エラーアドレスの振り分けや送信成功数のカウント、キャリアブロックに対する待機などをおこない、最後に MbSmtp::Result 形式でのレポートを生成する、といった動きをするようになっています。

@mail = TMail::Mail.new # MbMail::DMail とかでもOK
## @mail を適当に組み立てる
@result = MbSmtp.start(:docomo) do |smtp|
50.times do
smtp.send_mail @mail.encoded, @mail['from'].to_s, @mail['to'].to_s
end
end
puts @result # => MbSmtp::Result による送信結果レポート

この他の使い方や、動きの詳細については spec/mb_smtp/*.rb にある spec ファイルや lib/mb_smtp.rb 本体をご覧ください。



ちなみに MbSmtp は、元々は、個人的にメール配信時のキャリアのメールサーバの振る舞いを調べてみようとごにょごにょやっていたときの副産物です。実サービスでの利用実績はありませんし、これにより高速に携帯メール配信ができるという保証も検証結果も一切ありません。もちろん、これを使用した事で、キャリアブロックされまくってマトモにメールが送れなくなった、とかになっても、僕の方で責任はとれませんです。あくまでご参考まで、ということで。。

Tagged with:
12月 25

git とか GitHub とかいうのを使った事がなかったので、このたび初めて使ってみました。というわけで、標記の Rails プラグインを the MIT License にて公開します。


MbMail – GitHub


MbMail は、Rails で日本の携帯向けサービスをつくる際、 メール扱い周りで発生するちょっとした面倒を回避するための小物寄せ集めプラグインです。rails-2.0.2, tmail-1.1.0 環境下での動作を確認しています。また、rails プラグイン形式で配布していますが、MbMail::DMail は rails 環境に関係なく使えます。


MbMail は以下の機能を提供しています。

  • MbMail::DMail
    • キャリア間HTMLメール(デコメール, デコレーションメール, デコレメール)の相互変換
  • MbMail::MbMailer
    • ActionMailer の拡張
      • 標準で使用する charset (エンコーディング)を ISO-2022-JP に変更
      • 文字コード変換器に NKF を使用するように変更 (機種依存文字の送信を可能に)
      • 日本語ヘッダを作成するためのメソッド base64 を追加 (Mailer 内で日本語Subjectや日本語Fromを簡単に記述)
      • RFC違反のドットが連続するアドレスが扱えるよう、TMail::Parser を置き換え

たとえば、以下のように au デコレーションメール形式のファイルがある場合、これを load し to_docomo_format とすれば、docomo のデコメールフォーマットが得られます。

au = MbMail::DMail.load("au_decoration_mail.eml")
docomo = au.to_docomo_format
puts docomo.encoded # => docomo デコメールフォーマット文字列

その他、使用方法サンプルは、git リポジトリの中の spec ファイルたちをご参考ください。


パッケージに含まれるクラスのうち、MbMail::MbMailer は以前、こちらのエントリでも取り上げた「ドット連続アドレス問題」などに対応するものです。ただし、MbMailer を使用する事により TMail::Parser を書き換えてしまいますので、ご注意ください(MbMailer を使用するに当たって、ActionMailer の使い勝手を残しつつ TMail の一部だけ書き換える、ウマい方法が思いつかず。。誰か助けて)。


MbMail::DMail も MbMail::MbMailer も個人的な利用実績がありますので、基本的な部分はそこそこ使えると思います。が、プラグイン形式に切り出したのはこれが初めてですので、何かと不都合があるかもしれません。(絵文字の変換辺りで微妙にオカシイところがあるかも。)あと、git の使い方がよく分かってないうちは、何かと不慣れなことをしでかすかもしれません。諸々ご理解の上、ご利用いただければ幸いです。


なお、再掲を含みますが、上記のプラグイン提供に当たりお世話になった方々をご紹介しておきます。勉強させていただきました。ありがとうございました。

  • 携帯メールを扱うに当たっての base64 メソッドの実装や NKF 利用のアイデアとして、以下のサイト様を参考にさせていただきました
  • DMail の絵文字相互変換に Jpmobile の絵文字変換テーブルを使用させていただいています
  • ActionMailer の拡張時に TMail の幾つかのクラスを書き換え利用させていただいています
  • サンプルで使用している GIF 画像に 素材屋イチゴアポロ の素材を利用させていただいています

ついでに、以前こちらで紹介した「ケータイサイトで機種情報を取得する Rails プラグイン mbterm_db」も GitHub に引っ越しました。ついでに更新しようと思ったけど、更新の仕方を忘れたので、そのうちメンテします。


MbtermDb – GitHub


それでは皆様、よいクリスマス&よいお年をお迎えくださいませ。

Tagged with:
12月 11

以下のように around_filter 内で reset_session するアクションに対し、大量のアクセスを重ねていくと、メモリ使用量ががつがつ増大していく。

class FugaController < ApplicationController
around_filter :foo
def index
end
protected
def foo
reset_session
yield
end
end

こいつを script/server -e production で起動し、ab -n3000 http://localhost:3000/fuga/ などにより多量のリクエストを送ると、メモリ使用量が 80MB を超えるまでに増大し、リクエスト完了後もまったく解放されない。foo 内で yield しなくても同様。セッションストレージにデフォルトの CookieStore を使った場合のほか、:active_record_store, :mem_cache_store を使っても同じ。session_options[:expires] の設定も関係無し。
なお、before_filter や after_filter で reset_session した場合は、問題は起こらないようだ。また、rails-2.0.2 環境で発見したが、rails-2.2.2 でも再現する。

状況解析のため、こちらのエントリ(Memory leak profiling with Rails) を参考にメモリ利用状況をプロファイルしてみたところ、around_filter を使った場合は String のインスタンス数が一気に膨れ上がった後、一向に解放されないことがわかった。(before_filter を使った場合は、リクエスト処理完了後、それなりに解放された。)

次に、この String の正体を見るために、上記の MemoryProfiler の :string_debug オプションを使って ObjectSpace 中の String をダンプしてみたところ、session_id などセッションデータと思しき文字列が大量に入っていた。リクエスト終了後は、CgiRequest や CGI::Session などのリクエストやセッションに絡むオブジェクトはすぐに解放されていた事から、何らかの原因でセッション内文字列のみ GC 対象にならない状態になっていると思われる。

さらに、上記の String がどの時点で生成されているのかを追ってみた。

actionpack-2.0.2/lib/action_controller/cgi_process.rb L.150-

private
# Delete an old session if it exists then create a new one.
def new_session
if @session_options == false
Hash.new
else
CGI::Session.new(@cgi, session_options_with_string_keys.merge("new_session" => false)).delete rescue nil
CGI::Session.new(@cgi, session_options_with_string_keys.merge("new_session" => true))
end
end

どうも上記の CgiRequest#new_session の CGI::Session.new で確保されているようだ。何が原因なのか追いたいが、CGI::Session は ActionPack で拡張されており、どうも切り分けが難しい。いろいろ書き換えながら試してみたところ、オリジナルの CGI::Session 中:

lib/ruby/1.8/cgi/session.rb L.299

ObjectSpace::define_finalizer(self, Session::callback(@dbprot))

ここのファイナライザ定義があることで、around_filter でのみリークするようだ(これを試しに無効にすると、リークしない。謎)。かといって、こんなところを無効にするわけにはいかず.. と、今度は around_filter と before_filter とで、何が変わってきているのか、を追ってみた。

actionpack-2.0.2/lib/action_controller/filters.rb L.709-

def run_before_filters(chain, index, nesting)
while chain[index]
filter, index = skip_excluded_filters(chain, index)
break unless filter # end of call chain reached
case filter.type
when :before
filter.run(self)  # invoke before filter
index = index.next
break if @before_filter_chain_aborted
when :around
yielded = false
filter.call(self) do
yielded = true
# all remaining before and around filters will be run in this call
index = call_filters(chain, index.next, nesting.next)
end
halt_filter_chain(filter, :did_not_yield) unless yielded
break
else
break  # no before or around filters left
end
end
index
end

ソースを追う限り、filter 処理は適用順序に並べた配列形式に変換された後、before_filter も around_filter も上記の ActionController::Filters::InstanceMethods#run_before_filters で実際のフィルタ処理がおこなわれているように見える。で、around_filter では、上記の filter.call(self) へ渡しているブロックで index = call_filters している箇所があるが、いろいろ切り分けていってみるに、どうもここが怪しい。around_filter に入れ子になっている filter を再帰的に処理しているようだが、ここの中で CGI::Session.new するとセッション中の String のみが解放されない状態になってしまう.. ということだろうか。

もうひといきな気がするんだが、ちょっとツラくなってきたので、とりあえずここまでで POST しておく。Ruby の GC の問題なのか、Rails の黒魔術のせいなのか、まだ微妙なところだ。とりあえず before_filter にしとけば問題は起こらないのだが、多数のフィルタ処理をサイトの全域で使うようなサービスの場合、個々のコントローラには around_filter をひとつ書いておいて、その filter 内に多数のフィルタメソッドを並べる、というのがラクチンだとおもう。うーむ、悔しいが before_filter で回避してしまおうか.. フィルタ内で、セッション検査をおこなった上で、不正セッションであれば reset_session して仕切り直す、みたいなのって、わりとあるような気がするんだけどなあ.. ハマってる人が見当たらない。

Continue reading »

Tagged with:
10月 31

@東京ステーションコンファレンス。本日1日目は諸事情あり十分な聴講時間がとれなかったこと&明日2日目は行けそうにない事から、今日聞けた中で一番面白かった Ameba(アメブロ) の話のメモをとりあえずポストしておきます。スピーカーはサイバーエージェントの岡田さん、大黒さん。


セッションのメモは以下に転記しますが、講演の内容は、2006 年以降アメブロで行われた Oracle => MySQL 移行の苦労話や、増大する PV (月間50億PV!) による負荷対策の取り組み、各種ストレージエンジン x ファイルシステムの組み合わせでのパフォーマンス検証結果、自作サーバによる各種ディスク構成でのパフォーマンス検証結果などなど、といったところでした。


率直な感想としては、けっこう生々しくて濃いセッションでした。サイバーエージェントさんが社内の研究開発で、ここまで面白そうな事をやられてるとは存じ上げなかったので、そちらも驚きました。プレゼン資料や動画については、後日公開されると伺いましたので、興味のある方は、サイバーエージェントさんサイトとか、このプレスリリース周辺をウォッチしておくと良いかも知れません。


以下、メモ。

  • 組織の話
    • 「新規開発局」以下の組織体系。プロデュース、フロントクリエイティブ、システムクリエイティブ、インフラテクノロジーの 4 区分
    • インフラチームは、更に、インフラの統括的なところと、ネットワーク、DB の 3 区分
    • DB チームの業務は DB に関わる部分全般。監視やら、パフォーマンス改善、研究開発や DC でのラックマウント作業も
  • Oracle からの移行の話
    • 2006 年 9 月時点では Oracle x 2 での Active/Stanby – 月間 4 億 PV
    • 2008 年現在は Oracle x 4, MySQL シングルマスタ x 1 + レプリケーション 41 台。Oracle で管理していたブログ記事や、コメント、トラックバックのレコードを MySQL に移行
    • MySQL への移行前(中?)も、ページの一部(サイドバーとか)へのキャッシュ導入、WebLogic から Apache + tomcat への移行、NFS マウントから WebDAV への移行などのチューニングは、いろいろやっていた
    • MySQL へ移行するにあたって、レコード数の多いデータ(ブログ記事とか 80 百万レコードくらいあったとか)は –replicate-do-table しつつサーバ/テーブルを分割。最終的に月間 50 億 PV 捌けるようになったとか
    • Oracle からのデータ移行は、ダンプしたやつを scp とかで運んで LOAD DATA INFILE するのが速い。
    • 移行ツールが一見ラクそうだが、やってみるとアプリ側の処理がボトルネックになってるようだった。LOAD DATA INFILE で 2 億件ほどのレコードを 4 時間で移行できた
    • DB を変えるにあたって、DB に適したシステム設計にできるよう、UI 側のデザイン見直しにも積極的に関与したとか。ブログ管理画面に年月タブを表示したり
    • INDEX の見直し、クエリの見直し、explain での確認を丁寧にやった
    • 最終的に、一画面表示の際の、参照系のクエリ発行回数は増加したが、ディスクの I/O はかなり軽減し、スケールアウトしやすい構成にできた
    • 現在の構成で 35000-40000 クエリ/分くらいだが、60000 クエリくらいまでは行けそう
  • ストレージエンジン x ファイルシステムの比較検証の話
    • 開発では CentOS 4 系、ファイルシステムに ext3、DB に MySQL 4.1 系 MyISAM 利用がスタンダードになっていた。
    • サイバーエージェントでは MyISAM が好まれている。テーブルロックはテーブル分割で回避できているし、InnoDB は運用時の扱いにくさもあるので避けられているとか
    • 一部 MySQL 5.1 を採用したサービスもあるが、もっといい組み合わせが無いかと、いろいろ試してみた
    • ext3, xfs, zfs の比較。OS が異なるため厳密な比較はできなかったが、ext3 < xfs で xfs のスループットが 1 割ほど良い結果が得られた。zfs はチューニング次第で ext3 レベルまで持っていけるかも
    • xfs は journal log 周りにチューニングの余地があり、まだ伸びしろがありそう
    • MyISAM, InnoDB, Maria の比較。InnoDB は想定ほど悪くはなかった
    • 組み合わせ的には xfs + MyISAM がもっともよさそう
  • 自作サーバの話
    • 自身で機器選定してサーバ構築&パフォーマンス検証。ハードウェア構成検証、スキルアップ、コスト削減のための取り組み
    • HW-RAID, SW-RAID, SSD によるディスク構成比較
    • HW-RAID なかなかよい。SW-RAID は I/O が増大してくると不安定に。SSD は読み込みは抜群に高性能だが、書込みパフォーマンスが悪い

ブログアプリってことで、参照系アクセスが多いんでしょうね。50 億 PV でも、シングルマスタ& MyISAM テーブル分割で回避できちゃうんだなあ。いや、もちろん、言うほど簡単じゃないんだろうけど。しかし 41 台レプリケーションで LAN 内ネットワークトラフィックは問題にならないんだろうかとか思ってたら、その辺の議論も質疑で交わされてて、そちらも参考になりました。いまんとこ、ネットワーク側はそんなに問題になってないとか。うんー、勉強になりましたです。


なお、上記メモ後半部分の、サイバーエージェントさん社内研究成果については、コンファレンス会場の展示会場ブースで見せてもらう事ができます。他のテーマも含め、いろんなネタがファイルされていたのですが、こういう研究活動のアウトプットがしっかりまとめられているという体制/仕組みもすばらしいなあと思ったのでした。


以上です。一部理解が追いつかなかったところもあり、正確にメモできてるかは微妙です。すみません。なんかオカシイんじゃねえかとおもうところとか、訂正とか指摘いただける方は、コメントとかで教えてください。


ちなみに、明日はニフティさんココログの PostgreSQL => MySQL マイグレーション事例紹介セッションがあるそうです。こちらも聞いてみたいのですが、行けそうにないので、誰かレポートしてくれるとうれしいです。

Tagged with: