Nginxで静的ファイルをPOSTで取れない

htmlファイルやtxtファイルといったAPサーバーに送らず、Nginxが自ら処理するアクセスをPOSTで取ろうとすると405エラーを返すという挙動になっているようです。

通常のブラウザアクセスではほぼ起こり得ないパターンですが、ネイティブアプリ向けAPIを作っていてクライアント側が全部のアクセスをPOSTで送ってきたりなんかするときはあるかもしれません(実際、僕が遭遇したパターンはこれです)。

解決法としては2つあります。

  1. 設定ファイルで何とかする
  2. ソースコードを変更する

(1.)は、つまり405で来たものを無理やり元のuriにして戻すという手法ですね。
以下の一行をserverディレクティブの中に入れてやるだけでOKです。

error_page 405 = $uri;

ただ、個人的に(1.)の方法はどうなんかなーと思ったので(2.)のように元から断つことにしました。
問題の箇所はsrc/http/modules/ngx_http_static_module.cの207行目あたり*1にある以下のようなコードです。

    if (r->method & NGX_HTTP_POST) {
        return NGX_HTTP_NOT_ALLOWED;
    }

こいつをコメントアウトしてmake && make installすればOKです。
バージョンアップするときに忘れるといけないので備忘録として。

*1:バージョンによって違うかも

UDPサーバー立てたらハマった件

UDPパケットの受信は出来るっぽいが送信が出来ない!何故!?
色々調べてみた結果*1、「ニフティクラウドのサーバー」かつ「CentOS6系のOS」でこの現象が起こることまではわかった*2
で、それを元にいろいろググってみるとひとつ怪しい情報にぶち当たる。。。

Why does the vmxnet3 driver shipped with RHEL 6 update 2 drops small UDP packets? - Red Hat Customer Portal

どうやらVMWareのネットワークドライバ&RHEL6系の状況下だと小さいサイズのUDPパケットをぶっ壊してしまうらしい。
で、その対処法もあっさり判明。

VMware上のVMでvNICをVMXNET3にしたときにUDPのパケットが飛ばない現象のまとめ - higeblog

えっ、カーネルアップデートするだけ?
ということでニフティクラウド(というかVMWare系)の仮想環境のカーネルアップデート方法に従ってアップデートしたところ普通にUDPパケットを送信できましたとさ。
めでたしめでたし。

*1:netcatを使っていろんな環境でテストしまくった

*2:さくらのクラウドでもCentOS6系のOSのサーバーを動かしているがそっちのほうは大丈夫で、ニフティクラウドのサーバーでもCentOS5系のOSでは起きなかった

Nginx ⇔ php-fpmの接続をTCPからUNIX Socketにしたら(何故か)性能が劣化した件

とあるサービス(と言ってもこのブログで散々言及してきた「あの」アプリのサーバーです)で、普通の「Apache with mod_php」な構成から「Nginx with php-fpm」な構成に変え、しばらく安定して稼働していた*1ので、さらなるパフォーマンスを目指してTCPなPort接続から(Port接続よりも速いとされている)UNIX Socket接続に替えてみたところ。。。

グラフで言うと11日の朝にSocketに替えたのですが、明らかにピーク時のコネクションが落ちています*2
それだけならいいのですが、どうやらアクセス数が多くなるとNginx→Socketの接続が出来ずにTimeoutやらResource temporarily unavailable的なエラーが出まくる始末。
何故だ・・・

*1:深夜はLAが上昇するもリクエストはちゃんと返していた

*2:ちなみに14日がほとんどとれてないのはただの設定ミスです

MySQLがダメならPostgreSQLしかないのか

巷ではOracleMySQLオープンソースじゃなくする?*1みたいな噂が駆け巡ってますが、なにも本家MySQLじゃなくたって、MySQLからフォークしたRDBMSがいくつかあるんですよ。

ブコメの反応とかで疑問に思っていたのは、何故すぐに「PostgreSQLに移行しないと!」っていう発想に陥るのか。

