<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/atom10japanesefull.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0">
    <title>blog.nomadscafe.jp</title>
    <link rel="alternate" type="text/html" href="http://blog.nomadscafe.jp/" />
    
    <id>tag:blog.nomadscafe.jp,2009-07-12://1</id>
    <updated>2012-05-17T06:24:59Z</updated>
    <subtitle>湘南新宿ラインで通勤する Operations Engineer のBlog</subtitle>
    <generator uri="http://www.sixapart.com/movabletype/">Movable Type 4.27-ja</generator>

<atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/atom+xml" href="http://feeds.feedburner.com/nomadscafejp" /><feedburner:info uri="nomadscafejp" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><logo>http://feeds.feedburner.jp/~fc/nomadscafejp?bg=99CCFF&amp;fg=444444&amp;anim=0</logo><entry>
    <title>CloudForecast で Redis の監視</title>
    <link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/nomadscafejp/~3/dMi5YfTuQqY/cloudforecast-redis.html" />
    <id>tag:blog.nomadscafe.jp,2012://1.170</id>

    <published>2012-05-17T06:16:37Z</published>
    <updated>2012-05-17T06:24:59Z</updated>

    <summary>Redisをサービスで利用するというので、CloudForecastで監視するプ...</summary>
    <author>
        <name>Masahiro Nagano</name>
        <uri>http://blog.nomadscafe.jp/</uri>
    </author>
    
    
    <content type="html" xml:lang="ja" xml:base="http://blog.nomadscafe.jp/">
        <![CDATA[<p><a href="http://redis.io/">Redis</a>をサービスで利用するというので、<a href="https://github.com/kazeburo/cloudforecast">CloudForecast</a>で監視するプラグインを作ってみました。</p>

<p>監視項目は Percona の <a href="http://www.percona.com/doc/percona-monitoring-plugins/cacti/redis-templates.html">Monitoring Plugins</a> を参考にしてます</p>

<p><span class="mt-enclosure mt-enclosure-image" style="display: inline;"><a href="http://blog.nomadscafe.jp/2012/05/17/cf_redis.png"><img alt="cf_redis.png" src="http://blog.nomadscafe.jp/assets_c/2012/05/cf_redis-thumb-600x257-142.png" width="600" height="257" class="mt-image-center" style="text-align: center; display: block; margin: 0 auto 20px;" /></a></span></p>

<p><br />
<br /></p>

<p>Redis の統計情報は <a href="http://redis.io/commands/info">info</a> コマンドを実行すると得られます。telnet でも実行可能です。</p>

<pre><code>info
$1020
redis_version:2.4.11
redis_git_sha1:00000000
redis_git_dirty:0
arch_bits:64
multiplexing_api:epoll
gcc_version:4.1.2
process_id:18078
uptime_in_seconds:781399
uptime_in_days:9
lru_clock:1553555
used_cpu_sys:1.90
used_cpu_user:0.82
used_cpu_sys_children:0.00
used_cpu_user_children:0.00
connected_clients:185
connected_slaves:1
client_longest_output_list:0
client_biggest_input_buf:0
blocked_clients:0
used_memory:2357504
used_memory_human:2.25M
used_memory_rss:4677632
used_memory_peak:3999144
used_memory_peak_human:3.81M
mem_fragmentation_ratio:1.98
mem_allocator:jemalloc-2.2.5
loading:0
aof_enabled:0
changes_since_last_save:0
bgsave_in_progress:0
last_save_time:1336737589
bgrewriteaof_in_progress:0
total_connections_received:29998
total_commands_processed:303740 
expired_keys:0
evicted_keys:0
keyspace_hits:251
keyspace_misses:65
pubsub_channels:41
pubsub_patterns:0
latest_fork_usec:1997
vm_enabled:0
role:master
slave0:10.x.x.x,34080,online
db0:keys=8,expires=1
</code></pre>

<p>どれが重要な数字なのかまだわからない</p>
]]>
        

    </content>
<feedburner:origLink>http://blog.nomadscafe.jp/2012/05/cloudforecast-redis.html</feedburner:origLink></entry>

<entry>
    <title>MySQL で SELECT COUNT(DISTINCT) VS SELECT COUNT(*) FROM (SELECT .. GROUP BY ..) AS t</title>
    <link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/nomadscafejp/~3/xFfBrwmo3sM/mysql-select-countdistinct-vs-select-count-from-select-group-by-as-t.html" />
    <id>tag:blog.nomadscafe.jp,2012://1.169</id>

    <published>2012-04-09T08:34:43Z</published>
    <updated>2012-04-09T08:57:00Z</updated>

    <summary>とあるMySQLのslowlogに残っていたところから見つけたクエリの書き換え。...</summary>
    <author>
        <name>Masahiro Nagano</name>
        <uri>http://blog.nomadscafe.jp/</uri>
    </author>
    
    
    <content type="html" xml:lang="ja" xml:base="http://blog.nomadscafe.jp/">
        <![CDATA[<p>とあるMySQLのslowlogに残っていたところから見つけたクエリの書き換え。</p>

<p>サービスのどこで使われているものかまで詳しくみていないんだけど</p>

<pre><code>CREATE TABLE `category2item` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `category_id` int(10) unsigned NOT NULL,
  `subcategory_id` int(10) unsigned NOT NULL,
  `item_id` int(10) unsigned NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `subcategory_id` (`subcategory_id`,`item_id`),
  KEY `picture_id` (`item_id`),
  KEY `category_id` (`category_id`,`item_id`)
) ENGINE=InnoDB AUTO_INCREMENT=42651972 DEFAULT CHARSET=utf8
</code></pre>

<p>カテゴリーとアイテムを結びつけるテーブルがあって、そこに対して、ユニークなアイテム数をカウントするクエリを実行する。</p>

<pre><code>mysql&gt; SELECT COUNT(DISTINCT item_id) FROM category2item WHERE category_id = '2';
+----------------------------+
| COUNT(DISTINCT picture_id) |
+----------------------------+
|                    2388652 |
+----------------------------+
1 row in set (3.02 sec)
</code></pre>

<p>件数がそれなり多いので、3秒以上掛かる。</p>

<p>slowlogにも多く残っていたので書き換えをしてみた。それがこれ。</p>

<pre><code>mysql&gt; SELECT COUNT(*) FROM (SELECT item_id FROM category2item WHERE category_id = '2' GROUP BY item_id) AS t;
+-------------------------+
| COUNT(DISTINCT item_id) |
+-------------------------+
|                 2388652 |
+-------------------------+
1 row in set (1.26 sec)
</code></pre>

<p>大体1/2の時間になった。このクエリではユニークな件数取得のために DISTINCT ではなく、GROUP BY してユニークなIDリストを取得して、その結果を COUNT(*) している。</p>

<p>なんでこれが速くなるのかよくわからなかったんだけど、EXPLAIN をしてみたところ</p>

<pre><code>mysql&gt; EXPLAIN SELECT COUNT(*) FROM (SELECT item_id FROM category2item WHERE category_id = '2' GROUP BY item_id) AS t;
+----+-------------+---------------+------+---------------+-------------+---------+------+---------+------------------------------+
| id | select_type | table         | type | possible_keys | key         | key_len | ref  | rows    | Extra                        |
+----+-------------+---------------+------+---------------+-------------+---------+------+---------+------------------------------+
|  1 | PRIMARY     | NULL          | NULL | NULL          | NULL        | NULL    | NULL |    NULL | Select tables optimized away |
|  2 | DERIVED     | category2item | ref  | category_id   | category_id | 4       |      | 4409520 | Using where; Using index     |
+----+-------------+---------------+------+---------------+-------------+---------+------+---------+------------------------------+
2 rows in set (1.25 sec)
</code></pre>

<p>Extra に 「Select tables optimized away」が付いていたので、 MyISAM のクエリ最適化が行われたと予想。</p>

<p>そこでプロファイリングを有効にしてクエリを実行。プロファイリングについてはnippondanjiさんのblogを参考にした</p>

<p><a href="http://nippondanji.blogspot.jp/2009/02/mysql.html">漢(オトコ)のコンピュータ道: プロファイリングで快適MySQLチューニング生活</a></p>

<pre><code>mysql&gt; SET profiling=1;
mysql&gt; SELECT COUNT(*) ..
mysql&gt; SHOW PROFILE SOURCE;
+---------------------------+----------+---------+---------------+-------------+
| Status                    | Duration | Source  | Source_file   | Source_line |
+---------------------------+----------+---------+---------------+-------------+
| starting                  | 0.000041 | NULL    | NULL          |        NULL |
| Opening tables            | 0.000007 | unknown | sql_base.cc   |        4519 |
| System lock               | 0.000002 | unknown | lock.cc       |         258 |
| Table lock                | 0.000031 | unknown | lock.cc       |         269 |
| optimizing                | 0.000007 | unknown | sql_select.cc |         833 |
| statistics                | 0.000041 | unknown | sql_select.cc |        1024 |
| preparing                 | 0.000014 | unknown | sql_select.cc |        1046 |
| executing                 | 0.000004 | unknown | sql_select.cc |        1780 |
| Sorting result            | 0.000002 | unknown | sql_select.cc |        2205 |
| Sending data              | 0.510723 | unknown | sql_select.cc |        2338 |
| converting HEAP to MyISAM | 0.106450 | unknown | sql_select.cc |       10984 |
| Sending data              | 0.641679 | unknown | sql_select.cc |       11049 |
| init                      | 0.000011 | unknown | sql_select.cc |        2528 |
| optimizing                | 0.000005 | unknown | sql_select.cc |         833 |
| executing                 | 0.000011 | unknown | sql_select.cc |        1780 |
| end                       | 0.000002 | unknown | sql_select.cc |        2574 |
| query end                 | 0.000001 | unknown | sql_parse.cc  |        5082 |
| freeing items             | 0.000014 | unknown | sql_parse.cc  |        6106 |
| removing tmp table        | 0.003672 | unknown | sql_select.cc |       10916 |
| closing tables            | 0.000003 | unknown | sql_select.cc |       10941 |
| logging slow query        | 0.000002 | unknown | sql_parse.cc  |        1723 |
| cleaning up               | 0.000002 | unknown | sql_parse.cc  |        1691 |
+---------------------------+----------+---------+---------------+-------------+
</code></pre>

<p>22 rows in set (0.00 sec)</p>

<p>「converting HEAP to MyISAM」が入っているのでやはり MyISAM が使われた模様。</p>

<p>試しに元の COUNT(DISTINCT item_id) のプロファイルをとってみると</p>

<pre><code>mysql&gt; SHOW PROFILE SOURCE;
+--------------------+----------+------------------+---------------+-------------+
| Status             | Duration | Source_function  | Source_file   | Source_line |
+--------------------+----------+------------------+---------------+-------------+
| starting           | 0.000032 | NULL             | NULL          |        NULL |
| Opening tables     | 0.000006 | unknown function | sql_base.cc   |        4519 |
| System lock        | 0.000003 | unknown function | lock.cc       |         258 |
| Table lock         | 0.000003 | unknown function | lock.cc       |         269 |
| init               | 0.000010 | unknown function | sql_select.cc |        2528 |
| optimizing         | 0.000006 | unknown function | sql_select.cc |         833 |
| statistics         | 0.000038 | unknown function | sql_select.cc |        1024 |
| preparing          | 0.000009 | unknown function | sql_select.cc |        1046 |
| executing          | 0.000022 | unknown function | sql_select.cc |        1780 |
| Sending data       | 3.018044 | unknown function | sql_select.cc |        2338 |
| end                | 0.001905 | unknown function | sql_select.cc |        2574 |
| removing tmp table | 0.000006 | unknown function | sql_select.cc |       10916 |
| end                | 0.000003 | unknown function | sql_select.cc |       10941 |
| query end          | 0.000002 | unknown function | sql_parse.cc  |        5082 |
| freeing items      | 0.000017 | unknown function | sql_parse.cc  |        6106 |
| logging slow query | 0.000002 | unknown function | sql_parse.cc  |        1723 |
| logging slow query | 0.000030 | unknown function | sql_parse.cc  |        1733 |
| cleaning up        | 0.000003 | unknown function | sql_parse.cc  |        1691 |
+--------------------+----------+------------------+---------------+-------------+
18 rows in set (0.00 sec)
</code></pre>

<p>こんな感じ。「Sending data」の時間が大きくなっているところから、ユニークなIDを割り出す為のデータ走査に時間が取られたのかなと予想。</p>

<p>では、MyISAMに変換されなかったらどうなるかと思って、tmp_table_size を 64MB (元16MB) まであげてみると、</p>

<pre><code>mysql&gt; SELECT COUNT(*) FROM (SELECT picture_id FROM category2picture WHERE category_id = '2' GROUP BY picture_id) AS t;
+----------+
| COUNT(*) |
+----------+
|  2388710 |
+----------+
1 row in set (1.10 sec)
mysql&gt; SHOW PROFILE SOURCE;
+--------------------+----------+------------------+---------------+-------------+
| Status             | Duration | Source_function  | Source_file   | Source_line |
+--------------------+----------+------------------+---------------+-------------+
| starting           | 0.000041 | NULL             | NULL          |        NULL |
| Opening tables     | 0.000006 | unknown function | sql_base.cc   |        4519 |
| System lock        | 0.000002 | unknown function | lock.cc       |         258 |
| Table lock         | 0.000029 | unknown function | lock.cc       |         269 |
| optimizing         | 0.000006 | unknown function | sql_select.cc |         833 |
| statistics         | 0.000037 | unknown function | sql_select.cc |        1024 |
| preparing          | 0.000014 | unknown function | sql_select.cc |        1046 |
| executing          | 0.000004 | unknown function | sql_select.cc |        1780 |
| Sorting result     | 0.000002 | unknown function | sql_select.cc |        2205 |
| Sending data       | 1.099120 | unknown function | sql_select.cc |        2338 |
| init               | 0.000012 | unknown function | sql_select.cc |        2528 |
| optimizing         | 0.000004 | unknown function | sql_select.cc |         833 |
| executing          | 0.000010 | unknown function | sql_select.cc |        1780 |
| end                | 0.000002 | unknown function | sql_select.cc |        2574 |
| query end          | 0.000002 | unknown function | sql_parse.cc  |        5082 |
| freeing items      | 0.000013 | unknown function | sql_parse.cc  |        6106 |
| removing tmp table | 0.000015 | unknown function | sql_select.cc |       10916 |
| closing tables     | 0.000002 | unknown function | sql_select.cc |       10941 |
| logging slow query | 0.000001 | unknown function | sql_parse.cc  |        1723 |
| cleaning up        | 0.001948 | unknown function | sql_parse.cc  |        1691 |
+--------------------+----------+------------------+---------------+-------------+
20 rows in set (0.00 sec)
</code></pre>

<p>「converting HEAP to MyISAM」がなくなって「Sending data」が1回にまとまった分、MyISAMが使われるより若干早い感じ。HEAP(MEMORY)の場合もCOUNT(*)が最適化されるんですね。<a href="http://dev.mysql.com/doc/refman/5.1/en/where-optimizations.html">8.3.1.2. WHERE Clause Optimization</a>にも書いてあった</p>

<p>まぁ、これでも1秒以上クエリに掛かるので、Webアプリケーションの中から使うのは躊躇われるところ。どこで使われているかアプリケーションのソースコード読むかー。</p>
]]>
        

    </content>
