Kodama's home / tips.

postfix の負荷の調査とチューニング

  1. 事例の紹介
  2. postfix の負荷の調査
    1. postfix の構成
    2. ネットワークポートの利用状況
    3. UNIX ソケットの利用状況
    4. プロセスの起動状況
    5. 送信キューの状況
    6. メモリー使用量
    7. 同時オープンファイル数
    8. 調査手順のまとめ
  3. 負荷への対応をチューニングするポイント
    1. プロセスの同時起動数
    2. ファイルの同時オープン数
    3. キューの滞留時間
    4. 受信の制限
    5. syslogd の動作

事例の紹介

調査スクリプト( 調査手順のまとめ 参照) を cron で1時間毎に起動して, メールとして保管しておいた. メールの利用には mew を用いているので, 収集したメールは個別のファイルになって, ファイル名に番号を振って保存されている. まず, mailq の滞留数の部分を取り出す.
ls|sort -n|xargs -l grep -E '(Subject)|(Date)|(Requests)'
更に, 整形する.
gawk '/Subject/{if(s~/net_check/){print d" 0"};s=$2}/Date/{d=$4" "$5}/bytes.*in.*Requests/{print d" "$5;s="";next}/Requests/{s=""}'
この結果を月ごとに集計する.
gawk '(($1" "$2)!=d){if(n>0){print d" "s/n" "m};m=0;s=0;n=0}{d=$1" "$2;s=s+$3;n=n+1;if(m<$3){m=$3}}'

月ごとの集計結果. mailq による キューサイズ(1時間毎に測定)の 平均 最大

Jan 2006 221.078 304
Feb 2006 128.705 360
Mar 2006 270.508 472
Apr 2006 209.83 380
May 2006 255.063 419
Jun 2006 376.197 746
Jul 2006 430.722 889
Aug 2006 1297.07 2780
Sep 2006 603.092 872
Oct 2006 642.896 1094
Nov 2006 1176.89 1599
Dec 2006 2042.28 3048
Jan 2007 1759.06 2740
2006 年当初までは, postfix のデフォルトの設定で使用していた. 2006.1月辺りに, キューがしばしば 300を越えるようになってきた. これは, 総当たり的に送りつけて来る SPAM のバウンスに依るものと思われる. そのため, 送信のためのプロセス数が上がって, postfix の制限に抵触して, 通常のメールの送受にも支障が出て, メールの配送がほとんど停止に追い込まれた.

SPAM は実在するアカウントの名簿を元に一斉に送るものと, ありがちなアカウント名の一覧を元に総当たり的に送るものとがある. このうち後者の場合は, 実在しない者あてのメールとなってしまう事が多い. 実在しない者宛のメールは発送元サーバに返送されることになる. しかし, SPAM の場合, 発送元サーバを偽装している事が多く, 返送に失敗してしまう. 返送に失敗したメールは, 外部宛サーバに滞留し, サーバは定期的に配送を試みる. 滞留したメールが多くなって, それがサーバの制限に抵触するようになると, 他の正常なメールの送受にも影響する.

2月に,一旦, access リストによる受信制限をつけて, サーバの状態を回復させ, 同時に, 設定の調整 を行った. その後, サーバの調整の後に制限を解除した. 2月の平均値が下がっているのはこの期間の影響.

現在(2007.2)は, 2006年当初の 10倍程度の負荷となっているが, サーバ自体は問題なく動作している. 回線速度に余裕があるので, 他の用途(WWWなど)には影響は出ていない.

postfix の負荷の調査

postfix の構成

postfix は単一のプロセスではなく, 幾つかのプロセスが協力しあってメールを処理するようになっている. 大まかな構成としては, ネットワークからの SMTP による着信を smtpd が受け取り, cleanup に引き渡す, ここで一旦 incoming スプール(/var/spool/postfix/incoming)に置き, qmgr が取り上げてユーザへの配送(local), 他のサーバへの転送(smtp), UNIXパイプへの接続(pipe) を判別して各々に引き渡す.
ネットワークから着信--smtpd--cleanup--(incomingキュー)--qmgr(active,deferredキュー)--local,smtp,pipe

