odeの開発メモ日記

プログラマーやってます。今までの道のり ポケコンbasic→C(DirectX5 ネットやろうぜ)→Perl(ちょろっと)→Java→C#→Ruby→Android(Java)

mysqlでのdbのマスタースレーブに対応するのは厳しいねぇ。

先にいうと、あることはあるけど
メンテされてなくて最新のrailsでは動きませんでし。
そのうち使えるようになるだろうと信じてのメモ書きです。
急いで使うなら自分で作るか、改良する必要になります。


magic multi connections

findする際にモデルの頭に接続名を指定する形。

Master::User.find(:first)
Slave1::User.find(:first)
Slave2::User.find(:first)

複数のDBコネクションを自由に選択できる柔軟性があるが、単純なマスタースレーブ構成にするにはスレーブをまとめるための一工夫が必要になる。

メジャーなのがこれっぽいが、rails2.1で動かなかった。。
ので使えない。

http://magicmodels.rubyforge.org/magic_multi_connections/



mysql replication adapter

findの引数にスレーブ使うよとオプションを渡すスタイル

マスタースレーブ構成に一番適したスタイルだと思う。

User.find(:first, :use_slave => true)

rails2系では使えないっぽいがバグトラッカーに
rails2系で使えるようにしたパッチがありました。
http://rubyforge.org/tracker/index.php?func=detail&aid=20068&group_id=4116&atid=15778

一応動作したのですが、本番環境で1日おきに例外が。。