<feedburner:origLink>http://blog.nomadscafe.jp/2012/04/mysql-select-countdistinct-vs-select-count-from-select-group-by-as-t.html</feedburner:origLink></entry>

<entry>
    <title>「サーバ/インフラエンジニア養成読本 管理/監視編」に寄稿しました</title>
    <link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/nomadscafejp/~3/cycvfHlVHn0/post-13.html" />
    <id>tag:blog.nomadscafe.jp,2012://1.168</id>

    <published>2012-04-09T06:09:25Z</published>
    <updated>2012-04-09T06:45:04Z</updated>

    <summary>Software Design 2011年9月号に寄稿した『運用エンジニア「攻め...</summary>
    <author>
        <name>Masahiro Nagano</name>
        <uri>http://blog.nomadscafe.jp/</uri>
    </author>
    
    
    <content type="html" xml:lang="ja" xml:base="http://blog.nomadscafe.jp/">
        <![CDATA[<p>Software Design 2011年9月号に寄稿した『運用エンジニア「攻め」の仕事術」』の記事が4/11 に発売の『<a href="http://gihyo.jp/book/2012/978-4-7741-5037-6">サーバ/インフラエンジニア養成読本 管理/監視編</a>』に載りました。技術評論社様、献本ありがとうございます。</p>

<p><iframe src="http://rcm-jp.amazon.co.jp/e/cm?lt1=_blank&amp;bc1=000000&amp;IS2=1&amp;nou=1&amp;bg1=FFFFFF&amp;fc1=000000&amp;lc1=0000FF&amp;t=nomadscafejp-22&amp;o=9&amp;p=8&amp;l=as4&amp;m=amazon&amp;f=ifr&amp;ref=ss_til&amp;asins=4774150371" style="width:120px;height:240px;" scrolling="no" marginwidth="0" marginheight="0" frameborder="0"></iframe><br />
<br /></p>

<p>2008年から2011年にSoftware Design誌に載ったシステム監視やトラブルシューティングに関する記事が数多く採録されています。n0tsさんやwebooさんの記事は2008年に書かれたものですが今でも十分に参考になりますね。</p>

<p>監視ツールやトラブルシューティングにつかうコマンドの使い方だけではなく、どのようなマインドで運用に取り組むべきなのかまで、広く記事が集められているので、手に取って読んで頂けたらと思います。</p>
]]>
        

    </content>
<feedburner:origLink>http://blog.nomadscafe.jp/2012/04/post-13.html</feedburner:origLink></entry>

<entry>
    <title>Apache httpd.conf の Allow from .. にコメントを書いてしまうとDNSの逆引きが行われてレスポンスが悪化するので注意の件 + コメントが書けるようになるパッチ</title>
    <link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/nomadscafejp/~3/h5LJbLn5puM/apache-httpdconf-allow-from-dns.html" />
    <id>tag:blog.nomadscafe.jp,2012://1.167</id>

    <published>2012-04-04T07:18:59Z</published>
    <updated>2012-04-04T10:29:32Z</updated>

    <summary>Apacheのconfにコメントを書く際に、設定の後ろに書く事はできないのは知ら...</summary>
    <author>
        <name>Masahiro Nagano</name>
        <uri>http://blog.nomadscafe.jp/</uri>
    </author>
    
    
    <content type="html" xml:lang="ja" xml:base="http://blog.nomadscafe.jp/">
        <![CDATA[<p>Apacheのconfにコメントを書く際に、設定の後ろに書く事はできないのは知られているのかどうかよくわかりませんが、その通りです。例えば</p>

<pre><code>MaxRequestsPerChild 200 #少なめに
</code></pre>

<p>これは syntax error になります</p>

<pre><code>% ./local/httpd/bin/apachectl -t
Syntax error on line 12 of /Users/.../local/httpd/conf/httpd.conf:
MaxRequestsPerChild takes one argument, Maximum number of requests a particular child serves before dying.
</code></pre>

<p>よくやりがちなんですが、<a href="http://httpd.apache.org/docs/2.2/configuring.html">ドキュメント</a>にも </p>

<blockquote>
  <p>Directives in the configuration files are case-insensitive, but arguments to directives are often case sensitive. Lines that begin with the hash character &#8220;#&#8221; are considered comments, and are ignored. Comments may not be included on a line after a configuration directive. </p>
</blockquote>

<p>書いてありました。</p>

<p>ところで、最近とあるサービスの動いているApacheのhttpd.confで</p>

<pre><code>Order Deny,Allow
Deny from All
Allow from 127.0.0.1
..
Allow from x.y.z.252 #nat
Allow from x.y.z.253 #office1
Allow from x.y.z.254 #office2
</code></pre>

<p>という設定がされているのを見ました。設定行にコメントがあるけど、どうやらシンタックスエラーにはなって居ない。では問題ないじゃん、で終わるかというとそうではありません。</p>

<p>実はこれだけで HostnameLookups の設定関係なしに逆引きが行われてしまい、知らないうちにサーバの負荷になってしまうのです。実際にコメント部分を消したところロードアベレージが半分ぐらいに下がりました。</p>

<p>なぜ、シンタックスエラーにもならず、逆引きが行われるのかというと、「Allow」というディレクティブが複数の値を受け入れて、コメントをホスト名として扱ってしまうためです。ソースコードでみると、まずmod<em>authz</em>host.c の 174行目</p>

<pre><code>AP_INIT_ITERATE2("allow", allow_cmd, &amp;its_an_allow, OR_LIMIT,
                "'from' followed by hostnames or IP-address wildcards"),
AP_INIT_ITERATE2("deny", allow_cmd, NULL, OR_LIMIT,
                "'from' followed by hostnames or IP-address wildcards"),
</code></pre>

<p>ここで「Allow」と「Deny」のディレクティブを定義しています。AP<em>INIT</em>ITERATE2 というディレクティブ処理関数登録マクロは引数を無限に受け取り、それらを１つずつ処理するディレクティブを登録します。</p>

<p>AP<em>INIT</em>XXXのマクロについてはklabさんの記事が詳しい<br />
<a href="http://dsas.blog.klab.org/archives/50609035.html">DSAS開発者の部屋:[補足記事]ディレクティブ処理関数登録マクロ一覧 (apache module 開発事初め その3-2)
</a></p>

<p>そして、設定を行う「allow_cmd」では</p>

<pre><code>static const char *allow_cmd(cmd_parms *cmd, void *dv, const char *from,
                         const char *where_c)
{
    char *where = apr_pstrdup(cmd-&gt;pool, where_c);
    .. 省略 ..
    else if (!strcasecmp(where, "all")) {
        a-&gt;type = T_ALL;
    }
    else if ((s = ap_strchr(where, '/'))) {
        *s++ = '\0';
        a-&gt;type = T_IP;
    }
    else if (!APR_STATUS_IS_EINVAL(rv = apr_ipsubnet_create(&amp;a-&gt;x.ip, where,
                                                        NULL, cmd-&gt;pool))) {
        if (rv != APR_SUCCESS) {
            apr_strerror(rv, msgbuf, sizeof msgbuf);
            return apr_pstrdup(cmd-&gt;pool, msgbuf);
        }
        a-&gt;type = T_IP;
    }
    else { /* no slash, didn't look like an IP address =&gt; must be a host */
        a-&gt;type = T_HOST;
    }
    return NULL;
}
</code></pre>

<p>where に httpd.confで指定した文字列がきます。「#nat」や「#office」はIPアドレスではありませんし、スラッシュも含まないのでホスト名として扱われます。</p>

<p>Host名として扱われると、<a href="http://httpd.apache.org/docs/2.2/mod/mod_authz_host.html">ここ</a>に</p>

<blockquote>
  <p>Hosts whose names match, or end in, this string are allowed access. Only complete components are matched, so the above example will match foo.apache.org but it will not match fooapache.org. This configuration will cause Apache to perform a double reverse DNS lookup on the client IP address, regardless of the setting of the HostnameLookups directive. It will do a reverse DNS lookup on the IP address to find the associated hostname, and then do a forward lookup on the hostname to assure that it matches the original IP address. Only if the forward and reverse DNS are consistent and the hostname matches will access be allowed.</p>
</blockquote>

<p>書いてある通り、HostnameLookupsの設定に関係なく、逆引きを行ってアクセス可否を決めます。なので、「Allow」や「Deny」にはコメントを書くのは危険が危ないです。書かないようにしましょう。</p>

<p><br / ><br / ></p>

<p style="font-size:1.4em;font-weight:bold;text-align:center">だ　が　し　か　し</p>

<p><br / ><br / ></p>

<p>書きたいですよね。「Allow」行にコメント。なのでパッチ書いてみました。</p>

<pre><code>--- httpd-2.2.22.orig/modules/aaa/mod_authz_host.c  2008-06-14 20:44:19.000000000 +0900
+++ httpd-2.2.22/modules/aaa/mod_authz_host.c   2012-04-04 16:01:10.000000000 +0900
@@ -117,9 +117,17 @@
     char msgbuf[120];
     apr_status_t rv;

+    if (cmd-&gt;info &amp;&amp; !strncasecmp(cmd-&gt;info, "#",1))
+        return NULL;
+
     if (strcasecmp(from, "from"))
         return "allow and deny must be followed by 'from'";

+    if(!strncasecmp(where, "#", 1)) {
+        cmd-&gt;info = where;
+        return NULL;
+    }
+
     a = (allowdeny *) apr_array_push(cmd-&gt;info ? d-&gt;allows : d-&gt;denys);
     a-&gt;x.from = where;
     a-&gt;limited = cmd-&gt;limited;
</code></pre>

<p>かなり無理矢理ですが、このpatchをあてると、「Allow」と「Deny」の設定に限り「#」以降の文字を無視できます。</p>

<pre><code>Order deny,allow
Deny from all
Allow from 192.168.67.1 #foobar hogehoge
Allow from 127.0.0.1
</code></pre>

<p>「#foobar」だけではなく、ちゃんと「hogehoge」もコメントとして扱います。</p>

<p>アクセス制御を細かくやる必要がある場合、コメントを後ろに追加するとわりとわかりやすい設定が書けると思うので、このpatchでhttpd.conf が捗る事間違いなし！&#8230;かな</p>

<p>【追記】<br />
社内やIRCでちょっと話をして、コメントがあったらシンタックスエラーにしたほうがいいのではないかとの意見を頂きました。<br />
patchがあたっているApacheとそうでないApache両方があった場合、設定コピペしてはまることがありそうなので、よくないと。<br />
確かにそうなので、上のpatchを試してみようと思う方は、そのあたりを注意して頂けると幸いです。return NULLの代わりにエラーメッセージを返すとシンタックスエラー扱いになるはずです<br />
【/追記】</p>

<p>同じ事を書いている方いた<br />
<a href="http://blog.dtpwiki.jp/dtp/2007/12/apachehostnamel_b43a.html">M.C.P.C.: ApacheでHostnameLookups offでもログにホスト名が記録される場合
</a></p>
]]>
        

    </content>
