プレースホルダを遅延評価する

最近便利に使っているのがプレースホルダ(%s)を遅延評価させること。
要は動的な定数(?)を定義する事が出来る。
ソースを見た方が早いと思うのでさくっと。

SEARCH_URL = 'http://www.google.co.jp/search?q=%s'
%w(ruby perl php).each{|word|
  puts SEARCH_URL % word
}


Railsのどこかで使われていた手法だったんだけど、どこか忘れた…。

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
CodeZineDeNAの人気サイトに学ぶ 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なメソッドにアクセスすることが出来る。

正規表現オブジェクトの作り方と注意点

Ruby正規表現オブジェクトを作る方法は以下の三通り。

  1. /pattern/option
  2. %r{pattern}option
  3. 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?とかだったらいいんだけど…。