2年前にリリースしたRubyのシステムが突然止まった。しかも部分的に。
UDPのパケットを待ち受けていろいろやるプログラムでLinuxのデーモンとして動作するサーバー。
デバッグログを見てみると、こんなエラーが。
Rubyの組み込みライブラリであるlogger.rbとそれが利用してるmonitor.rbで例外が起こっている。
安易に考えれば、writeメソッド内をbegin-rescue-endでくくってしまえばなんとかなりそうだ、と思ったけど、ちゃんと調べてからにする事にした。
まあ、たぶん1年に一回起こるか起こらないかの事でしょう、いずれ対策を考えます、などと言って現場を後にした。
帰ってきて「monitor.rb ThreadError thread killed」などとググってみると、まさにそのバグが見つかった。
上のは4ヶ月前で、予想通り例外処理による一時しのぎ。そしてつい2日前に根本的解決がコミットされていたようだ。
まさか、こんなバグが昔からあったとは。rescueしたあと原因をログにはいてたりするところでおきたら致命的ではないか。
もっともこのプログラムがスレッドを頻繁に使用しているから起こったのであって、Railsではスレッドを多用しないし、
そもそもproduction環境で大量にログを吐くってこともないだろうからめったに遭遇しないのかもしれない。
とりあえず、小さな修正なので、Rubyをアップグレードしなくてもパッチを当てるだけでよさそう、これで解決!と思った矢先。
「また止まったんだけど。」
ここ2年で動き続けたシステムが1日に2回止まった。
これは偶然じゃなく、システムが何らかの限界を訴えているに違いない。
現場に急行してfreeしてみると、なんと2GBの物理メモリの空き容量が50MBしかない。
しかし、topで見てもそんなにメモリ食ってるプロセスは見当たらない。
何が悪いのか分からなかったが、再起動すると1.3GBくらい空きが出た。
いろいろ見ても、PostgreSQLくらいしか疑うところがない。
履歴テーブルが140GB近くになってるのだけど、このテーブルから100件ほどSELECTすると、
一気に1GBくらいメモリが増大することが分かった。
後はGCでぎりぎり頑張ってるという状態が続く。
postgresql.confを見てみると、なんとautovacuum=offになってた。CentOS5.2のPostgreSQLは8.1で、デフォルトではAUTO VACUUMされない。
24時間365日動かさないといけないのに、このテーブルにはBLOBの画像データも含んでいて簡単にバックアップもできないし、VACUUMも何時間、あるいは何日かかるか予想できない。
その間、システムのレスポンスは確実に落ちる。ドキュメントによればオプションなしのVACUUM中は読み書きが許可されるとあるが、しばらくCPUを占有されると、本来の動作が割り込んできた1回目のレスポンスが極端に落ちる。
さてどうしよう。
- VACUUMって部分的にできるの?できるなら毎晩ちょっとずつ実行するようにする。
- 代替機を用意し、その間にVACUUMを済ます。代替機にたまった履歴は破棄。
- システム完全停止。VACUUM FULL実行。終わるまで待ってもらう。
いずれにしても大変だ。
こういう蓄積するデータについてはデータベースではなくファイルで管理するしかないのだろうか。
--追記
メモリのfree領域がどんどん減っていくのは正常な挙動らしい。Linuxではバッファやキャッシュを利用してディスクIOの負担を減らしており、この領域はプロセスに占有されているとはみなさないらしい。
そのため、以下のようになっていても53.6MBしか空きがない!とあせる必要はないということのようだ。
[root@localhost ~]# free total used free shared buffers cached Mem: 2075444 2021844 53600 0 21848 1702788 -/+ buffers/cache: 297208 1778236 Swap: 2031608 112 2031496
いまいちRubyのエラー頻度が増えた理由との関連性があるのかよく分からないけど、VACUUMしてないことは今回のトラブルにおいてそれほど重要ではなかったということだと思う。しかし、UPDATEやDELETEしてできた領域が再利用できないのは問題なので、今後はなんとかしないといけないと思う。なにかの機会に8.4に上げるかのう。
--追記
VACUUM FULLというのは、PostgreSQL7.1以前のVACUUMで、今はこれをしないで済むように定期的なVACUUMをやるようにしなさいとのこと。
でもVACUUMがいくらテーブルロックをしなくても、確実に負荷は増すので、パーティショニングといってテーブルを分割する仕組みを利用するのがよさそう。パーティションテーブルの名前を年月からつけておき、1年以上前の月のテーブルは丸ごとDROPすれば不要領域も削除される。真のメンテナンスフリーが実現可能?
このあたりの仕組みは、最近のPostgreSQLはかなり機能アップしてきているみたいです。逆に言うとLinuxに標準で含まれるパッケージを利用してる場合はかなり使い勝手の悪さを強いられているということでしょう。
24時間365日メンテナンスフリーなんて、小さな規模では考えなくてもできるものだと思い込んでましたが、それはかなり大変なことで、データベースに詳しくない人でもそれなりにできる道が開けたというのはつい今しがた、というところなのかもしれません。
Railsならデータベースを意識する必要なし、本番環境で入れ替えたりもできちゃう、なんてのは、やはり宣伝効果を狙った機能ということになるのでしょうね。