<feedburner:origLink>http://blog.nomadscafe.jp/2012/04/apache-httpdconf-allow-from-dns.html</feedburner:origLink></entry>

<entry>
    <title>Replication Booster for MySQL を試す</title>
    <link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/nomadscafejp/~3/BVFyO8n_1ZY/replication-booster-for-mysql.html" />
    <id>tag:blog.nomadscafe.jp,2012://1.166</id>

    <published>2012-03-23T03:17:17Z</published>
    <updated>2012-03-23T03:28:30Z</updated>

    <summary>松信さんが作った Replication Booster for MySQL を...</summary>
    <author>
        <name>Masahiro Nagano</name>
        <uri>http://blog.nomadscafe.jp/</uri>
    </author>
    
    
    <content type="html" xml:lang="ja" xml:base="http://blog.nomadscafe.jp/">
        <![CDATA[<p>松信さんが作った Replication Booster for MySQL をデータサイズが大きいデータベースに対して使ってみました。</p>

<p><a href="http://yoshinorimatsunobu.blogspot.jp/2011/10/making-slave-pre-fetching-work-better.html">Yoshinori Matsunobu&#8217;s blog: Making slave pre-fetching work better with SSD
</a><br />
<a href="https://github.com/yoshinorim/replication-booster-for-mysql">github - yoshinorim/replication-booster-for-mysql</a></p>

<p>Replication Booster for MySQL をものすごく簡単に説明すると、以下のようになるでしょうか。</p>

<p><span class="mt-enclosure mt-enclosure-image" style="display: inline;"><img alt="replication_booster_mysql.png" src="http://blog.nomadscafe.jp/2012/03/23/replication_booster_mysql.png" width="558" height="217" class="mt-image-center" style="text-align: center; display: block; margin: 0 auto 20px;" /></span></p>

<p>MySQL でレプリケーションを設定した場合、マスターのバイナリログをIOスレッドが読み取り、relay-logへ記録します。そしてSQLスレッドがrelay-logから読み取ってテーブルを更新して行きます。Replication Booster を実行するとrelay-logを読み取り、更新クエリを参照クエリに書き換えてSQLスレッドよりも先にテーブルに対してアクセスします。これにより、InnoDBのbuffer_poolなどメモリ上にこれから更新されるデータを載せ、更新クエリの実行にかかる時間を減らそうというものです。</p>

<p>主なユースケースとしてはスレーブサーバの再起動した際に、バッファをより速く暖めレプリケーションの遅延の解消にかかる時間を短くすることが考えられます。</p>

<h2>検証した環境など</h2>

<p>今回試したのは再起動後の遅延解消ではなく、メモリに対してデータサイズがとても大きな某サービスのデータベースのバックアップスレーブの遅延対策としてReplication Boosterを動かしてみることです。ちなみにデータサイズは560GB、メモリは16GB(innodb_buffer_pool=10GB)です。毎秒10弱の更新クエリがあります。DiskはSATAをBBU付きRAID1という環境です</p>

<p><span class="mt-enclosure mt-enclosure-image" style="display: inline;"><img alt="replication_backup_slave.png" src="http://blog.nomadscafe.jp/2012/03/23/replication_backup_slave.png" width="437" height="319" class="mt-image-center" style="text-align: center; display: block; margin: 0 auto 20px;" /></span></p>

<p>レプリケーション遅延はバックアップスレーブだけではなく参照を行うスレーブサーバでも起きているのですが、参照を行う分だけバッファのヒット率が多少高いのか、バックアップスレーブほど遅延していません。そこでReplication Boosterを使い、バックアップスレーブでも参照クエリを発行してバッファの効率をあげてみるのが今回の狙い。</p>

<h2>Replication Booster のインストール</h2>

<p>2013年3月時点で、Replication Boosterも依存するBinlog APIもまだpre-alphaということで注意が必要です</p>

<p>今回インストールしたのはCentOS 5.xのサーバ。</p>

<p>まずcmake、bzrのインストールとboostのアップデート。bzrはepelを利用すると簡単。boostは <a href="http://packages.atrpms.net/dist/el5/boost/">atrpms</a>  を利用できます。</p>

<pre><code>$ sudo yum install cmake bzr
$ sudo rpm -Uvh http://dl.atrpms.net/all/boost-1.39.0-9.el5.x86_64.rpm \
  http://dl.atrpms.net/all/boost-devel-1.39.0-9.el5.x86_64.rpm http://dl.atrpms.net/all/boost-wave-1.39.0-9.el5.x86_64.rpm \
  http://dl.atrpms.net/all/boost-thread-1.39.0-9.el5.x86_64.rpm http://dl.atrpms.net/all/boost-test-1.39.0-9.el5.x86_64.rpm \
  http://dl.atrpms.net/all/boost-system-1.39.0-9.el5.x86_64.rpm http://dl.atrpms.net/all/boost-static-1.39.0-9.el5.x86_64.rpm \
  http://dl.atrpms.net/all/boost-signals-1.39.0-9.el5.x86_64.rpm http://dl.atrpms.net/all/boost-serialization-1.39.0-9.el5.x86_64.rpm \
  http://dl.atrpms.net/all/boost-regex-1.39.0-9.el5.x86_64.rpm http://dl.atrpms.net/all/boost-python-1.39.0-9.el5.x86_64.rpm \
  http://dl.atrpms.net/all/boost-program-options-1.39.0-9.el5.x86_64.rpm http://dl.atrpms.net/all/boost-math-1.39.0-9.el5.x86_64.rpm \
  http://dl.atrpms.net/all/boost-iostreams-1.39.0-9.el5.x86_64.rpm http://dl.atrpms.net/all/boost-graph-1.39.0-9.el5.x86_64.rpm \
  http://dl.atrpms.net/all/boost-filesystem-1.39.0-9.el5.x86_64.rpm http://dl.atrpms.net/all/boost-date-time-1.39.0-9.el5.x86_64.rpm \
  http://dl.atrpms.net/all/boost-doc-1.39.0-9.el5.x86_64.rpm
</code></pre>

<p>epelのyum repository利用しにくい環境の場合は、</p>

<pre><code>$ sudo yum install python-pycurl
$ sudo rpm -Uvh  http://dl.fedoraproject.org/pub/epel/5/x86_64/bzr-2.1.4-2.el5.x86_64.rpm \
  http://dl.fedoraproject.org/pub/epel/5/x86_64/python-paramiko-1.7.6-1.el5.noarch.rpm \
  http://dl.fedoraproject.org/pub/epel/5/x86_64/python-crypto-2.0.1-4.el5.2.x86_64.rpm
</code></pre>

<p>これでも大丈夫です。</p>

<p>次にBinlog APIのインストール。repositoryは　<a href="https://code.launchpad.net/mysql-replication-listener">https://code.launchpad.net/mysql-replication-listener</a>　にあります</p>

<pre><code>$ bzr branch lp:mysql-replication-listener
$ cd mysql-replication-listener/
$ cmake .
$ make -j4
$ sudo make install
</code></pre>

<p>最後にreplication_boosterのビルド</p>

<pre><code>$ git clone git://github.com/yoshinorim/replication-booster-for-mysql.git
$ cd replication-booster-for-mysql/
$ cmake .
$ make
</code></pre>

<p>検証したサーバではMySQLが通常とは異なるパスに入っていたので、CMakeLists.txtをごにょごにょしました。あと今回は検証目的なので、replication_boosterコマンドはmake installしていません。</p>

<p>コマンドのヘルプ</p>

<pre><code>$ ./replication_booster --help
Usage: 
 replication_booster [OPTIONS]

Example: 
 replication_booster --user=mysql_select_user --password=mysql_select_pass --admin_user=mysql_root_user --admin_password=mysql_root_password --socket=/tmp/mysql.sock 

Options (short name):
 -t, --threads=N                :Number of worker threads. Each worker thread converts binlog events and executes SELECT statements. Default is 10 (threads).
 -o, --offset-events=N          :Number of binlog events that main thread (relay log reader thread) skips initially when reading relay logs. This number should be high when you have faster storage devices such as SSD. Default is 500 (events).
 -s, --seconds-prefetch=N       :Main thread stops reading relay log events when the event's timestamp is --seconds-prefetch seconds ahead of current SQL thread's timestamp. After that the main thread starts reading relay logs from SQL threads's position again. If this value is too high, worker threads will execute many more SELECT statements than necessary. Default value is 3 (seconds).
 -m, --millis-sleep=N           :If --seconds-prefetch condition is met, main thread sleeps --millis-sleep milliseconds before starting reading relay log. Default is 10 milliseconds.
 -u, --user=mysql_user          :MySQL slave user name. This user should have at least SELECT privilege on all application tables (default: root)
 -p, --password=mysql_pwd       :MySQL slave password (default: empty)
 -a, --admin_user=mysql_user    :MySQL administration user for the slave. This user should have at least SUPER and REPLICATION CLIENT (for SHOW SLAVE STATUS) privileges. (default: root)
 -b, --admin_password=mysql_pwd :MySQL password for the administration user. (default:empty)
 -h, --host=mysql_host          :MySQL slave hostname or IP address. This must be local address. (default: localhost)
 -P, --port=mysql_port          :MySQL slave port number (default:3306)
 -S, --socket=mysql_socket      :MySQL socket file path
</code></pre>

<h2>Replication Booster の実行と結果</h2>

<p>replication_boosterは以下のように実行しました</p>

<pre><code>$ ./replication_booster  -t 8 -o 10 -u booster -p xxxxxxx -a booster -p xxxxxxx -h 127.0.0.1
</code></pre>

<p>スレッドは8個、relay-logはあまりskipせずに頻繁にクエリを実行させてみました。</p>

<p>結果は以下</p>

<p><span class="mt-enclosure mt-enclosure-image" style="display: inline;"><a href="http://blog.nomadscafe.jp/2012/03/23/replicationbooster.png"><img alt="replicationbooster.png" src="http://blog.nomadscafe.jp/assets_c/2012/03/replicationbooster-thumb-650x330-140.png" width="650" height="330" class="mt-image-center" style="text-align: center; display: block; margin: 0 auto 20px;" /></a></span></p>

<p>グラフは一つ前のエントリ「<a href="http://blog.nomadscafe.jp/2012/03/cloudforecastmysql.html">CloudForecastでMySQLのレプリケーション監視</a>」によるもの。上が秒数、下がバイナリログのポジション。明らかな差はでてないですが、Seconds Behind Masterの平均を取ると、導入前が「0.5」、実行後は「0.2」と遅延幅は小さくなり、レプリケーション遅延のアラートの回数も減りました。</p>

<p>今後は別のサーバも含めてもうちょっと検証していくつもり</p>
]]>
        

    </content>
