とあるサービスの高負荷チューニング日記

とあるサイトのとあるサービス機能について相談を受けた。高負荷が原因で 95% の確率でリクエストを無処理で NULL を返す仕様になっているとかなんとか。
正直 95% が結果が NULL なサービスは存在価値そのものから疑う必要があるわけだが、必要な機能らしい。

と言うわけでサーバへログインしてみた。
高負荷サイトのチューニングについて以前記述した手順でサイトの状態を把握していきます。

- スポンサーリンク -


1. top をみて上位に張り付いているプロセスを確認しつつ CPU or I/O のどちらが原因か判別

top で apache の幾つかのプロセスが CPU を食いつぶしていることが確認できます。メモリ不足というわけではなさそうです。

top - 19:11:41 up 294 days,  9:51,  4 users,  load average: 1.66, 1.18, 1.05
Tasks: 416 total,   3 running, 412 sleeping,   0 stopped,   1 zombie
Cpu(s): 75.2% us,  0.1% sy,  0.0% ni, 24.7% id,  0.0% wa,  0.0% hi,  0.0% si
Mem:   8173080k total,  7784328k used,   388752k free,    47060k buffers
Swap:  8191992k total,   696000k used,  7495992k free,  6184208k cached

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND    
 9304 apache    25   0 70728  46m 3644 R 99.0  0.6  12:10.90 httpd      
 9300 apache    25   0  111m  89m 3644 R 99.7  1.1  13:36.71 httpd      
 9298 root      16   0  6424 1404  852 R  0.3  0.0   0:00.13 top        
    1 root      16   0  4772  276  244 S  0.0  0.0   0:40.47 init       
    2 root      RT   0     0    0    0 S  0.0  0.0   0:06.12 migration/0
    3 root      34  19     0    0    0 S  0.0  0.0   0:00.17 ksoftirqd/0


2. ps を使ってプロセスの状態を確認して(T),(D)の状態から CPU or I/O のどちらが原因か判別

top で確認した CPU を食いつぶしている apache のプロセスの実行時間が 10 分以上を超えるのが確認できます。怪しさ満点です。

root      9320  0.2  0.0  6424 1404 pts/2    S+   19:13   0:00 top
root      9322  0.0  0.0  5452  816 pts/7    R+   19:13   0:00 ps aux
root     11773  0.0  0.0     0    0 ?        S    Sep12   0:50 [pdflush]
apache    9304 94.0  0.6 75612 52216 ?       R    18:53  12:14 /usr/local/apache2/bin/httpd -k start
apache    9300 99.5  0.5 72312 48900 ?       R    18:19  13:40 /usr/local/apache2/bin/httpd -k start
apache   12249  0.0  0.0 76732 2612 ?        S    Oct21   0:00 /usr/local/apache2/bin/httpd -k start
apache   12252  0.0  0.4 212576 37804 ?      S    Oct21   0:00 /usr/local/apache2/bin/httpd -k start
apache   12253  0.0  0.4 212576 37804 ?      S    Oct21   0:00 /usr/local/apache2/bin/httpd -k start
apache   12254  0.0  0.4 206676 33072 ?      S    Oct21   0:00 /usr/local/apache2/bin/httpd -k start
apache   12255  0.0  0.4 206676 33072 ?      S    Oct21   0:00 /usr/local/apache2/bin/httpd -k start


3. vmstat で procs の r, b の数、swap の si, so の状態、I/O の bi, bo の状態を確認
4. sar を使って os の状態をさらに詳しく確認

I/O 関連の数値は健全な値を示しているので、iowait 負荷問題ではなく、CPU 負荷問題であることが判別できました。かつ CPU 負荷の原因は apache が原因であることもわかりました。

00:00:01          CPU     %user     %nice   %system   %iowait    %steal     %idle
・・・中略・・・
15:10:02          all     97.84      0.00      1.88      0.00      0.00      0.28
15:10:02            0     93.59      0.00      6.24      0.00      0.00      0.17
15:10:02            1     99.48      0.00      0.21      0.00      0.00      0.31
15:10:02            2     99.39      0.00      0.39      0.00      0.00      0.22
15:10:02            3     98.91      0.00      0.67      0.00      0.00      0.42
15:20:01          all     91.85      0.00      2.03      0.00      0.00      6.12
15:20:01            0     76.52      0.00      6.79      0.00      0.00     16.69
15:20:01            1     99.52      0.00      0.38      0.00      0.00      0.09
15:20:01            2     99.40      0.00      0.40      0.00      0.00      0.21
15:20:01            3     91.98      0.00      0.55      0.00      0.00      7.47


5. apache のログと conf を確認

apache のログと conf を確認して、どんなアクセスが原因かを推測してみます。結局このサーバは、apache2 上で mod_perl を動作させていて、とある1つの CGI を動作させているだけであることがわかりました。つまりチューニングポイントは、その CGI プログラムであることが確定しました。


6. プログラムのチューニング

プログラムの構成を確認してみます。何をしているプログラムかも初めて知ることになるわけですが、指定の URL を渡すと、URL のコンテンツをクロールして取得し、html を解析して <img> タグを抽出して width が大きい画像の URL を返す CGI であることがわかりました。

プログラムの構成は概ね下記のような構成になっていました。

img001.jpg

解析の結果、他サイト(外部サイト)を GET でクロールした際に、Content-Type が text/html 以外の場合(つまりバイナリデータ)に html 解析時に仕込まれた Encode::decode() を実行すると、CPU が高負荷になることが判明しました。
Content-Type の判定ロジックを追加して、http://xxx/hoge.pdf などの入力を受け取った際に何も処理せず値を返すことで、CPU 高騰を回避することに成功しました。劇的な効果があったので、確率 95% の部分も良い数値に改善することが可能でしょう。加えて LWP::UserAgent を使っていたので、max_size() メソッドで response のサイズにも制限を加えれば完璧です。
ロジック修正後のプログラム構成は概ね下記のような構成になりました。

img002.jpg

障害調査プログラムはこんなコードを書いて解析しました。引数に pdf な url を入力すると cpu が高騰することがご確認いただけるかと思います。

use utf8;
use LWP::UserAgent;
use Encode::Guess qw/euc-jp shiftjis utf8/;
use Encode ();
use Data::Dumper;

# perl test.pl http://xxx 1
my $bugfix = 0 || $ARGV[1];
my $url = $ARGV[0] or die "input url\n";
print $url, "\n";

# LWP 制限
my $ua = LWP::UserAgent->new;
$ua->timeout(5);
$ua->max_size( 1 * 1024 * 1024 ) if $bugfix;

# コンテンツ取得
my $req = HTTP::Request->new( GET => $url );
my $res = $ua->request($req);
die $!, "\n" unless $res->is_success;
print $res->header('Content-Type'), "\n";
die "ContentType error\n" if $res->header('Content-Type') !~ /text/ && $bugfix;

my $html = $res->content;
print "guess_encoding", "\n";

# 文字コード変換
my $guess = Encode::Guess::guess_encoding($html);
if ($guess) {
    if ( ref $guess ) {
        $html = Encode::decode( $guess->name(), $html );
    }
    else {
        $html = Encode::decode( 'utf-8', $html );
    }
}
else {
    $html = Encode::decode( 'utf-8', $html );
}
print "decode", "\n";

# img 抽出
my @imgs;
foreach ( $html =~ /<img\s[^>]+>/g ) {
    push @imgs, $_;
}
print Dumper \@imgs;

今のお仕事は全然プログラムのお仕事じゃないので、たまに perl コードを書く機会があると、とっても楽しいです。w

- スポンサーリンク -