2009年4月アーカイブ
先日webtekoでAmazonEC2についてお話ししてきました。
webteko6:実践AmazonS3&EC2
- 「これって信用できるの?落ちたりしないの?」
- 「SLAもあるし、実際問題落ちていません。」
- 「でもなぁ。サービス丸ごとAmazonに預けることになっちゃうんでしょ。落ちたり、流出したりしない?」
- 「自社サービスならともかく、お客様のデータを置くのは怖いなぁ。」
今日偶然、こんな広告を見つけました。
所有から利用へシフト、技術者の運用負荷とコスト削減を両立させるさくらインターネットの専用ホスティングサービス
「所有から利用へシフト」というと、それはまさにAmazonEC2のキーワードです。
よく考えてみたら、さくらインターネットさんのレンタルサーバだって、データを丸ごと預けてますよね?ここから流出する可能性だって、絶対にないとは言えない。
なのになぜか、さくらインターネットのサーバを借りてそこにお客さんのWEBサイトをのせているWEB制作会社ってたくさんありますよね。
さくらインターネットさんには大変お世話になっておりますし、なんのウラミもないのですけど、さくらならよくてAmazonだと駄目というのは筋が通らんよなぁ、と思いました。
iptablesをリスタートしたらsshが切られる現象が起きまして。 最初は、テーブルの書き換え方がまずいのかな、と思ってはてなで聞いてみたりしていたのですが。
調べた末に、iptablesを終了した際にKernelPanic起こしていたことが分りました。
- おれ最前線ねっと - Kernel panic
- kernel panic は体に悪いぜ | DOOM! DOOMER!! DOOMEST!?
- Bug 456664 - Kernel panic when unloading ip conntrack modules
解決策については、おれ最前線ねっと - Kernel panicで書いていただいているとおり、Kernelを新しくするか、古くすればOKです。
問題なのは、このサーバは遠隔地にあるということで。 カーネルをアップデートしたら再起動しないといけないですけど、普通にやると終了時にKernelPanicを起こして、二度と上がってきません。
メールとか電話で再起動を依頼できるのですけど、必要以上に頼みたくないですし。
そんなわけで、Bug 456664の状況下で、KernelPanicせずにリブートを成功させる方法を見つけたのでメモです。
- とりあえず普通にKernelをアップデート(または、grub.confを修正する)
- /etc/sysconfig/iptables-configを開いて、IPTABLES_MODULES_UNLOAD を"no"にする
これで、iptablesの終了時にモジュールのアンロードをしなくなるので、KernelPanicせずにリブートさせることが出来ます。とりあえずこれで一回リブートしてkernelが変われば、あとは設定を元に戻してもiptablesをrestartさせることが出来るようになります。やれやれ。
#今回の調査のために10回くらい依頼したから、今さら一回くらい一緒なんですけどね。
参考資料
- Amazon Web Services
- AWS Management Console(β版)
- Elasticfox Firefox Extension for Amazon EC2
- Amazon S3 Firefox Organizer(S3Fox)
- 流行りのクラウドサービスを操ってみよう!Amazon EC2/S3環境構築のすべて:CodeZine(コードジン)
- はじめてのAmazon EC2&S3 ~これからの新サービスの公開の形~
- flashcast:フリーで働くITエンジニア集団のブログ
- Amazon EC2/S3を使ってみた- まとめ(目次) - RX-7乗りの適当な日々
- [24時間365日] サーバ/インフラを支える技術~スケーラビリティ、ハイパフォーマンス、省力運用(WEB+DB PRESS plusシリーズ)
実際に使おうと思ったら、自動化のためにコマンドラインツールも必要になるとは思うのですが、とりあえず使い始めるのにJava入れなくてもいいのはなかなか便利だなぁ、と思いました。
ずっとやりたかったことなのですが、動いているシステムはさわるなの方針があるし、データベースの更新となるとどこに影響が出るか分ったもんじゃないので、そう簡単には移行できなくて今までひっぱっていたのです。
sqlite2の理由
たしか、作った当時、windowsでRubyからアクセスできるsqlite3ライブラリが見あたらなかったのです。今考えると、railsで動いているんだから、動かないこともなかったと思うのですけど。当時は気軽にネットワークアクセスできない事情があったので、ファイルベースでWindowsで動く環境となるとsqlite2、という状態でした。
更新の理由
sqlite2では性能上の限界に達したせいです。sqlite2はデータを非圧縮で格納しているらしく、データ量が増えるとファイルサイズが線形に増えていきます。
で、一定のサイズを超えると、たまにinsertに100秒くらいかかるようになって、実用的に使えなくなる現象が発生していました。
どのくらいが限界かは環境にもよるみたいですけど、うちの場合、データベースのファイルサイズが49MBを超えるとこの現象が発生し始めて、おこづかい帳が応答しませんでした、ごめんなさい、とエントリーを書く羽目に陥っていました。
定期的にスパムメールを(手作業で!)削除したり、一年以上使っていない方のデータを削除させていただいたりして対処していたのですが、いつまでもそんなことやってられないので、大規模改修に着手した次第です。
ちなみに、sqlite2から3に移行するに当って実験してみたグラフ。10000bytesくらいのテキストを繰り返し書き込んで、データ量とアクセス時間がどうなるか測定してみたものです。
左軸がファイルサイズ、右軸がアクセス時間。横軸は書き込んだ件数。
縦軸に貼り付いているのがsqlite2のファイルサイズで、横軸に貼り付いているのがsqlite3のファイルサイズ。
赤いのがsqlite2での応答時間で、青いのがsqlite3でのアクセス時間です。
#ええ。これが異常に重い処理の正体です。
工夫
生のデータをつっこんで動かしてみないと何が起こるかなんて分ったもんじゃないので、新バージョンのテストは、生のおこづかい帳システムの裏で一緒に動かしてテストしました。railsみたいにデバッグモードを作って、デバッグモードの時はメールを外部に送信しないようにします。
本番用はm@ememo.jp、テスト用がtest@ememo.jpで動いているので、/etc/aliasesにこんなふうに書くと、ユーザーさんのメールを、本番システムとテスト用システムで、両方で見ることが出来ます。
この状態で3日くらい動かして、ログを眺めて挙動の違いを確認しました。m:|"/var/mailapp/app.rb", test@ememo.jp #実際の@は半角です。
実際やってみると、いいかげんなSQLのせいでデータの順番が異なってしまっている現象がみつかって、テストして正解だったな、と思いました。
この仕組みは普段開発する上でもとっても便利なので、早く作っておけばよかったです。
普通にブラウザから検索できるように、お気に入り(ブックマークレット)を作ってみました。
お気に入りに追加(IE以外のブラウザなら、ブックマークバーにドロップ)しておいてつかいます。
クリックすると検索キーワードを聞いてくるので、適当に入力してOKを押すと、その単語をGoogle英語検索してくれます。
WEBサイトを見ているときに、文字列を選択して「Google英語」をクリックすると、そのキーワードでGoogle英語検索を行います。
参考:
特に多いのが、選択した文字列をWikipedia/英和辞典/和英辞典などで検索するというもの。ところが、インターネットエクスプローラー用です、FireFox用です、と分けてあったり、そもそもFireFoxでしか動かない物が多かったりするのですよね。
Google英語検索ブックマークレットは、インターネットエクスプローラーと、FireFoxと、ChromeとSafariで動作確認してあります。あ。Operaも。
ということで、インターネットエクスプローラーと、FireFoxと、ChromeとSafari(Operaも)で動く、選択した文字列に対して~するブックマークレットを作る方法まとめです。
IEで選択文字列を取得
「document.selection.createRange().text」。IE以外では全く動かない。FireFoxで選択した文字列を取得
window.getSelection。document.getSelectionだと、Chromeやsafariで動かない。Chrome/Safariで選択した文字列を取得
window.getSelectionでうごく。ただし、to_stringが微妙らしく、そのまま文字列として扱うことが出来ないので、「window.getSelection()+""」としておくと文字列として取得することが出来る。これは、FireFoxやOperaでも動作する。IEとそれ以外の分岐
if(navigator.appName=="Microsoft Internet Explorer") がまっとうだけど、長いのでdocument.allが定義されているかどうかで代用。ここまでをまとめると
「var q=(document.all)?document.selection.createRange().text:(window.getSelection()+'');」文字列を選択していない時
「if(!q){void(q=prompt('keyword:',''))}; 」としておくと、文字列を選択していない時は入力を促すことが出来て親切。おまけ
URLを移動させる場合、window.location.hrefにURLを入れる人が多いけれど、これもChromeで動かない。location.hrefなら共通で動く。 動作確認環境は以下の通り。- InternetExplorer7.0.5730.13
- FireFox3.08
- GoogleChrome1.0.154.53
- Safari3.2.2
- Opera/9.60
Googleで検索するときは .com と .co.jp を使い分けよう。 - 日々、とんは語る。
僕はいつごろからか、Google で調べるときは、.co.jp と .com を使い分ける様になったのですが、これが非常に便利です。google.comって、google.co.jpにリダイレクトされません?
.com で検索すると、メインが英語圏になり、英語圏の情報が上位にやってくるようになります。 .co.jp だと、メインが日本語な上、日本語のページを検索というオプションしかなくて、英語圏だけを調べるというのが不可能なのです。
http://www.google.com
iGoogleを使っているせいなのかなぁ?
で、普通に検索するフォームを用意してみました。
英語優先で検索
日本語優先で検索
フォームの需要があるのかどうか知りたいので公開してみます。
special thanks:
» googleは英語を知らない - 葉っぱ日記
追記:Google英語検索ブックマークレットもつくりました。毎回ここに検索しに来なくていいのでこっちのほうが便利かも。
sqlite2の時は、アクセスが競合してBusyになった時のリトライ処理が見あたらなかったので、自前でこんなことをしていました。
10.times_retry(DBI::DatabaseError,1) {
@dbh = DBI.connect(@@db_prefix+@db_filename, "", "" ) unless @dbh
}
(DBに接続を試みて、DBI::DatabaseErrorが発生したら、1秒sleepしたあとリトライ、10回試みて、それでも駄目だったらDBI::DatabaseErrorをraiseします。)
Rubyらしいエレガントなコード♪とか思っていたのですが、プログラムのあちこちにこれが散見される状態というのもなんだか悲しい。
sqlite-rubyでは、busy_handlerという機能が実装され、こういう事はライブラリ内でやってくれるようになりました。ただ、これのサンプルが見あたらない。
あれこれ試行錯誤していちおう動くようになったのですが、なんだか謎の部分も残っているので、ご存じの方、教えていただけると嬉しいです。
片方のシェルで、sqlite3を走らせて、ロック状態にしておきます。
(sqlite2の頃は、BEGIN;した時点でロックされていたのですが、今のバージョンでは、実際にロックが必要になった時点でロックするみたいです)sqlite> BEGIN; sqlite> insert into fragments (user_id,content,lastmod,bornin) values(0,"test","2009-04-01 12:59:15","2009-04-01 12:59:15");
もう片方でirbを走らせて、こんな感じでロックに激突させます。
require 'rubygems'
require 'sqlite3'
dbh = SQLite3::Database.new("test.sqlite3")
#dbh.busy_timeout(10000)
dbh.busy_handler{|data, retries|
print "in busy_handler data is ",data,"\n"
print "retries is ",retries,"\n"
sleep 1;
(retries<=3)
}
dbh.execute("insert into fragments (user_id,content,lastmod,bornin) values(0,'test','2009-04-01 12:59:15','2009-04-01 12:59:15');")
それいけっ!
irb(main):013:0> dbh.execute("insert into fragments (user_id,content,lastmod,bornin) values(0,'test','2009-04-01 12:59:15','2009-04-01 12:59:15');")
in busy_handler data is nil
retries is 0
in busy_handler data is nil
retries is 1
in busy_handler data is nil
retries is 2
in busy_handler data is nil
retries is 3
/usr/lib/ruby/gems/1.8/gems/sqlite3-ruby-1.2.1/lib/sqlite3/errors.rb:94:in `check': database is locked (SQLite3::BusyException)
from /usr/lib/ruby/gems/1.8/gems/sqlite3-ruby-1.2.1/lib/sqlite3/resultset.rb:76:in `check'
:
よしよし。まとめ:busy_handlerの使い方
- DBに接続したあたりで、
dbh.busy_handler{|data, retries| # BUSYだった時の処理 }という具合にして宣言する。このコードはすぐ実行されるわけではなく、DBにアクセスしてBUSYだった時に初めて実行される。
- retriesに試行回数が入っている。
- (謎)busy_timeoutの挙動が謎。APIリファレンスを見ると、busy_timeout(1000)ってしたら、一秒おきにリトライしそうな物ですが、実際には丸無視しているように見えます
- しょうがないので、busy_handler内でsleepします
- (謎)dataって何でしょうね?
- busy_handlerのブロックの評価結果がtrueであれば、リトライが実施されます。falseであれば、例外が投げられます。
最後の項目、return false って書くわけじゃなくて、ブロックの評価結果が使われるところがポイントかと。
上のサンプルでは、3回まではリトライして、それで駄目なら例外を投げるようなコードになっています。