例えば「INSERT INTO ... ON DUPLICATE KEY UPDATE ...」などの構文はMySQL系のRDBMSでしか使えなかったはずですし、その他の動作*2にもかなりの違いがあるので、その知識を捨ててでもPostgreSQLに移行する価値はあるか?と言われると正直微妙な気がします。。。

じゃあMySQLからフォークしたのを使えばいいじゃない!

MySQLからフォークしたRDBMSは大きなところで3つ挙げられます。
名前は前からなんとなく知ってはいたんですが、必要に迫られることも無かったので特に調べていませんでした。
これを機会にちょっと調べてみたところ、三者にはそれぞれちょっとずつ違う特徴があるらしいです。

以下、個人的に調べた簡単な特徴です(あんまり当てにしないでください)。

  • Percona Server
    • 本家MySQLのコードから核となる部分はそんなに変えてないっぽい。互換性問題で迷ったらこれ使っておいたらいいと思います。
    • (本家と同じく)ひとつの企業が開発を主導。もちろん、いきなり使えなくなることはないと思うが、あまり気軽に開発に参加できないっていうクローズドさはあるかもしれない。Oracleよりはマシだと思うけど、今後同じような問題が噴き上がることが絶対にないとは言えない。
  • MariaDB
    • MySQLのコア・コードをかなりいじってる。とはいえ互換性も取られてるようなので代替としては使えそう。
    • 本家MySQLの開発者だった人物が立ち上げた会社が開発を支援、Perconaよりは開発に参加するのが容易そうなのでよりオープンな感じはする。
  • Drizzle
    • 「元々はMySQLをフォークしたものの今はほぼ別物」って感じがする。C++で書かれててマイクロカーネルでプラガブルでなMySQLみたいな感じ?
    • 上の2つと違って互換性に関してはとっても微妙な気がするが、いろいろと野心的な感じで将来性は感じる。既存のコードを大きく書きなおす覚悟&確固たる目的があって使うなら選択肢としてアリ。

上の3つのうち、Perconaだけ少し使って見ましたが、本家MySQLで使っていた設定を全く弄ることなく使えましたし、動作的にもほぼ変わり無いような感じでした。
MariaDBも(情報が確かなら)ほとんど弄ることなく移行できると思われます。
この2つには「Handlersocket」プラグインが最初から入ってるのが何気に嬉しいですね。
互換性を優先するならPercona、互換性を少し犠牲にしてでもパフォーマンスを取りたい場合はMariaDBを選ぶといいんじゃないでしょうか。

Drizzleに関してはかなり特定のターゲットに的を絞って作られているようなので、色々と実験するときには楽しいかもしれません。

参考

ぶっちゃけ、この資料に目を通しておけば上のクソみたいなまとめなんて読まなくても大丈夫なんですよね。
MySQL を超える

rbenv on Cron

Cronでrbenvで動かしてるruby*1を動かすのに手間取ったので備忘録として。

前提条件

  1. rbenvの設定は/etc/profileの中で行っている
    • ユーザーごとにrbenvの設定があるわけではなく、全ユーザー共通のrbenvがあるため
  2. 普段はログインシェルとしてzshを使っているので.zshrcの中で/etc/profileをsourceしてる

Cronでrbenvのrubyを動かすときは・・・

当然、rbenvの諸々の設定を予め読み込んでおかないといけない。
普通に使っている場合はログイン時に上記のように設定を読み込んでいるので動くが、Cronはそもそもログインしていないので、/etc/profileなどの設定はガン無視される・・・らしい。

シェル経由で動かす場合

最初に「source /etc/profile」と書いたりしてとにかくrbenvをロードする。

直でrubyスクリプトをcrontabに書く場合

ruby (スクリプト名)」の前にやはり「source /etc/profile」を書いて「;」で繋ぐ。

05 03 * * * root source /etc/profile; ruby /path/to/script/script_name.rb

もっとスマートな方法ないすかね・・・。

*1:っていう表現はどうなんですかね?

MySQLでテーブルの全件をちょっとずつ持ってきて処理したいときに注意すること

