ActiveRecordのestablish_connectionに気をつけろ

Rails3.1.3の話。

別DBにつなぐときの、ActiveRecordのestablish_connectionに気をつけろ。やつは1クラスごとに独自にdatabase.ymlのpool設定分のコネクションプールを作るぞッ。プールの意味ない!!

マスターDB1台で運用しているときは全然問題ない。コネクションプールは大変賢く働いてくれる。だが、2台目を運用し始めたとき、安易にestablish_connectionでつなぐと簡単に too many connections で、落ちる。

たとえば、ワーカー10個で、establish_connectionで別DB「hoge_db」に接続することにしたクラスが10個あったとしよう。その別DBへの接続設定(database.yml)で 「pool: 5」 としたとしよう。

そしてワーカーを立ち上げたとき、hoge_dbへのコネクション数はみるみるふくれあがり、ワーカー数(10)×establish_connection利用のクラス数(10)×pool設定数(5)で、実に500 100個のコネクションを張るぞ!

対応策としては、下記の4とおりが考えられる。

A、別DBのdatabase.yml設定はpoolを1に設定する。
(急場しのぎ。応急処置レベル。ちなみに0にはできないらしい)
B、コネクションプールをやめる
【参考】http://d.hatena.ne.jp/hxmasaki/20100812/1281588958
C、別DBへのコネクションプールを引き受けるクラスを作って継承する。
【参考】http://d.hatena.ne.jp/rudeboyjet/20101221/p1
D、適宜Rails.configuration.database_configuration の設定を変えて運用してみる。
【参考】http://yaplog.jp/gmodev/archive/71

いま、急場しのぎでAの対応をしている。根本解決として有望なのは、CかDかなぁと思う。どちらにしてもよく検証しないとなぁ。

Bは、ケースによってはアリだけど、僕のケースでは採用できないかな、と。

どっかに落ちてないのかな、解決済みのアイテムが。。。

【追記:2012/4/30】
Dの解決方法は、結局 establish_connection してるので、今回の地獄を解決してくれない。ので、Cの方法がたった一つの冴えたやり方っぽい。
【追記:2012/5/29】
コネクションはワーカー内でスレッドとか使って並列DBアクセスとかしない限りpool値分の接続を作ったりとかはしない模様。

Linuxでバッチのログを書き出し中のファイルを消して死ぬかと思ったが助かった話

諸事情で結構でかいデータの処理をすることになった。
かかる時間とかをログに出して計測しようと思って、標準出力をリダイレクトさせて放置してたんですわ。

んで、まあ、うっかり消しちゃった。
絶賛書き出し中の、そのファイルを。

一瞬死ぬかと思ったんだけど、ちょっと前になにかで、「プロセスがつかんでいるファイルはプロセスが生きてる間は実際には消えない」、って話を思い出したんです。
で、ググったら、あった。

削除したファイルをlsofで復元する
http://www.itmedia.co.jp/enterprise/articles/0611/30/news007.html

方法はすごい簡単で、

$ /usr/sbin/lsof | grep hoge

これでまずファイルの実体を探して cp でコピーすればすくい上げ完了。

$ cp /proc/プロセス番号/fd/ファイル番号 hoge.rescue

僕のケースでは、まだバッチがログを書き出し続けているので、これを tail -f で継続抽出することにした。
なので、こんな感じ。

$ cp /proc/プロセス番号/fd/ファイル番号 hoge.rescue && tail -f /proc/プロセス番号/fd/ファイル番号 >> hoge.rescue &

末尾に「&」をつけてバックグラウンド実行させて、ssh抜けても大丈夫なようにした。
あとはこのファイルをまた間違って消さないようにしないとな!

なんにせよ、助かった。
ありがとう!

mysql2 アダプタなら、IDにBIGINTを使っても大丈夫。

諸事情でRailsActiveRecordで使ってるテーブルのidがbigintだったりして、心臓が止まるかと思いました。
使ってるシステムは「Rails3 + mysql2」で助かった、というお話。