他のサーバからの受信の状況は smtpd の動作を観察すると良い. 他のサーバへの送信や転送の状況は smtp の動作を観察すると良い.

該当するユーザ名がないなどの理由で local が配送できなかった場合は, 一旦 bounce スプール(/var/spool/postfix/bounce)に置かれ qmgr が処理する. また, smtp で他に転送しようとして 転送できなかった場合には deferred スプール(/var/spool/postfix/deferred) に置かれ, 一定の時間が経過した後に再度 smtp に引き渡されて転送が試みられる. スプールの状況は mailq コマンドで見ることができる.

ネットワークポートの利用状況

外部からの着信(smtpd による受信)と 外部への転送(smtp による送信) による ネットワークポート や UNIX ソケットの使用状況は netstat でしらべる事ができる.
$ netstat -A inet -n
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State      
tcp        0      0 10.20.30.40:25          xx.xx.xx.xx:12345       ESTABLISHED 
tcp        0      0 10.20.30.40:23001       yy.yy.yy.yy:25          ESTABLISHED 
tcp        0      0 10.20.30.40:23002       zz.zz.zz.zz:25          SYN_SENT
....略
SMTP ポートの利用数をしらべるのは次のように, :25 の部分を grep で取り出し, wc で計数すると良い.

受信による25番ポートの利用数: この例では 3個の接続があることがわかる.

$ netstat -A inet -n | grep :25 | grep 10.20.30.40:25 |wc
      3      18     243

送信による25番ポートの利用数: この例では 11個の接続があることがわかる.

$ netstat -A inet -n | grep :25 | grep -v 10.20.30.40:25 |wc
     11      66     891

送信のうち State が SYN_SENT になっているものは, 相手方への接続を試みたが, 接続が確立できていない状態を表す. これが多い場合は, mailq でみたときにメールが溜っていることが多い. (タイムアウトでメールを送信できないため.)

netstat -n -A inet | grep SYN_SENT | wc
     11      66     891

UNIX ソケットの利用状況

メールの送信時の qmgr から smtp への引き継ぎは UNIX ソケットを用いている.

$ netstat -A unix
ソケットの利用数をしらべるには次のようにする. (/smtp の部分を grep で取り出し, wc で計数する.)
$ netstat -A unix | grep  /smtp | wc
     11      88     792
これは, 送信による25番ポートの利用数と対応している.

プロセスの起動状況

実際にプロセスが起動している状況を見るには, ps, top, pstree などを使用する. 各プロセスの起動数をみるには次のようにすると良い.
# ps -U postfix -o args | sort | uniq -c | sort -nr
     23	smtp -t unix -u -c
      4	smtpd -n smtp -t inet -u -c
      2	cleanup -t unix -u -c
      1	trivial-rewrite -n rewrite -t unix -u -c
      1	qmgr -l -t fifo -u -c
      1	pickup -l -t fifo -u -c
      1	COMMAND
この例では smtp が 23個, smtpd が 4個起動している. smtp は送信用, smtpd は受信用のプロセス.

停電やネットワーク障害などでサーバが止まっていた後の回復時には, 他サイトからメールが大量に流入することになり, 一時的に負荷が高くなる. 特に smtp と smtpd の起動数が問題となる.

プロセスの同時起動数は postfix の master.cf で設定できる.

送信キューの状況

現在の送信キューに幾つのメールが滞留しているかは, mailq の最終行に現れる.
$ mailq
...
...略 (個々のメールについて配送出来なかった理由の概略が表示されている)
...
-- 3789 Kbytes in 408 Requests.
この例では 408 通のメールがキューに滞留している.

上で略した個々のメールを配送できなかった理由をまとめてみるには 次のようにする. 滞留の理由の上位5位を表示してみた.

$ mailq | gawk '/\(/{$1=$1;print}' | sort | uniq -c | sort -nr | head -5
     30	(connect to boranch2.mailya.net[58.4.46.57]: Connection timed out)
     18	(Name service error for internationalcspedition.com: Host not found, try again)
      7	(connect to mx1.aex.com[10.23.42.11]: Connection timed out)
      6	(connect to null.com[207.44.141.137]: Connection refused)
      6	(Name service error for e-standard.biz: Host not found, try again)