「全部で100万件とか1000万件とかあるような、でっかいテーブル内のデータを全部持ってきて特定の処理を加えた後にどっかに入れる」というような処理をしたいときに注意すること。

通常はLIMITを使ってオフセット/リミットで絞ってちょっとずつやる、みたいなことをやると思いますが*1、これ実はBETWEENしてやるほうがずっと速いです。

-- こっちよりも
SELECT id,name,data FROM table_name ORDER BY id ASC LIMIT 100000,1000
SELECT id,name,data FROM table_name ORDER BY id ASC LIMIT 101000,1000
SELECT id,name,data FROM table_name ORDER BY id ASC LIMIT 102000,1000
SELECT id,name,data FROM table_name ORDER BY id ASC LIMIT 103000,1000

-- こっちの方が速い!
SELECT id,name,data FROM table_name WHERE id BETWEEN 100000 AND 101000
SELECT id,name,data FROM table_name WHERE id BETWEEN 101000 AND 102000
SELECT id,name,data FROM table_name WHERE id BETWEEN 102000 AND 103000
SELECT id,name,data FROM table_name WHERE id BETWEEN 103000 AND 140000

というのはEXPLAINしてやれば明確で、LIMITのほうはLIMIT節で指定したところまでをすべて持ってきて返す直前に後ろの1000件に絞り込んでるのに対して、BETWEENのほうは予めWHERE節で持ってくる結果セットを絞り込んでいるので一瞬で結果が帰ってくるのだと思われます。LIMITのほうは後ろへいけば行くほど激遅になります。

mysql> EXPLAIN SELECT id,name,data FROM test_table ORDER BY id ASC LIMIT 900000,1000;
+----+-------------+-------------+-------+---------------+---------+---------+------+--------+-------+
| id | select_type | table       | type  | possible_keys | key     | key_len | ref  | rows   | Extra |
+----+-------------+-------------+-------+---------------+---------+---------+------+--------+-------+
|  1 | SIMPLE      | test_table  | index | NULL          | PRIMARY | 8       | NULL | 901000 |       |
+----+-------------+-------------+-------+---------------+---------+---------+------+--------+-------+
1 row in set (0.00 sec)

mysql> EXPLAIN SELECT id,name,data FROM test_table WHERE id BETWEEN 900000 AND 901000;
+----+-------------+-------------+-------+---------------+---------+---------+------+------+-------------+
| id | select_type | table       | type  | possible_keys | key     | key_len | ref  | rows | Extra       |
+----+-------------+-------------+-------+---------------+---------+---------+------+------+-------------+
|  1 | SIMPLE      | test_table  | range | PRIMARY       | PRIMARY | 8       | NULL | 1000 | Using where |
+----+-------------+-------------+-------+---------------+---------+---------+------+------+-------------+
1 row in set (0.00 sec)

もちろん、上のクエリ例を見ていただければ分かると思いますが、これが出来るのは「PRIMARYキーが連番でセットされてある」*2場合に限ります(つまりLIMITでもBETWEENでも結果的に同じデータが帰ってくるときに限る)。

また、PRIMARYに数字がふられていれば、必ずしも最初から最後まで連番である必要はなく、例えば「1,2,5,7,9,10,20,21」という風にPRIMARYが歯抜けに並んでいても総件数がかなり多い場合はこちらの方が速いです。
ただし、あまりにPRIMARYが歯抜けになっている(1,5,8,100,9000,100000とか・・・)割に、総件数が少ない場合などではそもそもループする回数が多くなってしまいLIMITより時間が掛かる可能性*3があります。
その場合、(振りなおせるケースでは)PRIMARYを連番で振りなおしてしまえば処理時間を改善できます。

*1:まさか「一気に全部APサーバーに持ってくる」とかやってる人は居ないと思いますが・・・

*2:PRIMARYが1,2,3,4,5,...,nというように連番で並んでいることが保証されている

*3:件数にもよる・・・個人的には10万件を越えたらいくら飛び飛びでもBETWEENでやったほうがいいと思います