rails bigint」でググるとでてくるんですが、Railsでidにbigintを使うと値が32ビットを超えたときに32ビット整数に丸められてしまうという問題があるわけです。

古いエントリですが、↓このへん。
http://d.hatena.ne.jp/troopergreen/20080409
http://d.hatena.ne.jp/ryu00026/20070320/1174412910

これはRails2時代の gem "mysql" アダプタで接続した場合の挙動です。
Rails3時代の今は、gem "mysql2" アダプタが標準で、これは上記の問題は克服されている模様。


アダプタのソースを見てみると、

"mysql"アダプタ(mysql-2.8.1\ext\mysql_api/mysql.c)

/*	insert_id()	*/
static VALUE insert_id(VALUE obj)
{
    return INT2NUM(mysql_insert_id(GetHandler(obj)));
}

"mysql2"アダプタ(mysql2-0.3.11\ext\mysql2\client.c)

static VALUE rb_mysql_client_last_id(VALUE self) {
  GET_CLIENT(self);
  REQUIRE_OPEN_DB(wrapper);
  return ULL2NUM(mysql_insert_id(wrapper->client));
}

となっていて、「INT2NUM」が「ULL2NUM」に置き換わってます。
(INT2NUMとかULL2NUMとかはrubyのビルド用のマクロみたい)

で、MySQL側の値も my_ulonglong を返してくるので安心。
http://dev.mysql.com/doc/refman/5.1/en/mysql-insert-id.html

nginxでロードバランサー。httpとhttpsの両方で使う

仕事でhttpとhttps両方使う必要がでてきた。

サーバー構成は複数のアプリサーバーをロードバランサーで処理振り分けをする、よくあるタイプ。

問題はhttpsで、複数あるアプリサーバーにそれぞれSSL証明書をインストールすると、手間もかかるがお金もかかるので控えたい。

そこで、サーバー1台をロードバランサーとして仕立て上げ、そのサーバーにだけSSL証明書をインストールし、処理の振り分け先のアプリサーバーはすべてhttpで処理するといいんじゃないか、と考えた。

apacheでも同じこと出来ると思うが、軽量高速と評判
のnginxを使う。

なんと言っても、設定の単純さが際立つ。

nginx.conf 設定例

user              nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log;
pid        /var/run/nginx.pid;

events {
    worker_connections  1024;
}

http {
    upstream tests {
      server 127.0.0.1:8000;
      server 127.0.0.1:8001;
      server 127.0.0.1:8002;
    }

    server {
        listen       80;
        server_name  _;
        location / {
            proxy_pass http://tests;
            index  index.html index.htm;
        }
    }

    server {
        listen       443;
        server_name  _;
        ssl                  on;
        ssl_certificate      /etc/pki/tls/certs/localhost.crt;
        ssl_certificate_key  /etc/pki/tls/private/localhost.key;
        #    ssl_session_timeout  5m;
        ssl_protocols  SSLv2 SSLv3 TLSv1;
        ssl_ciphers  ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;
        ssl_prefer_server_ciphers   on;
        location / {
            proxy_pass http://tests;
            index  index.html index.htm;
        }
    }
}

以上で設定おわり。
あとは振り分け先ががんばればよい。

設定のキモは、upstream セクションの振り分け設定に、server セクション内location ディレクティブで proxy_pass で向けているところ。

SSL用の server セクションも、listenが443なのと、ssl系の設定が追加してあるだけで、location内でporxy_pass を設定しているのは同じ。

超簡単。
特に設定しなくても、upstream内の振り分けメンバーが落ちたらちゃんと振り分け先から外してくれるし、復帰したら振り分け先に戻してくれる。
感動した。

nginx公式サイトの設定例:
http://wiki.nginx.org/NginxLoadBalanceExample

重み付けもできる
http://wiki.nginx.org/NginxHttpUpstreamModule#upstream

      • -

追記
abコマンドでたたいてみたら、http側でのテストではnginxのCPU負荷は5%くらいでとっても余裕、https側のテストではnginxのCPU負荷90%とか行った。
SSLすげえ重いな。

