Apache(mod_proxy_balancer) + mongrelで運用するときの注意点
Railsをproduction環境で運用していると、こんなエラーが頻発していることに気が付いた。
Processing ApplicationController#index (for ::1 at 2007-08-07 15:51:09) [GET] Session ID: 006a27e09f46dfe1bd6f6cc2258cfa8e Parameters: {} ActionController::RoutingError (no route found to match "/" with {:method=>:get} ): /vendor/rails/actionpack/lib/action_controller/routing.rb:1292:in `recognize _path' /vendor/rails/actionpack/lib/action_controller/routing.rb:1282:in `recognize ' /vendor/rails/railties/lib/dispatcher.rb:40:in `dispatch' /usr/local/lib/ruby/gems/1.8/gems/mongrel-1.0.1/lib/mongrel/rails.rb:78:in ` #=> 長いので省略
今のシステムではmongrelに対してルートにアクセスする事はない。原因が分からなくてかなり困る。
Processing ApplicationController#index (for ::1 at 2007-08-07 15:51:09) [GET]
forの後ろにある「::1」と言う値には本来ホスト名やIPが入るはずなのだが…。謎なんだけど、記号だからググる事も出来ない。
とりあえず仮受け用にroutes.rbを編集してrequest.envを出力するアクションを作って結果を見ると、UserAgentにこんな値が。
"HTTP_USER_AGENT"=>"Apache (internal dummy connection)
…Apacheから?
今度はググれそうなのでググって見ると原因を解説しているサイトを発見。
When the Apache HTTP Server manages its child processes, it needs a way to wake up processes that are listening for new connections.
http://wiki.apache.org/httpd/InternalDummyConnection
mod_proxyなどで子供のサーバをぶら下げていると、Apacheは死活監視用のHTTPリクエストを送るらしい。
Railsはデフォルトのままでルートにアクセスするとdispacherがpublic/index.htmlに読み替えてWelcome Abord画面を表示するんだけど、これが不要だったからpublic/index.htmlを削除していた。これが直接的な原因っぽい。
空ファイルでも良いのでindex.htmlを置いておけばエラーは出なくなるはず、という事でやってみた結果、エラーは出なくなった。
という事で、Apacheでmod_proxy_balancerを使用しているときはproxy先で死活監視のHTTPアクセスを忘れないこと。
とりあえず空のindex.htmlを置いておくのが一番良いと思う。
ちなみに::1と言うのはループバック用のアドレスらしい。
死活監視用のリクエストヘッダは以下のようになっている。
"REMOTE_ADDR"=>"::1" "HTTP_X_FORWARDED_FOR"=>"::1"
でもなんでループバック用のアドレスになってるんだろ…。
MySQLリンク集
こつこつと貯めていたMySQLのリンク集を公開。
SBMでも良いんだけど、ある程度溜まってくるとページングで件数が区切られちゃって不便だし、こうやって並べることも出来ないから本気で情報収集したい場合は結局テキストでまとめちゃう…。
タグでエクスポートとか出来るといいんだけどなぁ、はてブ。
■チューニング(my.cnf中心)
mysql を高速化したいときに読むメモ (TechKnowledge)
http://tech.media-index.jp/2006/11/mysql_1.html
DSAS開発者の部屋:5分でできる、MySQLのメモリ関係のチューニング!
http://dsas.blog.klab.org/archives/50860867.html
DBT-1 による MySQL 5.0 の性能測定: Intel Xeon Dual-Core 編: 考察
http://ossipedia.ipa.go.jp/capacity/EV0604170061/index.php
MySQL AB :: MySQL 5.0 Reference Manual :: 5.2.3 System Variables
http://dev.mysql.com/doc/refman/5.0/en/server-system-variables.html
MySQLシステム変数
http://www.limy.org/program/db/mysql/mysql_variables.html
MySQLの高度な管理とチューニングテクニック(1/2)
http://www.atmarkit.co.jp/flinux/rensai/mysql11/mysql11a.html
■クエリの最適化
MySQLのクエリを最適化する10のTips - PHPプロ!ニュース
http://www.phppro.jp/news/362
CodeZine:「ちょっと待て」 真・MySQLのクエリを最適化する10のTips(Tips)
http://codezine.jp/a/article/aid/1229.aspx
KLab技術者ブログ:その7 MySQLスピードアップのコツ?
http://tech.blog.klab.org/archives/50277350.html
MySQL AB :: MySQL 4.1 リファレンスマニュアル :: 6.9 MySQL クエリキャッシュ
http://dev.mysql.com/doc/refman/4.1/ja/query-cache.html
■インデックス
MySQL:インデックスまとめメモ
http://www.res-system.com/item/550
KLab技術者ブログ:その6 インデックスもこんなにあった
http://tech.blog.klab.org/archives/50253823.html
Yet Another Hackadelic - 効率的なインデックスの生成と管理について
http://d.hatena.ne.jp/ZIGOROu/20061005/1160070517
MySQL - 複合インデックスのすすめ - Seesaa開発日記
http://dev.seesaa.net/article/238633.html
ウノウラボ Unoh Labs: MySQL5からのインデックス結合で1テーブル複数インデックスを使う
http://labs.unoh.net/2007/06/mysql5.html
MySQL AB :: MySQL 5.1 リファレンスマニュアル :: 6.4.5 MySQLにおけるインデックスの使用
http://www.mysqlpress.com/doc/refman/5.1/ja/mysql-indexes.html
※以下、実業務で初めてMySQLを使った自分の所見。
MySQLと言うDBMSは結局インデックスが使われれば速い、使われなきゃ遅いという印象。勿論my.cnfのチューニングも大事だけど。「ちゃんと設計すれば早い」と言う一般的な認識はその辺から来ているのだと思った。
それと、複雑だけど軽負荷なシステムよりはシンプルな設計で高負荷なシステムの方が向いているとも感じた。それはインデックスが一テーブルにつき一つ、と言うことに起因している。システムが複雑であればあるほどこの制限は響いてくる。
例えばWhereで絞った結果をORDER BYした場合にインデックスが使われずに遅くなるって言うのはMySQLでハマりやすいポイントで、その場合の対策は複合インデックスを使うことなのだけれど、WHEREで使っているカラムとORDER BYで使っているカラムのテーブルが異なる場合にはORDER BYでインデックスの使うのは基本的に無理(だと思う)。
まぁ結局MySQL以外もよく知らないから比較できないんだけど、他だとどうなんだろう。PostgreSQLとか。
■負荷分散
現場指向のレプリケーション詳説
http://www.irori.org/doc/mysql-rep.html
Life on the net - MySQL負荷分散のまとめ
http://d.hatena.ne.jp/manamanmana/20060308/1141780324
1人で稼ぐ日記 | MySQL:1台しかない環境でエセ負荷分散
http://kokoromo.jugem.cc/?eid=195
KLab技術者ブログ:その9 レプリケーションで負荷分散と可用性
http://tech.blog.klab.org/archives/50340425.html
KLab技術者ブログ:その10 レプリケーションいろいろ
http://tech.blog.klab.org/archives/50357090.html
KLab技術者ブログ:その11 めざせ高可用性と負荷分散
http://tech.blog.klab.org/archives/50373849.html
■テーブルタイプ
naoyaのはてなダイアリー - MyISAM vs InnoDB
http://d.hatena.ne.jp/naoya/20060729/1154139996
InnoDB vs MyISAM パフォーマンス比較 PrimaryKEY、UniqueIndex、非UniqueIndex || パフォーマンスチューニングBlog: インターオフィス
http://www.inter-office.co.jp/contents/120
InnoDB vs MyISAM パフォーマンス比較 SELECT ・・・ LIMIT N || パフォーマンスチューニングBlog: インターオフィス
http://www.inter-office.co.jp/contents/147
InnoDB vs MyISAM パフォーマンス比較 Left Join || パフォーマンスチューニングBlog: インターオフィス
http://www.inter-office.co.jp/contents/157
innodb_thread_concurrencyとか 計測してみました || パフォーマンスチューニングBlog: インターオフィス
http://www.inter-office.co.jp/contents/122
■コマンドリファレンス
MySQLクイック・リファレンス
http://www.bitscope.co.jp/tep/MySQL/quickMySQL.html
■バックアップ
KLab技術者ブログ:その12 忘れちゃいけないバックアップ
http://tech.blog.klab.org/archives/50388696.html
MySQLの高度な管理とチューニングテクニック(2/2)
http://www.atmarkit.co.jp/flinux/rensai/mysql11/mysql11b.html
■セキュリティ
KLab技術者ブログ:その13 敵からデータを守るには?
http://tech.blog.klab.org/archives/50408495.html
■障害復旧
※特に見つからなかった。
中〜大規模環境におけるマスタ障害時の対応、スレーブ障害時の対応の具体的な事例があれば有難いのだが…。
■文字コード
文字化け問題を本気で直す ヽ( ・∀・)ノくまくまー(2006-10-11)
http://wota.jp/ac/?date=20061011#p01
■TIPS
MySQLノウハウ
http://txqz.net/blog/2006/12/13/0943
MySQLの小技
http://tomo.ac/goodstream/database/mysql/linux/tips.htm
一日目午後:MySQLの最適化 - Oliver の日記
http://slashdot.jp/journal.pl?op=display&uid=4&id=26710
※かなり古い情報なので注意。(2001年 = MySQL3系列の頃?)
MyNA Web Site
http://www.mysql.gr.jp/frame/modules/bwiki/?FAQ
■企業サイト
MySQL Server 5.0 設定手順書 Red Hat Enterprise Linux 4
http://h50146.www5.hp.com/products/software/oe/linux/summary/reference/pdfs/mysql-50-config-rhel4-v10.pdf
日本HP Linux リファレンスアーキテクチャ
http://h50146.www5.hp.com/products/software/oe/linux/summary/reference/
※素晴らしいドキュメント郡。
こんな良い物がひっそりと公開されているのは勿体無い…。
■大規模サイトの事例
◆はてブ
[ThinkIT] 第6回:データベースの負荷分散とまとめ (1/3)
http://www.thinkit.co.jp/free/article/0610/1/6/
◆Mixi
BKCon 2006 - にぽたん研究所
http://blog.livedoor.jp/nipotan/archives/50538571.html
◆DeNA
CodeZine:DeNAの人気サイトに学ぶ LAMPによるWeb-DBシステム構築/運用の極意(前編)(モバオク, モバゲー)
http://codezine.jp/a/article/aid/1585.aspx
■書籍(個人的なオススメ)
◆実践ハイパフォーマンスMySQL
http://www.amazon.co.jp/dp/4873112095
実践ハイパフォーマンス MySQL : NDO::Weblog
http://naoya.dyndns.org/~naoya/mt/archives/001406.html
◆MySQL全機能リファレンス
http://www.amazon.co.jp/dp/477412169X
◆現場で使える MySQL (DB Magazine SELECTION)
http://www.amazon.co.jp/dp/4798111139/
■比較
スラッシュドット ジャパン | MySQLを使う5つの理由、使わない8つの理由
http://slashdot.jp/developers/article.pl?sid=07/05/26/1222259
セッタが未定義もしくはprivateなインスタンス変数の内容をクラスの外部から取得する
追記
エントリ本文の内容は間違い。
instance_variable_getメソッドの引数にはシンボルだが、変数名のみではなく@を含む必要があった。
puts c.instance_variable_get(:write_only) rescue puts $!.message #=>`instance_variable_get': `write_only' is not allowed as an instance variable name (NameError) puts c.instance_variable_get(:@write_only) rescue puts $!.message #=>"write only instance variable"
公式ドキュメントにもそう書いてある。ちゃんと読もう…。
エントリ内容は既に意味を失っているが、一応残しておく。
以下エントリ本文
Rubyではセッタが定義されていないインスタンス変数にはinstance_variable_getメソッドでも取得することが出来ない。
class C public attr_writer :write_only private attr_accessor :priv_variable end c = C.new c.write_only = "write only instance variable" puts c.write_only rescue puts $!.message #=>undefined method `write_only' for #<C:0x2bb9fac> (NoMethodError) puts c.instance_variable_get(:write_only) rescue puts $!.message #=>`instance_variable_get': `write_only' is not allowed as an instance variable name (NameError) puts c.instance_variable_get(:priv_variable) rescue puts $!.message #=>`priv_variable' is not allowed as an instance variable name
上記のように、アクセスを許可されていないと言う旨の例外がスローされる。
Rubyとしては上記のような変数に対しては外部から軽々しく操作する事は推奨していないと言うことだろう。
それでもアクセスする方法は存在する。instance_evalメソッドを使えばよい。
puts c.instance_eval("@write_only") #=>write only instance variable c.instance_eval("@priv_variable = 'hogehoge'") puts c.instance_eval("@priv_variable") #=>hogehoge
instance_evalはレシーバのコンテキストでevalするからアクセス可能なのだと思う。
詳細はよく分からないから推測になってしまうが、クラス外部のコンテキストだった場合にはinstance_variable_getはただ単にセッタを呼び出しているだけなのだろう。
セッタが定義されていてもprivate変数はクラス外部のコンテキストからはアクセスできないので拒否されてしまうのだと思う。
検証用としてprivate_instance_variable_getメソッドを追加してみた。
#※上記ソースの続き class C def private_instance_variable_get(name) instance_variable_get("@#{name.to_s}") end end puts c.private_instance_variable_get(:write_only) #=>write only instance variable c.instance_eval("@priv_variable = 'fugafuga'") puts c.private_instance_variable_get(:priv_variable) #=>fugafuga
ちゃんと動いた。
例えばこのメソッドをObjectクラスにでも追加すればinstance_evalしなくてもprivateなメソッドにアクセスすることが出来る。
正規表現オブジェクトの作り方と注意点
- /pattern/option
- %r{pattern}option
- Regexp.new('pattern', option)
一番利用頻度が高いのが/pattern/で、この場合patternには式展開を含める事ができる。
/hoge#{"foo"}/ # => /hogefoo/
ただし、optionには式展開を含める事ができない(正確にはできるが、無視される)。これはコンパイルエラーにもならないので注意が必要。
/hoge#{"foo"}/#{"ni"} # => /hogefoo/ #式展開でくっつけたoption値が無視されている。
%r記法でも同様である。
その為、optionを動的にしたい場合はRegexp.newメソッドを使用する。
…と思っていたのだが、そうでもないらしい。
Regexp.new("hoge", "i") # => /hoge/i Regexp.new("hoge", "o") # => /hoge/i
どんな値を入れても何故かignore caseがオプションになってしまう。
Regexpのrdocを見て謎が解明。
第二引数が Fixnum であった場合、その値は
Regexp::IGNORECASE
Regexp::MULTILINE
Regexp::EXTENDED
の論理和でなければなりません。第二引数が Fixnum 以外であれば真偽値の指定として見なされ、真 (nil, false 以外)であれば Regexp::IGNORECASE の指定と同じになります。
http://www.ruby-lang.org/ja/man/?cmd=view;name=Regexp
わざわざ論理和とか計算したり長い定数名を書くらいならcaseで分けた方が早いと思う…。
そもそもoptionに式展開を含めたいと考えたのは文字コードを動的に変えたいからだった。
引き続きRegexpのrdocを読んで見ると以下のような記述を発見。
第三引数が与えられた時には、$KCODE の値にかかわらず、指定された文字コードでマッチを行います。文字コードは $KCODE への代入と同様に文字列引数の最初の一文字で決定されます。
http://www.ruby-lang.org/ja/man/?cmd=view;name=Regexp
と言うわけで以下のように使うと今回の目的は達成できる。
charset = "s" Regexp.new("hoge", nil, charset) # => /hoge/s charset = "u" Regexp.new("hoge", Regexp::MULTILINE , charset) # => /hoge/mu
AR#==
ActiveRecordの==はどう動くのか気になったので調べてみた。
例えばSNSで日記にユーザからコメントがついた場合、新着に表示する時にはまず日記を書いたユーザとコメントを書いたユーザが別かどうかを判定しなきゃいけない。
早速ソースを読む。
def ==(comparison_object) comparison_object.equal?(self) || (comparison_object.instance_of?(self.class) && comparison_object.id == id && !comparison_object.new_record?) end
つまり、
- オブジェクトIDが同じ場合はtrue
- idが一緒ならtrue、ただし新規レコードはfalse
と言う事。
例
#IDが一緒ならTrue user1 = User.find(1) user2 = User.find(1) user1 == user2 #=> true #IDが違えばfalse user3 = User.find(2) user1 == user3 #=> false #オブジェクトIDが一緒ならtrue user4 = user1 user4 == user1 #=> true user4.id = 3 user4 == user1 #=> true user1.id #=> 3 #新規レコードならIDが一緒でもfalse user_new1 = User.new user1 == user_new1 #=> false user_new1.id = 1 user1 == user_new1 #=> false #新規レコード同士でIDが一緒でもfalse user_new2 = User.new user_new2.id = 1 user_new1 == user_new2 #=>false
たぶん問題は出ないと思う。
よく考えられてるなぁ。
HTTP POST ≒ request.post?
例えば以下のようにlink_toメソッドを呼び出してリンクを作成すると、HTTP的にはPOST扱いとなる。
link_to("削除", {}, :confirm => "削除する?", :delete)
HTTPリクエストは以下のように発行される。
POST / HTTP/1.1 (中略) _method=delete
で、このリクエストをコントローラで判断してみる。
request.post? #=> false request.delete? #=> true
request.post?がfalseになるのはかなり違和感がある。
requestはHTTP Requestを表現するオブジェクトなのだから個人的にはrequest.post?はHTTP POSTだったら常にtrueであるべきとは思うんだけどな。*1
まぁたぶんRESTとかActiveResourceとかに関連してくる話なのだと思う。
DHHがRailsConf2006で語っていた気がするけどまだ詳細は押さえていなかったり。
*1:method.post? method.delete?とかだったらいいんだけど…。