ActiveRecord::ConnectionAdapters::CannotWriteToSlave (You attempted to perform a write operation inside a slave-balanced read block.):
[FATAL]     /vendor/plugins/mysql_replication_adapter/lib/active_record/connection_adapters/mysql_replication_adapter.rb:91:in `ensure_master'

恐らくdbが切断されたときの再接続で失敗する時があるくさい。
開発環境でdb接続を強制切断して何回か試してたまに再現できました。

というわけで使えない。



acts_as_readonlyable

更新系はマスターに
読み取り系はスレーブにしてくれるやつ。

けどスレーブはデータ反映まで遅延するから
データを更新したあとに確認させるのに使えない。。

日本最大規模のrailsを使ったサイトのクックパッドでは
このライブラリを改良して使ってるとのこと。

thinでswfを扱う場合

前書き

publicフォルダにswfをおいた場合に
thinだとcontent-typeがtextになってしまうようです。
これだと携帯でのflash表示が失敗するので(mimeが違うとau,softbankは見れないっぽい。docomoは無視して表示するみたいだが)
そのためflashmimeをきちんと返すようにする必要があります。
(ちなみにmongrelはswf対応してました)

やり方

thinはRackというWebサーバーを作るフレームワークを使用していて
そこに静的ファイルの扱いをまかせてるっぽいです。
RackのクラスにMime定義のHashがあったので、
そこにswfのmime定義を追加することで解決できました。
(Rackは勉強してないんで、もっといいやり方があるかもしれません。。)


environment.rb

#mongrelだとエラーでたのでif文きりわけ
if defined? Rack::File::MIME_TYPES
  Rack::File::MIME_TYPES["swf"] = "application/x-shockwave-flash"
  Rack::Directory::MIME_TYPES["swf"] = "application/x-shockwave-flash"
end

セッションの自動延長

前書き

セッションはご存知のとおりリクエストがないと一定時間で消えてしまいます。
けどページによってはタイムアウトの時間をとても長くしたい場合があります。


例えばお絵かきツールで大作を描いたり。。メール文面入力ページで緻密に計算されたラブレターを書いていたり。。などなど。(消えちゃったら発狂しますね)


なのでそのページだけはタイムアウトをなくしたいです。


やり方

ajaxで定期的に裏でリクエストを発行すれOKです。
リクエストのたびにセッションの有効期限が延長されます。

util_controller.rb
class UtilController < ApplicationController
  def extend_session_expire
    # 特になにもしない。
    render :text=>"ok"
  end
end


自動延長したいview

<%--  javaスクのライブラリロード --%>
<%= javascript_include_tag :defaults %>


<%--  特定のurlを定期的にたたくコード。この場合10分間隔。 --%>
<%= periodically_call_remote :url=>{:controller=>"/util", :action=>"extend_session_expire"}, :frequency => 10.minute %>

cache_fuのconfについて。memcached.yml

(ちなみに全部の説明はありません。適当にピックアップしました。)

#trueにするとsessionの格納先がmemcacheになります。
sessions: truefalse

#セッションのmemcacheサーバーを別にしたい場合は指定する。
#一緒でいい場合はfalseを指定(デフォルト)
session_servers: 192.168.xxx.xxx:11211

#フラグメントキャッシュの格納先をmemcacheに切り替える。
fragments: truefalse

#trueにすると早くなるっぽい。
#その代わりruby以外のクライアントとの互換性がなくなるっぽい。
#hashの実装を変更して実現してるみたい。
fast_hash: falsefalse

#trueにするとfast_hashするより早くなるっぽい。
#その代わりfast_hashより互換性の条件が悪くなり、
#同じruby同士でもos等のアーキテクチャが違うってもだめっぽい。
#hashの実装を単純なrubyのhashにしているみたい。
#ってことは恐らくアーキテクチャによってhashの実装が違うんでしょう。
#他アプリと連携しないで複数サーバーも同じOSを使うならこれはonにして
#よいんじゃないでしょうか。
fastest_hash: truefalse

sessionsをtrueにした場合は下記を適用しましょう。

  • application.rbのprotect_from_forgeryの:secretのコメントを外す
  • deveropment.rbで有効期限を設定
    • ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS.merge!-({'expires' => 30.minute })


ちなみにデフォルトで用意されてるmemcached.ymlは
:defaultsとdevelopment:の両方でsessionとfragmentsをfalseにしてあるので
:defaultsだけtrueにしても反映されません。(動かねーじゃんと一度やっちゃいました)

セッションの格納先をmemcacheにする。

やり方

memcache-clientのgemをインストール(追記あり)

gem install memcache-client

追記(2008/11/20)

rails2.1からはmemcache-clientが同梱されていました。(rails2.0にはなかったです)
なのでgemのインストールは不要になりました。

activesupportの中にありますソース見た感じ一緒でした(ちょびっとリファクタしてるっぽかったですが)。
ruby_lib\gems\1.8\gems\activesupport-2.1.2\lib\active_support\vendor\memcache-client-1.5.0


enviroment.rb

(省略)
Rails::Initializer.run do |config|
(省略)
  config.action_controller.session = {
    :session_key => '_アプリ名_session',
    :secret      => 'セキュアな文字列(ランダムな文字列等)'
  }
  config.action_controller.session_store = :mem_cache_store
(省略)
end

memcache_options = {
  :namespace => "アプリ名-session-#{ENV['RAILS_ENV']}",
  :readonly => false
}
SESSION_CACHE = MemCache.new([ APP_CONF_SESSION_MEMCACHE_HOST ], memcache_options)
ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS.merge!({ 'cache' => SESSION_CACHE, 'expires' => 30.minute })

memcache_optionsに渡せるoptionは3つらしい(ソース見た感じ)
:namespace、:readonly、:multithread
(rails1.2 までは別のライブラリRuby-MemCacheというのを使っていたので引数が少なくなってるらしい
昔は:compression、:debug、:urlencodeがあった。


CookieStoreから変更したので
application.rbのprotect_from_forgeryの:secretのコメントを外す。

class ApplicationController < ActionController::Base
  helper :all # include all helpers, all the time

  # See ActionController::RequestForgeryProtection for details
  # Uncomment the :secret if you're not using the cookie session store
  protect_from_forgery :secret => 'セキュアな文字列(ランダムな文字列等)'


以上で完了


いろいろググって調べたつもりだけど
大抵下記のオフィシャル情報をみんな見てやってるらしい。
http://wiki.rubyonrails.org/rails/pages/HowtoChangeSessionStore

けどこれだとtimeoutのexpiresが指定されていなく、デフォルトの0秒が使われてしまう。
なんか1日たってもログアウトしないなーと思ってたら永遠の設定らしい。。(よくあるデフォルト値が30分とかではなかった。。)
ググってもわからなかったのでソースを見たら
DEFAULT_SESSION_OPTIONSにexpiresという引数があったので、それを指定したらうまくいった!

expiresのために参照したソース
\ruby_lib\gems\1.8\gems\actionpack-2.1.2\lib\action_controller\session\mem_cache_store.rb


実行環境

rails(2.1.2)
memcache-client(1.5.0)


追記(2008/11/20)

cache_fuというmemcacheのライブラリでsessionをmemcacheにする設定がありました。こっちのほうがいいかな。
設定方法

acts_as_paranoidで関連を扱う場合

acts_as_paranoidとはモデルを論理削除できるようにするrailsプラグインです。


簡単な使用では問題ないのですが
関連を使った場合には消したデータを見てしまう可能性がありました。

やりたいこと

例として学校クラスと生徒クラスがあったとして
学校を削除できたとする。(過疎化、少子化のせいですかねぇー)


この場合に生徒一覧を取得する、但し削除した学校の生徒は出さない
といったことをする

クラス

class School < ActiveRecord::Base
  acts_as_paranoid
  has_many :students
end

class Student < ActiveRecord::Base
  belongs_to :school
end

方法と結果

1番
School.students.find(:all)
OK.問題ない


2番
Student.find(:all, :include=>:school, :condition=>"school.created_at > '2001/1/1'")
NG.削除した学校の生徒も取得してしまう


3番
Student.find(:all, :include=>:school)
OK.これは大丈夫

説明

このプラグインはacts_as_paranoidと定義してあるモデルからfindした場合にしか
発動しないらしい。(なんちゃってソース見で)
そのため2番目のがNGになる。
(Studentにはacts_as_paranoidを定義していないせい)


では、3番目のがOKになるのは何故か?
それは恐らくrails2.1だからだろう。
rails2.1から単純な結合はjoinを使った1回のsqlではなく
各モデルに対する2回のsqlに分けるようになったためだ。
動作としてStudentを取得するsqlを発行後に、Schoolを取得するsqlを発行する。
ということでShoolに対してfindしてるのでacts_as_paranoidの対象となる。


じゃあ、何故2番目の例がNGになるかというと
conditionに結合先の条件が入っているためだ。この場合はrails2.0以前のものと動作が同じになり
joinする1回のsqlになる。

2番目の場合の回避策としてはconditionに明示的にdeleted_at is nullとのチェック処理を入れることで
対応できる。(まぁ、あたりまえですねぇ。。自動で関連先も見る機能がほしいけどやっぱ複雑なんだろうなぁ。。)

実行環境

rails2.1.1

HPの激安サーバー&freenasで4TBのnasを安価に作る

家のIOデータの1.6T NASに容量と速度の面で限界を感じたので
作ってみました。


4TB(テラバイト)の高速NASが約6万円で。

使ったもの

  • HDD
    • 1TBのを4つ(WDの省エネなやつ)
    • 11000円*4=44000円
  • USBメモリ
    • FreeNasのインストール先に使用。USBメモリからブートできる。
    • 1G 1000円 実際にはもっと容量少ないのでOKです。確か256Mとかでもいけたはず。


raid5を使いました。なので実質は3TB。安全性のため多少は仕方がない。

速度のほうは

今回作ったNAS

Sequential Read : 51.108 MB/s
Sequential Write : 62.798 MB/s
Random Read 512KB : 51.409 MB/s
Random Write 512KB : 31.290 MB/s
Random Read 4KB : 9.969 MB/s
Random Write 4KB : 1.672 MB/s

Test Size : 100 MB

今までのIOのNAS(遅っ)

Sequential Read : 9.048 MB/s
Sequential Write : 12.973 MB/s
Random Read 512KB : 9.533 MB/s
Random Write 512KB : 17.320 MB/s
Random Read 4KB : 2.631 MB/s
Random Write 4KB : 2.767 MB/s

Test Size : 100 MB


CrystalDiskMarkにて測定
http://crystalmark.info/software/CrystalDiskMark/



体感的速度的にも写真を見る速度があがったり、動画閲覧の速度が上がったり
速度アップのメリットを実感できました。

速度計るときの注意点

今回vistaで速度を計ったんですが、最初は10MB/s程度しかでなくてショック受けたんですが
vistaの落とし穴でした。
なんと音楽再生中は(winamp立ち上げてたんですが)ネットワークの速度が制限されるとのこと。
なので100MLANなら気がつかないですが、今日のギガLANの時代だと強制的に100MLANと同じ速度になるみたいです。

治し方

レジストリ
HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Multimedia\SystemProfile
に、NetworkThrottlingIndexという名前のDWORD値を0xffffffffにする。

参考元
http://blogs.yahoo.co.jp/chototsu_moushinp/32348055.html
http://windowsvista.ms/index.php?FAQ#cde892fa


消費電力

今回作ったNAS
  • 通常 85w
    • 1か月1346円
  • HDDスタンバイ状態にした場合 74w
    • 1か月1172円
今までのIOのNAS
  • 通常 64W


やっぱりちょいと負けるけど、まー問題ないでしょう。

まとめ

今までの売って4万になるみたいなので
追加2万でこんなにパワーアップするとは満足でした。
作ってよかったー!!