nginx側はDTIのServersMan@VPS、ab側は自宅のノートPCのVMWarePlayerだから、あんまり結果に意味ないと思うけど、abテストの結果をのっけておく。

httpへのabテスト
# ab -n 1000 -c 100 http://*********/
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Server Software:        nginx/0.8.55
Server Hostname:        **************
Server Port:            80

Document Path:          /
Document Length:        5 bytes

Concurrency Level:      100
Time taken for tests:   9.370 seconds
Complete requests:      1000
Failed requests:        0
Write errors:           0
Total transferred:      258000 bytes
HTML transferred:       5000 bytes
Requests per second:    106.72 [#/sec] (mean)
Time per request:       937.032 [ms] (mean)
Time per request:       9.370 [ms] (mean, across all concurrent requests)
Transfer rate:          26.89 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:       11  252 789.6     47    4436
Processing:    21  270 834.5     54    9021
Waiting:       21  263 832.8     52    9021
Total:         36  522 1303.5    102    9042

Percentage of the requests served within a certain time (ms)
  50%    102
  66%    115
  75%    125
  80%    132
  90%   3069
  95%   3161
  98%   6139
  99%   6151
 100%   9042 (longest request)
httpsへのテスト
# ab -n 1000 -c 100 https://*********/
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Server Software:        nginx/0.8.55
Server Hostname:        **************
Server Port:            443
SSL/TLS Protocol:       TLSv1/SSLv3,DHE-RSA-AES256-SHA,1024,256

Document Path:          /
Document Length:        5 bytes

Concurrency Level:      100
Time taken for tests:   25.925 seconds
Complete requests:      1000
Failed requests:        0
Write errors:           0
Total transferred:      258000 bytes
HTML transferred:       5000 bytes
Requests per second:    38.57 [#/sec] (mean)
Time per request:       2592.517 [ms] (mean)
Time per request:       25.925 [ms] (mean, across all concurrent requests)
Transfer rate:          9.72 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:      351 2361 825.5   2400    8492
Processing:    21  113  94.1     95     855
Waiting:       20  108  85.3     93     854
Total:        521 2475 813.1   2499    8592

Percentage of the requests served within a certain time (ms)
  50%   2499
  66%   2642
  75%   2789
  80%   2906
  90%   3115
  95%   3291
  98%   4578
  99%   6458
 100%   8592 (longest request)
            • -

追記その2(2012/2/29)

最近のSSLって同じドメインなら何台にインストールしても追加料金なしなのがあるんだねー。
なら無理にロードバランサ側でSSL仕込まないほうがいいな。

MySQL5.5のcreate temporary tableが激烈遅い

ひどい目にあったので書く。

ストアドプロシージャベースで構築されたWEBシステムを、MySQL5.5に載せて運用を始めたところ、本稼働後数時間でデータベースのレスポンスが激烈に悪くなった。

別に破綻するほどアクセスが殺到した訳でもなく、サーバーのスペックが悪いわけでもなく、スロークエリのログにはストアド名がでるけど肝心の内部のクエリまでは見えない。

困ってプロセスリストをのぞくとcreate temporary tableがいっぱい詰まってるのが観測された。
一時テーブルをもっと多用した別のシステムでもこんな状況は見たことがない、ってことでどうもMySQL5.5の問題なのかもと推測。一時テーブルの設定(tmp_table_sizeなど)を変更してみたり、いろいろチューニングを試みるけど効果なし。

で、特に公式サイトとかに必要な設定とか注意点の情報も無く、ぐぐっても対処法が見あたらなかった(2ちゃんに1件だけ同じ事例があった)ので、やむを得ずMySQL5.1にダウングレードしたら嘘のように収束した。

バージョンはMySQL5.5.13、ダウングレード先はMySQL5.1.58。
MySQL5.5はいろいろすごくなってるんだよね? 一時テーブルだけひどくなるとか無いよね。。。
きっと設定があるはず。
もしくは将来直るはず。
頼むよ。



2013/8/15 追記
どうやら、MySQL5.5からデフォルトのストレージエンジンがInnoDBになったためらしい。
create tamporary table時に従来のMyISAMを明示的に指定してあげたら改善した模様。

Debian ( wheezy/sid ) で パスワードが変えられない

Debianサーバーをwheezy/sidにアップグレードしてみた。
んで、作業用アカウントhogeとかつくって、パスワード設定するじゃないですか。

passwd hoge

こんな風に。
そしたら、

Current Kerberos password:

とか聞かれて、そんなの設定してないのに! と大変困った。
rootのパスとか、空のままとか、適当に入れても当然だめで、

passwd: Authentication information cannot be recovered
passwd: password unchanged

こんな風に蹴っ飛ばされます。
んで、検索したら同じ症状で困ってる人がいて、解決方法としてはKerberos認証とめちゃえ、ということのようだった。

aptitude remove libpam-krb5

でスッキリ解決(なのか?)


今回助けてもらったネタ元:
Debian Unstable (Sid) – Unable to Change Root Password
http://www.uncompiled.com/2009/10/debian-unstable-sid-unable-to-change-root-password/

Linuxサーバー設定メモ(OSチューニング)

Linuxサーバー(主にCentOS)のOSチューニングについて、メモをまとめておく。
自分がセッティングするときに好んで仕込むおまじない。


■午前四時の高負荷の原因を止める。
mlocateとかmakewhatisとかは行われる処理はサーバーの仕事自体には関係ない。
なので実行させないようにする。
※やってることは実行属性を消してるだけなので、動かしたいときは755にでもしてあげればOK

 chmod 0 /etc/cron.daily/mlocate.cron
 chmod 0 /etc/cron.daily/makewhatis.cron
 chmod 0 /etc/cron.weekly/makewhatis.cron

Debianだとman-dbとかapt-xapian-indexとか。


スワップ抑制
メモリぎりぎりまでスワップを我慢させる設定。
低いほどスワップしにくくなる。
変更:60(デフォルト)-> 0

・すぐに反映
 echo 0 > /proc/sys/vm/swappiness

・再起動した時にも有効にする
 vi /etc/sysctl.conf
    # swap control
    vm.swappiness = 0


■【DB向け】IOキューサイズ変更
ディスクIOのキューサイズは低いほどレスポンス重視、高いほどスループット重視。
変更: 128(デフォルト) -> 1024
※設定対象はDBのデータがあるディスクデバイス

・すぐに反映
 echo 1024 > /sys/block/ディスクデバイス名/queue/nr_requests

・再起動したときにも有効にする
 vi /etc/rc.local
 上のコマンドを書いておく。


■【DB向け】IOスケジューラ
MySQL5.1のInnoDB pluginを使う場合、IOスレッドが多いので、デフォルトのcfqのままでよい。
MySQL5.1やMySQL5.0のビルトインInnoDBを使う場合は、noopかdeadlineにするとディスクIOの効率が向上する。
※設定対象はDBのデータがあるディスクデバイス
※2014/2/11追記:最近、DB向けにはcfqよりdeadlineやnoopのほうが高負荷に強いという話をみたので、ハードウェアによって最適な設定は違う模様。SSDとかの超高速なディスクが出てきたからかなぁ。

・すぐに反映
 echo deadline > /sys/block/ディスクデバイス名/queue/scheduler

・再起動したときにも有効にする
 vi /etc/rc.local
 上のコマンドを書いておく。


■【DB向け】ファイルへの最終アクセスタイムを記録しないようにする
ファイルシステムext3の場合の設定。
noatimeオプションを設定してあげるとファイルへの高頻度アクセスの負荷が減る。
※設定対象はDBのデータがあるパーティション

・すぐに反映
mount -o remount,noatime /dev/パーティション名  マウントポイント名

・再起動したときにも有効にする
 vi /etc/fstab
 /dev/パーティション名  マウントポイント名  ext3  defaults,noatime  2