qshape コマンドを使う方が良いらしい.

メモリー使用量

free コマンドを使う. (Linux のメモリー管理 を参照)
$ free
             total       used       free     shared    buffers     cached
Mem:       1024796    1011848      12948          0      59040     346600
-/+ buffers/cache:     606208     418588
Swap:       151192        452     150740

同時オープンファイル数

同時にオープンするファイル数にも制限がある. linux の場合つぎのように確認する.
$ cat /proc/sys/fs/file-max 
202056
# cat /proc/sys/fs/file-nr 
3680    0       202056
この例では同時オープンの限界は 202056. 現在の使用数は 3680.

調査手順のまとめ

以上をまとめると次のようなスクリプトになる.
#!/bin/sh
MY_ADDRESS=10.20.30.40

echo -n inet_smtp_receive
netstat -n -A inet | grep :25 | grep $(MY_ADDRESS):25 | wc

echo -n inet_smtp_send
netstat -n -A inet | grep :25 | grep -v $(MY_ADDRESS):25 | wc

echo -n inet_SYN_SENT
netstat -n -A inet | grep SYN_SENT | wc

echo -n unix_smtp 
netstat -A unix | grep /smtp | wc

echo process
ps -U postfix -o args | sort | uniq -c | sort -nr

echo mailq
mailq | grep Requests
mailq | gawk '/\(/{$1=$1;print}' | sort | uniq -c | sort -nr | head
結果の例:
inet_smtp_receive    4      24     324
inet_smtp_send     72     432    5832
inet_SYN_SENT     70     420    5670
unix_smtp     72     576    5184
process
     75	postfix  smtp -t unix -u -c
      2	postfix  smtpd -n smtp -t inet -u -c
      2	postfix  cleanup -t unix -u -c
      1	postfix  trivial-rewrite -n rewrite -t unix -u -c
      1	postfix  qmgr -l -t fifo -u -c
      1	postfix  pickup -l -t fifo -u -c
      1	USER     COMMAND
mailq
-- 3043 Kbytes in 467 Requests.
     29	(connect to XXXXXXXXXX.net[XX.XX.XX.XX]: Connection timed out)
     20	(connect to XXXXXX.com[XX.XX.XX.XX]: Connection refused)
     11	(Name service error for XXXXX.biz: Host not found, try again)
      9	(connect to XXXXXX.co.jp[XX.XX.XX.XX]: No route to host)
      6	(connect to null.com[XX.XX.XX.XX]: Connection refused)

このほか, CPUの利用状況, メモリー, ディスク, ネットワークの負荷などを ps, top, vmstat, df, df -i, free などのコマンドで調べるが, これらは postfix に特有の問題ではないので ここでは割愛する.

cron (man crontab) で定期的にデータを取って, ファイルに落とすか, 自分にメールすると良い. 以下は, 1時間毎にメールする例.

# MIN HOUR DAY MONTH DAYOFWEEK   COMMAND
0 * * * *  my_script.sh > mailx -s net_check my-mail-addr@math.kobe-u.ac.jp

負荷への対応をチューニングするポイント

プロセスの同時起動数

プロセスの同時起動数は幾つかの設定で制限されている.
  1. linux カーネルとしての制限は次のように見る事ができる.
    # cat /proc/sys/kernel/threads-max 
    14336
    
    次のようにして調整する.
    # echo 20000 > /proc/sys/kernel/threads-max 
    # cat /proc/sys/kernel/threads-max 
    20000
    
    または /etc/init.d の適当なファイルか, /etc/sysctl.conf に記述する.

    sysctl.conf の例:

    kernel.threads-max = 20000
    
  2. postfix 管理下のスレッド数の制限は /etc/postfix/main.cf の default_process_limit で指定することができる. また, postconf で確認できる.
    $ /usr/sbin/postconf | grep  default_process_limit
    default_process_limit = 50
    
  3. コマンド毎の制限は /etc/postfix/master.cf で指定できる. この数はデフォルトで default_process_limit=50 となっている.