<feedburner:origLink>http://blog.nomadscafe.jp/2012/03/replication-booster-for-mysql.html</feedburner:origLink></entry>

<entry>
    <title>CloudForecastでMySQLのレプリケーション監視</title>
    <link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/nomadscafejp/~3/t3etAfPXIjU/cloudforecastmysql.html" />
    <id>tag:blog.nomadscafe.jp,2012://1.165</id>

    <published>2012-03-14T08:12:23Z</published>
    <updated>2012-03-14T08:36:31Z</updated>

    <summary>MySQLのレプリケーションの遅延状況を取得するプラグインをCloudForec...</summary>
    <author>
        <name>Masahiro Nagano</name>
        <uri>http://blog.nomadscafe.jp/</uri>
    </author>
    
    
    <content type="html" xml:lang="ja" xml:base="http://blog.nomadscafe.jp/">
        <![CDATA[<p>MySQLのレプリケーションの遅延状況を取得するプラグインをCloudForecast本体に追加しました。</p>

<p>host_configで</p>

<pre><code>---
component_config:
resources:
  - traffic:eth0
  - traffic:eth1
  - basic
  - mysql
  - innodb
  - mysqlreplication
</code></pre>

<p>このように追加すれば使えます。</p>

<p>作られるグラフな下のような感じで、秒数による表示とバイナリログのポジションの2つが作成されます。</p>

<p><span class="mt-enclosure mt-enclosure-image" style="display: inline;"><a href="http://blog.nomadscafe.jp/2012/03/14/cloudforecast_mysqlreplication.png"><img alt="cloudforecast_mysqlreplication.png" src="http://blog.nomadscafe.jp/assets_c/2012/03/cloudforecast_mysqlreplication-thumb-600x263-136.png" width="600" height="263" class="mt-image-center" style="text-align: center; display: block; margin: 0 auto 20px;" /></a></span></p>

<p>グラフは某サービスの実際のレプリケーション遅延状況です。データが非常に多いので結構遅れてる事が多いですね。蛇足ですがMySQL4系の場合は「Seconds Behind Master」がないので秒数は常に0になります^^</p>

<p>大きくレプリケーションが遅延するタイミングがあれば、その際に発行されているクエリを見直してみる等の用途にこのグラフが使えるでしょうか。</p>
]]>
        

    </content>
<feedburner:origLink>http://blog.nomadscafe.jp/2012/03/cloudforecastmysql.html</feedburner:origLink></entry>

<entry>
    <title>「Webエンジニアのためのデータベース技術[実践]入門」を読みました </title>
    <link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/nomadscafejp/~3/3h5gTQP0nCI/web-2.html" />
    <id>tag:blog.nomadscafe.jp,2012://1.164</id>

    <published>2012-03-07T07:15:30Z</published>
    <updated>2012-03-07T07:22:08Z</updated>

    <summary>「Webエンジニアのためのデータベース技術[実践]入門」を技術評論社様から献本頂...</summary>
    <author>
        <name>Masahiro Nagano</name>
        <uri>http://blog.nomadscafe.jp/</uri>
    </author>
    
    
    <content type="html" xml:lang="ja" xml:base="http://blog.nomadscafe.jp/">
        <![CDATA[<p>「Webエンジニアのためのデータベース技術[実践]入門」を技術評論社様から献本頂きました。ありがとうございます。</p>

<p><span class="mt-enclosure mt-enclosure-image" style="display: inline;"><a href="http://blog.nomadscafe.jp/2012/03/07/mysql.jpg"><img alt="mysql.jpg" src="http://blog.nomadscafe.jp/assets_c/2012/03/mysql-thumb-600x450-134.jpg" width="600" height="450" class="mt-image-center" style="text-align: center; display: block; margin: 0 auto 20px;" /></a></span></p>

<p>このblogで張ってるAmazonのアフィリエイトリンクを通して一番売れている本は、写真奥に写っていますが、同じく松信さんによる「<a href="http://www.amazon.co.jp/gp/product/4798120723/ref=as_li_ss_tl?ie=UTF8&amp;tag=nomadscafejp-22&amp;linkCode=as2&amp;camp=247&amp;creative=7399&amp;creativeASIN=4798120723">Linux-DB システム構築/運用入門 (DB Magazine SELECTION)</a><img src="http://www.assoc-amazon.jp/e/ir?t=nomadscafejp-22&amp;l=as2&amp;o=9&amp;a=4798120723" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" />
」です。</p>

<p>blog記事の中やWebアプリケーションエンジニアにお勧めの本を聞かれる度に紹介してきましたが、大体「前半の高可用性の所は呼び飛ばしてもいいよ」という若干残念な一言を加えていました。この「Webエンジニアのためのデータベース技術[実践]入門」ではその心配がなくなって一安心です</p>

<p>Webアプリケーションエンジニアがデータベースを使う上で押さえておいて欲しいポイントは以下の３つだと個人的に考えています</p>

<ul>
<li>インデックスの構造についてビジュアライズして想像できること</li>
<li>データの量と性能に関する感覚</li>
<li>ハードウェアのトレンド、メモリ・SSD・CPU・クラウド</li>
</ul>

<p>上述の「Linux-DB システム構築/運用入門」でもこれらの点が丁寧に紹介されていて、非常に参考となりました。</p>

<p>今回の「Webエンジニアのためのデータベース技術[実践]入門」の内容は、インデックス、テーブル設計、SQL、トランザクションといった基礎知識をおさえつつ、DeNAの現場で使われているだろう可用性、レプリケーション、ハードウェアのトレンド、性能改善手法といった実践的な事柄が網羅的に取り上げられています。</p>

<p>データベースの基礎と実践を押さえることができる良本として、これからオススメしていきます。</p>

<iframe src="http://rcm-jp.amazon.co.jp/e/cm?lt1=_blank&bc1=000000&IS2=1&nou=1&bg1=FFFFFF&fc1=000000&lc1=0000FF&t=nomadscafejp-22&o=9&p=8&l=as4&m=amazon&f=ifr&ref=ss_til&asins=4774150207" style="width:120px;height:240px;" scrolling="no" marginwidth="0" marginheight="0" frameborder="0"></iframe>
]]>
        

    </content>
<feedburner:origLink>http://blog.nomadscafe.jp/2012/03/web-2.html</feedburner:origLink></entry>