起動数の設定が少すぎる場合の問題:

  1. 送受信の要求に追い付かない場合, ピークが過ぎるまでメールの配送が遅延する
  2. 障害からの回復時に送受信のラッシュが起こるが, 対処能力が限られるので回復が遅れる
起動数の設定が多すぎる場合の問題:
  1. メモリーの不足
  2. CPU の不足
  3. スプール用のディスクの負荷
  4. ネットワークの占有
  5. inet ポートの消費
  6. ファイルハンドルの消費
  7. 相手側サーバに負荷をかける

ファイルの同時オープン数

同時オープンファイル数の調整は以下のようにして行う.
# cat /proc/sys/fs/file-max 
202056
# echo 300000 > /proc/sys/fs/file-max
# cat /proc/sys/fs/file-max 
300000
また, 起動時にこれを行うように, /etc/init.d の適当なファイルか /etc/sysctl.conf に記述する.

/etc/sysctl.conf の例:

fs.file-max = 300000

キューの滞留時間

main.cf の maximal_queue_lifetime で指定できる. デフォルトは 5d(5日) だがキューの負荷が問題となるようなら制限する.

送信キューにメールが溜ることで明らかになる問題:

  1. postfix の設定
  2. ネットワーク関連の設定
  3. 名前引きの問題(DNS関連)
  4. 通信回線の障害
  5. 相手先サーバの障害
  6. 相手先サーバの接続拒否
  7. 送り先サーバ名の誤り/詐称
送信キューにメールが溜ること自体による問題:
  1. キューの制限(qmgr_message_active_limit)に抵触
    $ postconf | grep qmgr_message_active_limit
    qmgr_message_active_limit = 10000
    
  2. ディスク容量の消費
  3. ディスク i-node の消費
  4. inet ポート の消費
  5. 回線容量の消費
  6. プロセスの起動数が増える.
    1. メモリを消費
    2. CPU の消費
    3. OS のプロセス数の制限に抵触
    4. postfix のプロセス数の制限に抵触
      postfix の制限は資源を枯渇させないためのものなので これ自体は良いのだが, 必要な資源の見積りに問題がないか考え直す必要はある.
  7. 特定サイト宛てのメールが溜っている場合, 相手先サーバの回復時に送信のラッシュが起こる.
送信キューの保持時間を制限した場合の問題:
  1. メールが失われやすくなる

受信の制限

応答できないアドレスから辞書攻撃を仕掛けるような迷惑メール業者からの, 大量のメールが流れ込んでいる場合, deferred スプールが溢れる可能性があります. 負荷の元となる迷惑メールの送信元が明確に切り分けられれば main.cf の smtpd_client_restrictions で 接続元のIP アドレス, ホスト名, ドメイン名などによるフィルタをかけることで 受け取りを制限することも考えられる. この項の目的としては, サーバとネットワークの負荷の軽減が目的だが, 副作用として迷惑メール対策にもなる.
# main.cf
smtpd_client_restrictions = permit_mynetworks,
	check_client_access hash:/etc/postfix/access
/etc/postfix/access ファイルの例. 変更後は postmap /etc/postfix/access で hash テーブルを更新する.
# access
co.jp OK
ad.jp OK
go.jp OK
ac.jp OK
so-net.ne.jp OK
ezweb.ne.jp OK
docomo.ne.jp OK
vodafone.ne.jp OK
221.207.172  REJECT
221.207.173  REJECT
制限した場合の問題点:
  1. 正常なメールを拒否してしまう可能性がある
  2. 設定をメンテナンスする必要がある

syslogd の動作

syslogd の動作は /etc/syslog.conf で設定する. ファイル名の前に "-" をつけると, 書き込みが非同期に行われるようになるので 書き込み性能が改善される. ただし, システム障害が発生した場合に記録が失われる可能性が増える.
mail.*                -/var/log/mail.log

Kodama's home / tips.