<entry>
    <title>最新のログファイルにリンクが作れるようになった Apache 2.4.1 の rotatelogs を試す</title>
    <link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/nomadscafejp/~3/Nq8P5saETHw/-apache-241-rotatelogs.html" />
    <id>tag:blog.nomadscafe.jp,2012://1.163</id>

    <published>2012-03-01T09:57:53Z</published>
    <updated>2012-03-01T10:01:53Z</updated>

    <summary>この機能欲しかったんだよねー。 CustomLogで、pipeしてrotatel...</summary>
    <author>
        <name>Masahiro Nagano</name>
        <uri>http://blog.nomadscafe.jp/</uri>
    </author>
    
    
    <content type="html" xml:lang="ja" xml:base="http://blog.nomadscafe.jp/">
        <![CDATA[<p>この機能欲しかったんだよねー。</p>

<p>CustomLogで、pipeしてrotatelogsを使ってログ分割を行う場合、</p>

<pre><code>CustomLog "|/path/to/rotatelogs /path/to/log/access_log.%Y%m%d%H 7200 540"
</code></pre>

<p>ログファイルは、</p>

<pre><code>$ ls -1
access_log.2012030116
access_log.2012030118
</code></pre>

<p>の様に最新のファイルが変更になります。tail -f で追いかけていた場合は、途中でファイルを手動で切り替えないとならないのでとても不便です。また、fluentdのtail pluginも利用できません。</p>

<p>Apache 2.4.1 のrotatelogsでは、最新のファイルに対してハードリンクを張る機能が追加されたので、とりあえず試してみました。</p>

<pre><code>$ wget http://ftp.riken.jp/net/apache//httpd/httpd-2.4.1.tar.gz
$ tar zxf httpd-2.4.1.tar.gz
$ cd httpd-2.4.1
</code></pre>

<p>2.4.1のソースコード中にはapr,apr-utilが含まれていないのでこちらもダウンロード</p>

<pre><code>$ cd srclib
$ wget http://ftp.kddilabs.jp/infosystems/apache//apr/apr-1.4.6.tar.gz \
    http://ftp.kddilabs.jp/infosystems/apache//apr/apr-util-1.4.1.tar.gz       
$ tar zxf apr-1.4.6.tar.gz
$ tar zxf apr-util-1.4.1.tar.gz  
$ mv apr-1.4.6 apr
$ mv apr-util-1.4.1 apr-util
$ cd ..
</code></pre>

<p>そしてbuild。</p>

<pre><code>$ ./configure --prefix=/path/to/httpd24 --enable-static-rotatelogs --with-included-apr
$ make
</code></pre>

<p>enable-static-rotatelogs を使うと、依存の無い(コピーして使える) rotatelogs コマンドが作れるので便利です。今回は rotatelogs を試したいだけなので、make install はしません</p>

<p>ビルドできたらrotatelogsを試しに実行</p>

<pre><code>./support/rotatelogs 
Incorrect number of arguments
Usage: ./support/rotatelogs [-v] [-l] [-L linkname] [-p prog] [-f] [-t] [-e] [-c] &lt;logfile&gt; {&lt;rotation time in seconds&gt;|&lt;rotation size&gt;(B|K|M|G)} [offset minutes from UTC]

Add this:

TransferLog "|./support/rotatelogs /some/where 86400"

or 

TransferLog "|./support/rotatelogs /some/where 5M"

to httpd.conf. By default, the generated name will be
&lt;logfile&gt;.nnnn where nnnn is the system time at which the log
nominally starts (N.B. if using a rotation time, the time will
always be a multiple of the rotation time, so you can synchronize
cron scripts with it). If &lt;logfile&gt; contains strftime conversion
specifications, those will be used instead. At the end of each
rotation time or when the file size is reached a new log is
started.

Options:
  -v       Verbose operation. Messages are written to stderr.
  -l       Base rotation on local time instead of UTC.
  -L path  Create hard link from current log to specified path.
  -p prog  Run specified program after opening a new log file. See below.
  -f       Force opening of log on program start.
  -t       Truncate logfile instead of rotating, tail friendly.
  -e       Echo log to stdout for further processing.
  -c       Create log even if it is empty.

The program is invoked as "[prog] &lt;curfile&gt; [&lt;prevfile&gt;]"
where &lt;curfile&gt; is the filename of the newly opened logfile, and
&lt;prevfile&gt;, if given, is the filename of the previously used logfile.
</code></pre>

<p>「-L」オプションがどうやらそれの様子。</p>

<p>次にログを rotatelogs にログを渡す perl スクリプトを作って実際に切り替わるか試してみます。</p>

<pre><code>#!/usr/bin/perl

use strict;
use warnings;

open(my $fh, '|-:unix', './support/rotatelogs',
    '-L','./log/access_log.link',
    './log/access_log.%Y%m%d%H%M','60','540') or die $!;
while(1){
    print $fh "[".localtime()."] foo bar\n";
    sleep 1;
}
</code></pre>

<p>上記のスクリプトでは access_log.%Y%m%d%H%M がログファイルで、access_log.link　をリンクとして、60秒ごとにログファイルを切り替えるようにrotatelogsを起動し、1秒ごとにログを出力します。</p>

<p>こいつをtest.plとして保存し実行して、</p>

<pre><code>$ perl test.pl
</code></pre>

<p>ログを tail -F(大文字) で観察。</p>

<pre><code>$ tail -F log/access_log.link
[Thu Mar  1 18:21:55 2012] foo bar
[Thu Mar  1 18:21:56 2012] foo bar
[Thu Mar  1 18:21:57 2012] foo bar
[Thu Mar  1 18:21:58 2012] foo bar
[Thu Mar  1 18:21:59 2012] foo bar
tail: `access_log.link' has been replaced;  following end of new file
[Thu Mar  1 18:22:00 2012] foo bar
[Thu Mar  1 18:22:01 2012] foo bar
[Thu Mar  1 18:22:02 2012] foo bar
[Thu Mar  1 18:22:03 2012] foo bar
</code></pre>

<p>59秒と00秒の間で切り替わりました。よさそう。</p>

<p>staticでビルドすれば、Apache 2.4系を使わなくても rotatelogsだけコピーして、2.2系のサーバに使ったりできるので、今すぐにでもrotatelogsでローカルのログを管理しつつ、fluentも使うことができるんじゃないでしょうか。</p>
]]>
        

    </content>
<feedburner:origLink>http://blog.nomadscafe.jp/2012/03/-apache-241-rotatelogs.html</feedburner:origLink></entry>

<entry>
    <title>GrowthForecastに1分更新グラフ作成とサマリーなどのJSONフォーマットでの出力機能追加</title>
    <link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/nomadscafejp/~3/Q8X-GuonVXY/growthforecast1json.html" />
    <id>tag:blog.nomadscafe.jp,2012://1.162</id>

    <published>2012-02-21T03:25:24Z</published>
    <updated>2012-02-21T03:33:22Z</updated>

    <summary>「GrowthForecastというグラフ表示ツールで捗る話」で紹介したGrow...</summary>
    <author>
        <name>Masahiro Nagano</name>
        <uri>http://blog.nomadscafe.jp/</uri>
    </author>
    
    
    <content type="html" xml:lang="ja" xml:base="http://blog.nomadscafe.jp/">
        <![CDATA[<p>「<a href="http://blog.nomadscafe.jp/2011/12/growthforecast.html">GrowthForecastというグラフ表示ツールで捗る話</a>」で紹介したGrowthForecastですが、モリス氏の<a href="http://d.hatena.ne.jp/tagomoris/20120205/1328455394">fluent meetupでの発表</a>やriywo氏の<a href="http://blog.riywo.com/2012/02/17/173640">発表</a>で少し紹介されていたりするわけですが、社内でも少しずつメトリクスが増えて活用されています。</p>

<p>データが既に入っているので大きな変更はできないのですが、少し機能追加をしています。</p>

<p>ソースコード<br />
<a href="https://github.com/kazeburo/GrowthForecast">https://github.com/kazeburo/GrowthForecast</a></p>

<h2>1分更新グラフ</h2>

<p>GrowthForecast はWebアプリケーションとWorkerの2つから構成されています。APIに対してPOSTされたデータは、一旦SQLiteのDBに格納され、Workerがそれを取得、RRDファイルを更新します。これまでは5分毎に動くworkerがいるだけでしたが、そこに1分毎動くworkerを追加しました。</p>

<p><span class="mt-enclosure mt-enclosure-image" style="display: inline;"><img alt="growthforecast_two_workers.png" src="http://blog.nomadscafe.jp/2012/02/21/growthforecast_two_workers.png" width="615" height="254" class="mt-image-center" style="text-align: center; display: block; margin: 0 auto 20px;" /></span></p>

<p>上の図のようにworkerが2つ動くようになりました。すぐに結果が知りたいとき、5分よりも高い解像度でデータがみたいときに利用できます。</p>

<p>画面では、とりあえず感が強いけどボタンが追加されています。</p>

<p><span class="mt-enclosure mt-enclosure-image" style="display: inline;"><img alt="growthforecast-1min.png" src="http://blog.nomadscafe.jp/2012/02/21/growthforecast-1min.png" width="599" height="197" class="mt-image-center" style="text-align: center; display: block; margin: 0 auto 20px;" /></span></p>

<p>デフォルトでこの機能が有効になっていますが、以下のように起動することで、これまでと同じ5分更新だけにできます</p>

<pre><code>$ perl growthforecast.pl --disable-1min-metrics
</code></pre>

<h2>JSONフォーマットでの出力機能追加</h2>

<p>これまでGrowthForecastはデータを溜め込んでグラフとして表示するだけでしたが、これだけではせっかく貯めたデータの再利用ができず捗りません。そこでRRDファイルに保存されているデータをJSONにて出力できるAPIを作りました。</p>

<p>APIは2種類。1つ目はグラフの凡例にでているサマリー取得API</p>

<pre><code>$ curl http://example.com/summary/path/to/graph
{"graph":["0.00000000","0.09241971","5.91815168","0.00000000"]}
</code></pre>

<p>レスポンスのデータは指定期間中の「現在」「平均」「最大」「最小」となります。</p>

<p>もう一つのAPIは、グラフデータのエクスポート。</p>

<pre><code>$ curl http://gf.ops.dev.livedoor.net/xport/path/to/graph?t=d&amp;gmode=subtract
{"column_names":["graph"],"step":600,"columns":1,"end_timestamp":1329790200,"start_timestamp":1329671400,"rows":[[0.499581818333333],[0],[0],[0],[0],[0],[0],[0],[0],...,[null]]}
</code></pre>

<p>このようにグラフを描く際に使う値が取得できます。２つのAPIは複合グラフでも利用可能です。</p>

<p>これらのAPIを使って、以下のような監視をすることを妄想中です。</p>

<p><span class="mt-enclosure mt-enclosure-image" style="display: inline;"><img alt="fluent-growthforecast-monitoring.png" src="http://blog.nomadscafe.jp/2012/02/21/fluent-growthforecast-monitoring.png" width="698" height="94" class="mt-image-center" style="text-align: center; display: block; margin: 0 auto 20px;" /></span></p>

<p>アクセスログからステータスコードごとにまとめて件数をGrowthForecastになげて、エラーの割合が一定を超えたらアラートを出すとか、ツールを連携させることで実現できそうですね</p>

<p>合わせて読みたい</p>

<p><a href="http://d.hatena.ne.jp/tagomoris/20120218/1329558305">fluent-plugin-flowcounter および fluent-plugin-growthforecast released! #fluentd - tagomorisのメモ置き場</a></p>
]]>
        

    </content>
<feedburner:origLink>http://blog.nomadscafe.jp/2012/02/growthforecast1json.html</feedburner:origLink></entry>

<entry>
    <title>Apache 2.2.15から入った mod_reqtimeout を Reverse Proxyで使う場合の注意点</title>
    <link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/nomadscafejp/~3/wTAAnS29YiE/apache-2215-mod-reqtimeout-reverse-proxy.html" />
    <id>tag:blog.nomadscafe.jp,2012://1.161</id>

    <published>2012-02-16T02:59:19Z</published>
    <updated>2012-02-16T03:04:12Z</updated>

    <summary>slowloris対策として、Apacheの2.1.15から入ったモジュールにm...</summary>
    <author>
        <name>Masahiro Nagano</name>
        <uri>http://blog.nomadscafe.jp/</uri>
    </author>
    
    
    <content type="html" xml:lang="ja" xml:base="http://blog.nomadscafe.jp/">
        <![CDATA[<p>slowloris対策として、Apacheの2.1.15から入ったモジュールにmod_reqtimeoutというのがあります。</p>

<pre><code>RequestReadTimeout header=10 body=30
</code></pre>

<p>このように設定することで、headerの受信が10秒以内、bodyの受信が30秒以内に完了しない場合、「408」エラーとできます。簡単で便利そうですね</p>

<p>公式ドキュメント
<a href="http://httpd.apache.org/docs/2.2/en/mod/mod_reqtimeout.html">http://httpd.apache.org/docs/2.2/en/mod/mod_reqtimeout.html</a></p>

<p><br />
<strong>ただし、</strong>
<br />
<br /></p>

<p>Apacheをreverse proxyとして使用している場合はTimeoutにならず、リクエストの一部がproxy先に送られるという問題があるので注意が必要というか、はまったのでその話。</p>

<p>ちなみに、すでにBugzillaには上がっているけど、2.2系ではまだ対応完了してない
<a href="https://issues.apache.org/bugzilla/show_bug.cgi?id=51103">https://issues.apache.org/bugzilla/show_bug.cgi?id=51103</a></p>

<p><br />
<br />
<br />
以下再現方法。</p>

<p>まず、reverse proxy側で RequestReadTimeout と ProxyPass を設定します</p>

<pre><code>RequestReadTimeout header=10 body=5
ProxyPass /backend http://127.0.0.1:5000
</code></pre>

<p>そして、backendとなるStarletには以下のpatchをあてます。</p>

<pre><code>diff --git a/lib/Starlet/Server.pm b/lib/Starlet/Server.pm
index ea5a34a..f99f360 100644
--- a/lib/Starlet/Server.pm
+++ b/lib/Starlet/Server.pm
@@ -17,6 +17,8 @@ use Socket qw(IPPROTO_TCP TCP_NODELAY);
 use Try::Tiny;
 use Time::HiRes qw(time);

+use Log::Minimal;
+
 use constant MAX_REQUEST_SIZE =&gt; 131072;
 use constant MSWin32          =&gt; $^O eq 'MSWin32';

@@ -155,6 +157,10 @@ sub handle_connection {
         undef $can_exit;
         my $reqlen = parse_http_request($buf, $env);
         if ($reqlen &gt;= 0) {
+            {
+                local $Log::Minimal::AUTODUMP=1;
+                warnf $env;
+            }
             # handle request
             if ($use_keepalive) {
                 if (my $c = $env-&gt;{HTTP_CONNECTION}) {
@@ -177,6 +183,7 @@ sub handle_connection {
                             $conn, \$chunk, $cl, 0, $self-&gt;{timeout})
                             or return;
                     }
+warnf "chunk size: %d", length($chunk);
                     $buffer-&gt;print($chunk);
                     $cl -= length $chunk;
                 }
</code></pre>

<p>関係ないけどStarletはシンプルなのでこういう検証に便利ですね</p>

<p>そしてtest用のpsgiアプリケーションを書いて</p>

<pre><code>use Plack::Request;

sub {
    my $env = shift;
    my $req = Plack::Request-&gt;new($env);
    my $content = $req-&gt;content();
    my $length = length($content);
    [200,['Content-Type'=&gt;'text/plain','Content-Length'=&gt;length($length)+1],["$length\n"]];
};
</code></pre>

<p>起動、</p>

<pre><code>$ plackup -s Starlet test.psgi
</code></pre>

<p>今度は、Bodyを送るのに5秒以上掛かるように細工したクライアントを書きます</p>

<pre><code>#!/usr/bin/perl

use strict;
use warnings;
use IO::Socket::INET;

my $sock = IO::Socket::INET-&gt;new(
    PeerAddr =&gt; '127.0.0.1',
    PeerPort =&gt; '8080',
);

my $header = &lt;&lt;EOF;
POST /backend/foo HTTP/1.1
Host: example.com
User-Agent: slowpost
Content-Length: 32768
EOF

$header =~ s/\n/\r\n/g;

$sock-&gt;syswrite("$header\r\n");

for (1..8) {
    $sock-&gt;syswrite("1"x4096);
    sleep 1;
}

my $res;
$sock-&gt;sysread( $res, 8192 );

print $res;
</code></pre>

<p>これを使って、Apacheに向けてリクエストを送ってみると、、</p>

<pre><code>$ perl slowpost.pl
HTTP/1.1 400 Bad Request
Date: Thu, 16 Feb 2012 02:28:51 GMT
Content-Length: 226
Connection: close
Content-Type: text/html; charset=iso-8859-1

&lt;!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"&gt;
&lt;html&gt;&lt;head&gt;
&lt;title&gt;400 Bad Request&lt;/title&gt;
&lt;/head&gt;&lt;body&gt;
&lt;h1&gt;Bad Request&lt;/h1&gt;
&lt;p&gt;Your browser sent a request that this server could not understand.&lt;br /&gt;
&lt;/p&gt;
&lt;/body&gt;&lt;/html&gt;
</code></pre>

<p>Bodyの送信に5秒以上掛かっているので、期待としては「408」が返って来そうですが「400」となりました。</p>

<p>Apacheのエラーログには</p>

<pre><code>[Thu Feb 16 11:28:56 2012] [error] proxy: pass request body failed to 127.0.0.1:5000 (127.0.0.1) from 127.0.0.1 ()
</code></pre>

<p>このように記録されています。</p>

<p>ふむふむ、bodyのproxyに失敗しただけかと思うのですが、実際にはbodyも一部だけがproxyされ途中で切れています。</p>

<p>以下がplackupを起動したターミナルの出力</p>

<pre><code>2012-02-16T11:28:54 [WARN] {'psgi.multiprocess' =&gt; 1,'SCRIPT_NAME' =&gt; '','SERVER_NAME' =&gt; 0,'HTTP_CONNECTION' =&gt; 'close','HTTP_X_FORWARDED_SERVER' =&gt; 'kazeburomba.local','PATH_INFO' =&gt; '/foo','CONTENT_LENGTH' =&gt; '32768','REQUEST_METHOD' =&gt; 'POST','psgi.multithread' =&gt; '','HTTP_USER_AGENT' =&gt; 'slowpost','QUERY_STRING' =&gt; '','REMOTE_PORT' =&gt; 55700,'SERVER_PORT' =&gt; 5000,'psgix.input.buffered' =&gt; 1,'REMOTE_ADDR' =&gt; '127.0.0.1','HTTP_X_FORWARDED_HOST' =&gt; 'example.com','SERVER_PROTOCOL' =&gt; 'HTTP/1.1','HTTP_X_FORWARDED_FOR' =&gt; '127.0.0.1','psgi.streaming' =&gt; 1,'psgi.errors' =&gt; *::STDERR,'REQUEST_URI' =&gt; '/foo','psgi.version' =&gt; [1,1],'psgi.nonblocking' =&gt; '','psgix.io' =&gt; bless( \*Symbol::GEN1, 'IO::Socket::INET' ),'psgi.url_scheme' =&gt; 'http','psgi.run_once' =&gt; '','HTTP_HOST' =&gt; '127.0.0.1:5000'} at /Users/hoge/perl5/perlbrew/perls/perl-5.12.2/lib/site_perl/5.12.2/Starlet/Server.pm line 162
2012-02-16T11:28:54 [WARN] chunk size:16384 at /Users/hoge/perl5/perlbrew/perls/perl-5.12.2/lib/site_perl/5.12.2/Starlet/Server.pm line 184
</code></pre>

<p>ヘッダも送られてきて、Bodyも16384byte読み込んでいます。</p>

<p>実際、StarletではBody部分がContent-Lengthの長さにならなければリクエストの処理に移らないので、問題とはならないのですが、Content-Lengthと異なっていても正常に処理をしてしまった場合(libapreqを使うmod_perlがそうだった気がする)問題となる可能性があります。</p>

<p>こわいこわい</p>

<p>おそらく原因としては、mod_reqtimeoutもmod_proxyもfilterで動いていて、reqtimeoutがエラーと判断した時にはすでにmod_proxyがbackendにコンテンツを送ってしまっていて、mod_proxyでリクエストのBofyの続きが読めなくなって「400」エラーって事なんだろうなぁと思ってます。</p>

<p>対策としては、RequestReadTimeout を使わない、もしくは使う場所を限定する（アップロードなど通信時間長くなるところには使わない)、Content-LengthやChunked-Transferを正しく扱うサーバを使ってのが考えられます。</p>

<p>そういえば昔、同じような問題に、<a href="http://httpd.apache.org/docs/2.2/en/mod/core.html#limitrequestbody">LimitRequestBody</a>でもあたったことがあって、ApacheのFilter機構は難しいなぁと思った次第。</p>
]]>
        

    </content>
<feedburner:origLink>http://blog.nomadscafe.jp/2012/02/apache-2215-mod-reqtimeout-reverse-proxy.html</feedburner:origLink></entry>

<entry>
    <title>nginx-1.1.x で httpなupstreamにもkeepaliveができるようになったので検証してみた</title>
    <link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/nomadscafejp/~3/eOsImjQa47I/nginx-11x-httpupstreamkeepalive.html" />
    <id>tag:blog.nomadscafe.jp,2012://1.160</id>

    <published>2012-02-15T07:48:52Z</published>
    <updated>2012-02-15T08:01:05Z</updated>

    <summary>画像配信など大量にアクセスを捌く際にちょっと気になっていたhttpなupstre...</summary>
    <author>
        <name>Masahiro Nagano</name>
        <uri>http://blog.nomadscafe.jp/</uri>
    </author>
    
    
    <content type="html" xml:lang="ja" xml:base="http://blog.nomadscafe.jp/">
        <![CDATA[<p>画像配信など大量にアクセスを捌く際にちょっと気になっていたhttpなupstreamとkeepaliveできない件が、nginx-1.1系でできるようになったので試してみた</p>

<p><span class="mt-enclosure mt-enclosure-image" style="display: inline;"><img alt="nginx11keepalive.png" src="http://blog.nomadscafe.jp/2012/02/15/nginx11keepalive.png" width="478" height="101" class="mt-image-center" style="text-align: center; display: block; margin: 0 auto 20px;" /></span></p>

<p>今回keepaliveできるようになったのは↑のbackendと通信するところ。</p>

<p>本家のドキュメントはこちら<br />
<a href="http://nginx.org/en/docs/http/ngx_http_upstream_module.html#keepalive">http://nginx.org/en/docs/http/ngx<em>http</em>upstream_module.html#keepalive
</a></p>

<p>keepalive機能を使うには、以下のように設定します</p>

<pre><code>http {
    upstream backend {
        server 127.0.0.1:5000;
        keepalive 16;
    }
    server {
        listen       8080;
        server_name  localhost;
        location / {
            proxy_http_version 1.1;
            proxy_set_header Connection ";
            proxy_pass http://backend;
        }
    }
}
</code></pre>

<p>upstreamブロックにkeepaliveで、保持するコネクション数を指定し、proxy_http_version で「1.1」を(1.0については後述)、proxy_set_headerでConnectionヘッダを削除します。削除しなければならないのは、nginxがデフォルトで「close」を設定するからです。これで http://127.0.0.1:5000/ への通信がkeepalive可能になるはずです。</p>

<p>確認には、HTTP/1.1をサポートしたStarmanに、以下のpatchをあてて利用します</p>

<pre><code>diff --git a/lib/Starman/Server.pm b/lib/Starman/Server.pm
index 67d1528..137972a 100644
--- a/lib/Starman/Server.pm
+++ b/lib/Starman/Server.pm
@@ -175,6 +175,7 @@ sub process_request {

         my $env = {
             REMOTE_ADDR     =&gt; $self-&gt;{server}-&gt;{peeraddr},
+            REMOTE_PORT     =&gt; $self-&gt;{server}-&gt;{peerport},
             REMOTE_HOST     =&gt; $self-&gt;{server}-&gt;{peerhost} || $self-&gt;{server}-&gt;{peeraddr},
             SERVER_NAME     =&gt; $self-&gt;{server}-&gt;{sockaddr}, # XXX: needs to be resolved?
             SERVER_PORT     =&gt; $self-&gt;{server}-&gt;{sockport},
</code></pre>

<p>そして用意したPSGIアプリケーションが以下。</p>

<pre><code>sub {
    my $env = shift;
    my $port = $env-&gt;{REMOTE_PORT};
    [200,['Content-Type'=&gt;'text/plain','Content-Length'=&gt;length($port)+1],["$port\n"]];
};
</code></pre>

<p>keepaliveが有効であれば、backend側でのREMOTE_PORTは変わらないはずです。試しにstarmanを起動し、直接アクセスします。</p>

<pre><code>$ starman --preload-app test.psgi
$ curl http://localhost:5000/{1..5}
55075
55075
55075
55075
55075
</code></pre>

<p>curlもHTTP/1.1 keepaliveをサポートするので、全て同じポート番号が戻ります。</p>

<p>次に nginx を起動して試してみます</p>

<pre><code>$ ./sbin/nginx
$ curl http://localhost:8080/{1..5}
55099
55099
55099
55099
55099
</code></pre>

<p>おぉ。同じ番号になってます。keepaliveできてそう。</p>

<p>確認のために、nginx側でkeepaliveに関する設定を削除してみると、</p>

<pre><code>$ curl http://localhost:8080/{1..5}
55233
55234
55235
55236
55237
</code></pre>

<p>このようにポート番号が次々に変わって行きます。keepaliveが有効になっているのがわかりますね！</p>

<p>ここまで検証してサービスで使えそう！と思ったのですが、今回使う事を考えていた画像配信で使っているSquidは残念なことに、HTTP/1.1 keepaliveをサポートしていません。Connectionヘッダにkeep-aliveを入れてリクエストをするHTTP/1.0が必要となります。</p>

<p>そこで、nginxの設定を</p>

<pre><code>server {
    listen       8080;
    server_name  localhost;
    location / {
        proxy_set_header Connection "keep-alive";
        proxy_pass http://backend;
    }
}
</code></pre>

<p>このように変更し試してみましたが、keepaliveされませんでした。そこでnginxのソースコードを追って行くと、HTTP/1.1でない場合はコネクションを必ずcloseしてしまうようになってたので、レスポンスのConnectionヘッダに「keep-alive」が入っていた場合に、切断しないようにする以下のpatchをあてました。</p>

<pre><code>--- nginx-1.1.14.orig/src/http/ngx_http_upstream.c      2012-01-19 00:07:43.000000000 +0900
+++ nginx-1.1.14/src/http/ngx_http_upstream.c   2012-02-09 17:11:02.000000000 +0900
@@ -3426,6 +3426,13 @@
         r-&gt;upstream-&gt;headers_in.connection_close = 1;
     }

+    if (ngx_strlcasestrn(h-&gt;value.data, h-&gt;value.data + h-&gt;value.len,
+                         (u_char *) "keep-alive", 10 - 1)
+        != NULL)
+    {
+        r-&gt;upstream-&gt;headers_in.connection_close = 0;
+    }
+
     return NGX_OK;
 }
</code></pre>

<p>これでHTTP/1.0でもkeepaliveできるはずなので、今度はHTTP/1.1をサポートしないStarletを使って検証します。Starmanと同じくREMOTE_PORTが取れるようpatchをあてます</p>

<pre><code>diff --git a/lib/Starlet/Server.pm b/lib/Starlet/Server.pm
index ea5a34a..63b54f2 100644
--- a/lib/Starlet/Server.pm
+++ b/lib/Starlet/Server.pm
@@ -106,6 +106,7 @@ sub accept_loop {
                     SERVER_NAME =&gt; $self-&gt;{host},
                     SCRIPT_NAME =&gt; '',
                     REMOTE_ADDR =&gt; $conn-&gt;peerhost,
+                    REMOTE_ADDR =&gt; $conn-&gt;peerport,
                     'psgi.version' =&gt; [ 1, 1 ],
                     'psgi.errors'  =&gt; *STDERR,
                     'psgi.url_scheme' =&gt; 'http',
</code></pre>

<p>これで、plackup使いStarletサーバを起動し検証します。</p>

<pre><code>$ plackup -s Starlet --max-keepalive-reqs=1000 test.psgi
$ curl http://localhost:8080/{1..5}
55961
55961
55961
55961
55961
</code></pre>

<p>同じポート番号がならんだので、keepaliveできてそう！やった！！１</p>

<p>最後に、参考程度にMacBookAir上でApacheBenchを使ってベンチマークとって見ました</p>

<pre><code>$ ab -k -c 10 -n 5000 http://localhost:8080/

# keepaliveなし
Requests per second:    1910.71 [#/sec] (mean)
# keepaliveあり
Requests per second:    2935.73 [#/sec] (mean)
</code></pre>

<p>通常のアプリケーションサーバでは接続数が問題となるので、使わないと思いますが、後ろの接続数が問題とならない特定の用途では使えます。<br />
ということで、既に画像配信の本番サーバにもpatchあてたnginx-1.1系が投入されています。多少CPU使用率が減った気がします。</p>
]]>
        

    </content>
<feedburner:origLink>http://blog.nomadscafe.jp/2012/02/nginx-11x-httpupstreamkeepalive.html</feedburner:origLink></entry>

<entry>
    <title>hb qp bp study 新年LT&amp;ビアバッシュ2012に参加してビール飲んでピザ食べてきた</title>
    <link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/nomadscafejp/~3/V3OYELX7QfM/hb-qp-bp-study-lt2012.html" />
    <id>tag:blog.nomadscafe.jp,2012://1.159</id>

    <published>2012-01-15T14:44:58Z</published>
    <updated>2012-01-15T14:51:25Z</updated>

    <summary><![CDATA[hb qp bp study 新年LT&amp;ビアバッシュ2012に参加して発...]]></summary>
    <author>
        <name>Masahiro Nagano</name>
        <uri>http://blog.nomadscafe.jp/</uri>
    </author>
    
    
    <content type="html" xml:lang="ja" xml:base="http://blog.nomadscafe.jp/">
        <![CDATA[<p><a href="https://connpass.com/event/213/">hb qp bp study 新年LT&amp;ビアバッシュ2012</a>に参加して発表してきました。</p>

<p>資料はこちら</p>

<div style="width:425px" id="__ss_11057661"> <strong style="display:block;margin:12px 0 4px"><a href="http://www.slideshare.net/kazeburo/operation-11057661" title="捗れ！Operation" target="_blank">捗れ！Operation</a></strong> <iframe src="http://www.slideshare.net/slideshow/embed_code/11057661" width="425" height="355" frameborder="0" marginwidth="0" marginheight="0" scrolling="no"></iframe> <div style="padding:5px 0 12px"> View more <a href="http://www.slideshare.net/" target="_blank">presentations</a> from <a href="http://www.slideshare.net/kazeburo" target="_blank">Masahiro Nagano</a> </div> </div>

<p>内容は今年の目標と俺俺waresの紹介です。</p>

<p>ビール飲んでピザ食べて、面白いLT聞けて、みなさん大人でとても良い会でした。</p>
]]>
        

    </content>
<feedburner:origLink>http://blog.nomadscafe.jp/2012/01/hb-qp-bp-study-lt2012.html</feedburner:origLink></entry>

<entry>
    <title>Plack::Middleware::ReverseProxy でリモートホストを確認する理由</title>
    <link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/nomadscafejp/~3/72JVzYbaevA/plackmiddlewarereverseproxy.html" />
    <id>tag:blog.nomadscafe.jp,2012://1.158</id>

    <published>2012-01-06T07:44:09Z</published>
    <updated>2012-01-06T07:49:58Z</updated>

    <summary>Reverse Proxyの後ろでApplication Serverを動かす際...</summary>
    <author>
        <name>Masahiro Nagano</name>
        <uri>http://blog.nomadscafe.jp/</uri>
    </author>
    
    
    <content type="html" xml:lang="ja" xml:base="http://blog.nomadscafe.jp/">
        <![CDATA[<p>Reverse Proxyの後ろでApplication Serverを動かす際に、REMOTE_HOSTを本当のアクセス元に書き換えてくれる仕組みはいくつかありますが^1、Plackでは壇上氏の <a href="http://search.cpan.org/~danjou/Plack-Middleware-ReverseProxy/lib/Plack/Middleware/ReverseProxy.pm">Plack::Middleware::ReverseProxy</a> がそれにあたります。</p>

<blockquote>
  <p>^1 例えば mod_extract_forwarded <a href="http://www.openinfo.co.uk/apache/">http://www.openinfo.co.uk/apache/</a></p>
</blockquote>

<p>PM::ReverseProxy のSYNOPSISでもそうなってますが、このような仕組みを使う場合、REMOTE_HOSTを指定するのが安全です。</p>

<pre><code>builder {
    enable_if { $_[0]-&gt;{REMOTE\_ADDR} eq '127.0.0.1' } 
        "Plack::Middleware::ReverseProxy";
    $app;
};
</code></pre>

<p>拙作の <a href="http://search.cpan.org/~kazeburo/Plack-Builder-Conditionals/lib/Plack/Builder/Conditionals.pm">Plack::Builder::Conditionals</a> を使うと以下のように書けます。</p>

<pre><code>use Plack::Builder;
use Plack::Builder::Conditionals;

builder {
    enable match_if addr(['192.168.0.0/24','127.0.0.1']),
        "Plack::Middleware::ReverseProxy";
    $app
};
</code></pre>

<p>このようにREMOTE_HOSTを指定するのは、改ざんされたリクエストを防ぐためです。</p>

<p>PM::ReverseProxyの仕組みは以下の図のようになります。</p>

<p><span class="mt-enclosure mt-enclosure-image" style="display: inline;"><img alt="reverseproxy1.png" src="http://blog.nomadscafe.jp/2012/01/06/reverseproxy1.png" width="450" height="373" class="mt-image-center" style="text-align: center; display: block; margin: 0 auto 20px;" /></span></p>

<p>ブラウザ(IPアドレス:v.x.y.z)からReverse Proxyに対してリクエストを行うと、Reverse Proxyは「X-Forwarded-For」ヘッダにREMOTE_ADDRを追加してApplication Serverにリクエストします。Application Serverでは当然REMOTE_ADDRがReverse Proxyの物(ここではlocalhost)になりますが、PM::ReverseProxyはX-Forwarded-Forをみて、REMOTE_ADDRを上書きします。これにより本来のアクセス元がApplication ServerでもREMOTE_ADDRで取得できます。</p>

<p>ここで、Application Server側でREMOTE_ADDRを確認しないと、以下のような事態がおきます。</p>

<p><span class="mt-enclosure mt-enclosure-image" style="display: inline;"><img alt="reverseproxy2.png" src="http://blog.nomadscafe.jp/2012/01/06/reverseproxy2.png" width="450" height="231" class="mt-image-center" style="text-align: center; display: block; margin: 0 auto 20px;" /></span></p>

<p>X-Forwarded-Forヘッダに任意のIPアドレス(v.x.y.z)を追加し、Application Serverに直接アクセスするとPM::ReverseProxyは同じようにREMOTE_ADDRを設定します。本来REMOTE_ADDRは「192.168.9.41」にも関わらず、「v.x.y.z」と名乗れてしまいます。もし、v.x.y.zからしか閲覧することができないコンテンツがあっても、この方法で見る事ができてしまいます。</p>

<pre><code>use Plack::Builder;
use Plack::Builder::Conditionals;

builder {
    enable 'ReverseProxy';
    mount '/private' =&gt; builder {
        enable match_if addr('!','v.x.y.z'), 
            sub { sub { [403,['Content-Type'=&gt;'text/plain'],['Forbidden']] } };
        $private_app;
    };
    $app
};
</code></pre>

<p>↑こういうの危険。</p>

<p>Application Serverに直接アクセスできないのは普通だと思いますが、なんらかの設定漏れでアクセスができてしまうことも考えられます。念をいれてREMOTE_ADDRをチェックするのをお勧めします。</p>
]]>
        

    </content>
<feedburner:origLink>http://blog.nomadscafe.jp/2012/01/plackmiddlewarereverseproxy.html</feedburner:origLink></entry>

<entry>
    <title>Plack::Middleware::AccessLog でありがちな罠</title>
    <link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/nomadscafejp/~3/Yv_Ht4ZTKps/plackmiddlewareaccesslog.html" />
    <id>tag:blog.nomadscafe.jp,2012://1.157</id>

    <published>2012-01-06T03:17:26Z</published>
    <updated>2012-01-06T03:26:06Z</updated>

    <summary>Plack::Middleware::AccessLog は Apacheライク...</summary>
    <author>
        <name>Masahiro Nagano</name>
        <uri>http://blog.nomadscafe.jp/</uri>
    </author>
    
    
    <content type="html" xml:lang="ja" xml:base="http://blog.nomadscafe.jp/">
        <![CDATA[<p>Plack::Middleware::AccessLog は Apacheライクなログが残せる便利ミドルウェアなんですが使う上で一つ注意点があります。</p>

<pre><code>use Plack::Builder;

builder {
    enable "AccessLog", format =&gt; "combined";
    sub { die };
};
</code></pre>

<p>これで、500エラーのログが残ることを期待するかもしれませんが、実際は記録されません。</p>

<p>例外がMiddleware層を飛ばしてServerまで伝わる為で、何らかの形で例外を補足してあげる必要があります。</p>

<p>例えば、Plack::Middleware::HTTPExceptions</p>

<pre><code>builder {
    enable "AccessLog", format =&gt; "combined";
    enable "HTTPExceptions";
    sub { die };
};
</code></pre>

<p>もしくは自前でMiddlewareを書く方法。</p>

<pre><code>use Plack::Builder
use Try::Tiny

builder {
    enable "AccessLog", format =&gt; "combined";
    enable_if { ($ENV{PLACK_ENV} || '') ne 'development'} sub {
        my $app = shift;
        sub {
            my $env = shift;
            try {
                $app-&gt;($env);
            } catch {
                $env-&gt;{'psgi.errors'}-&gt;print($_);
                [500,['Content-Type'=&gt;'text/plain'],['Internal Server Error']];
            }
        }
    };
    sub { die };
};
</code></pre>

<p>やってることはHTTPExceptionsとほぼ同じです</p>

<p>Middlewareを使う場合は、miyagawaさんの<a href="http://www.slideshare.net/miyagawa/plack-at-oscon-2010/93">slide</a>にも登場する。この図を思い浮かべるといいと思います。</p>

<p><span class="mt-enclosure mt-enclosure-image" style="display: inline;"><img alt="pylons_as_onion.png" src="http://blog.nomadscafe.jp/2012/01/06/pylons_as_onion.png" width="478" height="435" class="mt-image-center" style="text-align: center; display: block; margin: 0 auto 20px;" /></span></p>

<p><a href="http://docs.pylonsproject.org/projects/pylons-webframework/en/latest/concepts.html#wsgi-middleware">pylonsのドキュメント</a>から転載です</p>
]]>
        

    </content>
<feedburner:origLink>http://blog.nomadscafe.jp/2012/01/plackmiddlewareaccesslog.html</feedburner:origLink></entry>

<entry>
    <title>そろそろSTFのデータベース運用についてひとこと言っておくか</title>
    <link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/nomadscafejp/~3/ngiHa0vrxzM/stf.html" />
    <id>tag:blog.nomadscafe.jp,2011://1.156</id>

    <published>2011-12-31T14:29:26Z</published>
    <updated>2011-12-31T14:37:19Z</updated>

    <summary>祝オープンソース化。 STF 分散オブジェクトストレージシステム http://...</summary>
    <author>
        <name>Masahiro Nagano</name>
        <uri>http://blog.nomadscafe.jp/</uri>
    </author>
    
    
    <content type="html" xml:lang="ja" xml:base="http://blog.nomadscafe.jp/">
        <![CDATA[<p>祝オープンソース化。</p>

<p>STF 分散オブジェクトストレージシステム<br />
<a href="http://labs.edge.jp/stf/">http://labs.edge.jp/stf/</a></p>

<p>ライブドアのサービスで主に画像管理用に使っているSTFがオープンソースで公開されています。</p>

<p>Perl/PSGI、Q4M、MySQL、Apacheという、Webアプリケーションエンジニアにとってとてもなじみやすい構成を取っており、実際運用もしやすくなっています。</p>

<p>ただひとつ気になるのはMySQLのデータのデカさ。3億オブジェクト／10億エンティティを保存した段階でのMySQLのデータサイズは、約220GBにもなります。これを潤沢にメモリを積み、SSDを4本RAID10にしたサーバにて運用しております。</p>

<p>データの取り回しも大変で、データのダンプに数時間、リストアに数十時間、レプリケーションが追いつくのにまた数時間と移設作業を行うのにまるまる一週間かかるような感じです。とってもカジュアルではありませんね。</p>

<h2>テーブル構成</h2>

<p>STFの主なテーブルは以下の4つです。</p>

<ul>
<li>bucket</li>
<li>object</li>
<li>entity</li>
<li>storage</li>
</ul>

<p>bucketはオジェクトをまとめる単位。objectは外から見えるファイル、entityはファイルの実体でサーバに分散配置されます。そのサーバについて定義するのがstorageテーブルです。</p>

<p>すべてのスキーマはソースコードの中に含まれるので以下から取得可能です。</p>

<p>h<a href="ttps://github.com/stf-storage/stf/blob/master/misc/stf.sql">ttps://github.com/stf-storage/stf/blob/master/misc/stf.sql</a></p>

<p>このテーブルのうち、データサイズが大きくなるのはobjectとentityの2つ。</p>

<pre><code>CREATE TABLE object (
       id BIGINT NOT NULL PRIMARY KEY,
       bucket_id BIGINT NOT NULL,
       name VARCHAR(255) NOT NULL,
       internal_name VARCHAR(128) NOT NULL,
       size INT NOT NULL DEFAULT 0,
       num_replica INT NOT NULL DEFAULT 1,
       status TINYINT DEFAULT 1 NOT NULL,
       created\_at INT NOT NULL,
       updated\_at TIMESTAMP,
       UNIQUE KEY(bucket_id, name),
       UNIQUE KEY(internal_name)
) ENGINE=InnoDB;

CREATE TABLE entity (
       object_id BIGINT NOT NULL,
       storage_id INT NOT NULL,
       status TINYINT DEFAULT 1 NOT NULL,
       created_at INT NOT NULL,
       updated_at TIMESTAMP,
       PRIMARY KEY id (object_id, storage_id),
       KEY(object_id, status),
       KEY(storage_id),
       FOREIGN KEY(storage_id) REFERENCES storage(id) ON DELETE RESTRICT
) ENGINE=InnoDB;
</code></pre>

<h2>データサイズを小さく保つための工夫</h2>

<p>まず object テーブルをみて気になるのが、ユニーク属性を保つインデックスが3つあるということ。</p>

<ul>
<li>PRIMARY KEY</li>
<li>UNIQUE KEY(bucket_id, name)</li>
<li>UNIQUE KEY(internal_name)</li>
</ul>

<p>PRIMARY KEYはBIGINTで時間とDispatcherサーバに割り当てられたIDから計算されるユニークな数値です。2つは目システムの外から見えるバケットとファイル名のユニーク制限。そして3つ目がSTF内部でのファイル名です。</p>

<p>nameもinternal_nameもVARCHARとなるので、インデックスのサイズも大きくなりそうです。そこで利用できるのがMurmurHash等を使いより小さいデータにインデックスを張る方法。</p>

<p>データ分散とインデックス最適化のためのハッシュ関数の利用 - Perl Advent Calendar Japan 2011 Hacker Track<br />
<a href="http://perl-users.jp/articles/advent-calendar/2011/hacker/11">http://perl-users.jp/articles/advent-calendar/2011/hacker/11</a></p>

<p>MurmurHashは32bitの数値となるのでハッシュ値が衝突する可能性が割と高いと思われるので、実際に使う場合はhash値をにユニークインデックスを張らず、通常のインデックスとし、データ追加時にトランザクションを使ってデータが被らないようにする必要があります。</p>

<p>internal_nameについては、本当にこの値が必要かどうか考えることが必要そうです。ここまで既にテーブル上に2つのユニークな値があるので、そっちを活用すればインデックスだけではなくカラム自体の削除もできそうです。例えば、PRIMARY KEYのBIGINTを内部のファイル名にしても良いはずです。</p>

<p>entityテーブルについては、2点ほど気になる点があります。1つはcreated_atとupdated_atのカラム、もう一つがKEY(object_id, status)のインデックスです。</p>

<p>MySQLのドキュメントに各データタイプにおける必要保存領域が書かれています。</p>

<p>10.5. データタイプが必要とする記憶容量<br />
<a href="http://dev.mysql.com/doc/refman/5.1/ja/storage-requirements.html">http://dev.mysql.com/doc/refman/5.1/ja/storage-requirements.html</a></p>

<p>created_atとupdated_atはそれぞれINTとTIMESTAMPなので、4byte+4byte=8byte必要となります。10億レコードあれば8GB分のデータにもなります。消すと若干節約できますね。</p>

<p>KEY(object_id, status)のインデックスも基本必要ないはずです。object_idを指定した段階で結果はオブジェクトの複製数（せいぜい3から5）にしぼらるので、statusをインデックスに含めなくても高速に検索可能です。PRIMARY KEYが(object_id, storage_id)なのでこちらで十分に事足ります。</p>

<p>ここまでぐだぐだと書いた内容を入れ込むとスキーマはこんな感じかな</p>

<pre><code>CREATE TABLE object (
       id BIGINT NOT NULL PRIMARY KEY,
       bucket_id BIGINT NOT NULL,
       name VARCHAR(255) NOT NULL,
       name_hash INT UNSIGNED NOT NULL,
       size INT NOT NULL DEFAULT 0,
       num_replica INT NOT NULL DEFAULT 1,
       status TINYINT DEFAULT 1 NOT NULL,
       created_at INT NOT NULL,
       updated_at TIMESTAMP,
       KEY(bucket_id, name_hash)
) ENGINE=InnoDB;

CREATE TABLE entity (
       object_id BIGINT NOT NULL,
       storage_id INT NOT NULL,
       status TINYINT DEFAULT 1 NOT NULL,
       PRIMARY KEY id (object_id, storage_id),
       KEY(storage_id),
       FOREIGN KEY(storage_id) REFERENCES storage(id) ON DELETE RESTRICT
) ENGINE=InnoDB;
</code></pre>

<p>データサイズがどれくらいになるかはやってみないとわからないけど、確実に小さくはなって、データのハンドリングが楽になると思うので、仕事が始まったらできるところからやってみたいです ＞ 誰か</p>
]]>
        

    </content>
<feedburner:origLink>http://blog.nomadscafe.jp/2011/12/stf.html</feedburner:origLink></entry>

</feed>

