やっぱり Sun がスキ! : Weblog やっぱり Sun がスキ!

やっぱり Sun がスキ!

http://blogs.sun.com/yappri/date/20091125 2009年 11月 25日 水曜日

ディスクドライブの RAS 機能

SAS ディスクと SATA ディスク の RAS 機能差異について

SAS ディスクは SATA ディスクと比較して、平均故障間隔 (MTBF) が長いことやエラー発生頻度が低いことは周知のことと思います。
それでは、通常稼働中のエラー制御機能の違いについてはどうでしょうか。調べ てみるとここにも違いがありました。(下記表内、色塗りされたセル)
今回は、次の 2 つのディスクドライブについて確認してみました。

 ドライブインタフェース  SAS   SATA 
 型番  Seagate ST3450856SS
 Seagate ST31000340NS
 24 時間 x 7 日間フル稼働時の年間故障率
 0.55%  0.73% 
 平均故障間隔 (MTBF)
 160 万時間
 120 万時間

 修正不可能エラー発生頻度
 (ビット読み込みあたり)

 1 セクタ / 10^16
 1 セクタ / 10^15 

 誤り制御/エラー訂正機能
 (ECC)

 最大 320 ビット
 10 ビット
 Background Media Scan
 ○  ×
 Media Pre-Scan
 ○  ×
 Deffered Auto-Reallocation
 ○  ×
 Idle time Read after Write
 ○  ○

Background Media Scan (BMS)
    ディスクドライブが idle 時に読み込みテ ストを行う機能

Media Pre-Scan
    書き込み前に BMS 済みかを確認し、未実施なら Write Verify を実行する機能

Deffered Auto-Reallocation (DAR)
    書き込み時に unreadable かどうかを確認し、unreadable な場合に自動で再配置する機能

Idle time Read after Write (IRAW)
    ディスクドライブが idle 時に最近 書き込まれたデータと発行された Write コマンドのデータとを比較し、必要に応じて修正する機能


まとめ

SAS ディスクも 600GB@15KRPM モデルがリリースされて大容量化が進んでいますから、 Sun Storage J4200/J4400 のような JBOD ストレージを選定する際は、容量や使用期間の長さ以外にもドライブ単体の RAS 機能の違いから SAS ディスク構成の検討もしてみてはいかがでしょうか。

もちろん ZFS にてファイルシステムレベルでデータ整合性を担保するというのもお忘れ無く。


http://blogs.sun.com/yappri/date/20091120 2009年 11月 20日 金曜日

IPMP でネットワークの負荷を分散する

はじめに

Solaris の IPMP には送信パケットの負荷を分散するアウトバウンド・ロード・スプレッディングという機能があります。マニュアル にも記載されていますので、ご存知の方も多いと思いますが、この機能を利用するとサーバからの返信時(アウトバウンド)のネットワークの負荷(ロード)を複数のインターフェイスに分散(スプレッディング)する事が可能になり、結果としてスループットを上げることができる場合があります。今回の記事ではその挙動をご覧頂きたいと思います。

検証環境

検証用に次の図の様な環境を用意しました。サーバには igb1 と igb2 という 1 Gigabit のネットワークインターフェイスがあり、どちらも同じネットワークセグメントに接続されています。この 2 つのインターフェイスに負荷を分散することができれば、最大で 2 Gigabit の帯域を使用出来ることになります。

igb1 の IP アドレスは 192.168.10.1/24 で、igb2 の IP アドレスは 192.168.10.2/24 です。サーバと同じセグメントにはクライアントマシンが 2 台接続されています。クライアント A の IP アドレスは 192.168.10.3/24, クライアント B の IP アドレスは 192.168.10.4/24 です。なお、サーバにはテスト用に lighttpd をインストールしてあります。サーバもクライアントも OS は Solaris 10 10/09 です。

   +-----------------+                 o Switching Hub 192.168.10.0/24
   |                 |                 |
   |                 | 192.168.10.1    |
   |            igb1 +-----------------+
   |                 |                 |    192.168.10.3 +----------+
   | Server          |                 +-----------------+ Client A |
   |                 | 192.168.10.2    |                 +----------+
   |            igb2 +-----------------+
   |                 |                 |    192.168.10.4 +----------+
   |                 |                 +-----------------+ Client B |
   +-----------------+                 |                 +----------+
                                       o

サーバ側のネットワークの設定は以下の通り行いました。

 # ifconifg igb1 plumb
 # ifconfig igb1 inet 192.168.10.1/24 broadcast + up
 # ifconfig igb2 plumb
 # ifconfig igb2 inet 192.168.10.2/24 broadcast + up

なお、ルーティングは停止してあります。

 # routeadm 
               Configuration   Current              Current
                      Option   Configuration        System State
 ---------------------------------------------------------------
                IPv4 routing   disabled             disabled
                IPv6 routing   disabled             disabled
             IPv4 forwarding   disabled             disabled
             IPv6 forwarding   disabled             disabled
 ...

SPARC マシンで試す場合は local-mac-address? を true に設定して下さい。

 # eeprom local-mac-address?=true
 # reboot

[検証1] IPMP を組んでいない場合の挙動

この検証環境を使って、まずは IPMP を組んでいない場合の挙動から見て行きましょう。

HTTP で接続する

テストには HTTP を使用しました。2 台のクライアントからサーバの HTTP ポートに telnet コマンドで同時に接続し、返信パケットの負荷分散が行われるかを確認します。クライアントからの送信時もネットワーク帯域を有効に利用するため、クライアントはそれぞれ別のネットワークインターフェイスを目指して接続する事にします。クライアント A の接続先は 192.168.10.1 の 80 番ポートで、クライアント B の接続先は 192.168.10.2 の 80 番ポートです。これは丁度、複数のウェブサーバの手前にロードバランサーを置いて負荷分散している構成と似ています。今回の構成はサーバが一台だけで済んでいると言う点だけが異なります。

物理構成上はサーバとクライアント A の間の通信と、サーバとクライアント B の間の通信は、全く重複しない経路を通る事が可能です。それぞれの通信が完全に別々の経路を通った場合、ネットワーク帯域は最大 2 Gigabit となります。実際にどういう動きになるか見てみましょう。

 <<クライアント A から HTTP で接続>>
 # telnet 192.168.10.1 80
 
 <<クライアント B から HTTP で接続>>
 # telnet 192.168.10.2 80

クライアントから接続したら、意図した通りの接続になっているかを netstat -a コマンドで確認します。コマンドの出力結果を見ると、クライアント A は 192.168.10.3 から 192.168.10.1 の 80 番ポートへ、クライアント B は 192.168.10.4 から 192.168.10.2 の 80 番ポートへきちんと接続されている事が分かります。サーバ側でも同じ IP アドレスのペアで接続を受け取っている事が確認出来ます。

 <<クライアント A>>
 # netstat -a | grep 80          
 192.168.10.3.42881   192.168.10.1.80      49640      0 49640      0 ESTABLISHED
 
 <<クライアント B>>
 # netstat -a | grep 80
 192.168.10.4.37305   192.168.10.2.80      49640      0 49640      0 ESTABLISHED
 
 <<サーバ>>
 # netstat -a | grep 80   
 192.168.10.1.80      192.168.10.3.42881   49640      0 49640      0 ESTABLISHED
 192.168.10.2.80      192.168.10.4.37305   49640      0 49640      0 ESTABLISHED

HTTP の通信が実際にどの経路を通るかを確認する

次に HTTP の通信を発生させて、実際にどのネットワークインターフェイスを通ってデータがやり取りされるかを確認します。HTTP の通信はクライアント側からファイルを GET する事で発生させます。

 <<クライアント A>>
 # telnet 192.168.10.1 80
 Trying 192.168.10.1...
 Connected to 192.168.10.1.
 Escape character is '^]'.
 GET /001.txt HTTP/1.0
 
 
 <<クライアント B>>
 # telnet 192.168.10.2 80
 Trying 192.168.10.2...
 Connected to 192.168.10.2.
 Escape character is '^]'.
 GET /001.txt HTTP/1.0
 

ネットワークインターフェイスの使用状況は、サーバ側で dladm コマンドを使用して確認します。dladm コマンドの出力は rbytes がそのインターフェイスに於ける受信バイト数、obytes が送信バイト数です。この値が 0 以外であればデータ通信が発生している事を示しています。

下記の出力結果を見ると igb1 は受信も送信もデータのやり取りが発生しています。一方、igb2 は受信バイト数は上がっていますが、送信バイト数は 0 になっており、データの送信には使われていない事が分かります。igb2 で受け付けた接続の返信パケットはどうなってしまったのでしょうか。

 # dladm show-dev -s -i 1 igb1
                 ipackets  rbytes         ierrors opackets        obytes      oerrors
 igb1            503       32192       0       1893      2797601     0       
                 ipackets  rbytes         ierrors opackets        obytes      oerrors
 igb1            389       24896       0       1975      2931778     0       
                 ipackets  rbytes         ierrors opackets        obytes      oerrors
 igb1            416       26624       0       1644      2438276     0
 # dladm show-dev -s -i 1 igb2
                 ipackets  rbytes         ierrors opackets        obytes      oerrors
 igb2            386       24704       0       0         0           0       
                 ipackets  rbytes         ierrors opackets        obytes      oerrors
 igb2            469       30016       0       0         0           0       
                 ipackets  rbytes         ierrors opackets        obytes      oerrors
 igb2            628       40192       0       0         0           0

パケットの中身を確認する

もう少し詳しく状況を調査するため、igb1 を通るパケットを snoop コマンドでキャプチャします。snoop コマンドに -o オプションを付けて、保存先のファイル名を指定するとパケットを保存する事が出来ます。保存したファイルから読み込む場合は -i オプションでファイルを指定します。また、snoop コマンドに port 80 オプションを付けると、80 番ポートを通ったパケットだけを抽出する事が出来ます。同じく src 192.168.10.3 オプションを付けた場合は、送信元アドレスが 192.168.10.3 のパケットだけを抽出します。snoop コマンドのデフォルトの出力は一つのパケットに付き一行ずつ表示されます。出力の内容は、一番左がパケットに順番に振られる通し番号、続いて前のパケットを受け取ってからの経過時間、送信元 IP アドレス、宛先 IP アドレス、残りは通信プロトコル毎の出力です。

snoop コマンドを使用して igb1 上で port 80 番のパケットを見ると、192.168.10.2 から送信されるパケットも igb1 を通って外へ出て行っている事が分かります("->" の左側の送信元アドレスが 192.168.10.2 になっているパケットがあります)。src オプションで抽出すると、受信パケットは 192.168.10.3 から送信された物だけです。これで先ほどの dladm コマンドの出力で送信パケットが片方のインターフェイスしか使っていなかった理由が分かりました。サーバに入ってくるパケットは別々のインターフェイスを通っていますが、サーバから出て行くパケットは igb1 のインターフェイスしか使っていない様です。しかし、これでは返信パケットは片方のインターフェイスの帯域しか使用する事が出来ません。また 192.168.10.2 は igb2 に設定された IP アドレスなのに何故 igb1 から出て行っているのでしょうか。

 <<サーバ>>
 # snoop -d igb1 -o /var/tmp/snoop01.dump
 ...
 ^C
 # snoop -i /var/tmp/snoop01.dump port 80
   1   0.00000 192.168.10.3 -> 192.168.10.1 HTTP C port=42875 
   2   0.00012 192.168.10.1 -> 192.168.10.3 HTTP R port=42875 
   3   0.00010 192.168.10.3 -> 192.168.10.1 HTTP C port=42875 
   4   2.21303 192.168.10.2 -> 192.168.10.4 HTTP R port=37300 
   5  16.15477 192.168.10.3 -> 192.168.10.1 HTTP GET /001.txt HTTP/1.0
 ...
 # snoop -i /var/tmp/snoop01.dump src 192.168.10.3
   1   0.00000 192.168.10.3 -> 192.168.10.1 HTTP C port=42875 
   2   0.00023 192.168.10.3 -> 192.168.10.1 HTTP C port=42875 
   3  18.36780 192.168.10.3 -> 192.168.10.1 HTTP GET /001.txt HTTP/1.0
   4   2.85314 192.168.10.3 -> 192.168.10.1 HTTP (body)
   5   0.00014 192.168.10.3 -> 192.168.10.1 HTTP C port=42875 
 ...
 # snoop -i /var/tmp/snoop01.dump src 192.168.10.4

経路情報を確認する

送信元アドレスが 192.168.10.2 のパケットが何故 igb1 から送り出されるのかを調べる為にサーバ側でルーティングテーブルを確認します。netstat -ar コマンドの出力を見ると、クライアント A(192.168.10.3) 宛の経路もクライアント B(192.168.10.4) 宛の経路も igb1 になっている事が分かります。192.168.10.4 宛のパケットの送信元アドレスは igb2 に割り当てた 192.168.10.2 ですが、192.168.10.4 宛の経路は igb1 が選択されています。これが送信元アドレスが 192.168.10.2 のパケットが igb1 から送信されていた原因です。

 <<サーバ>>
 # netstat -ar | egrep '192.168.10.3|192.168.10.4'
 192.168.10.3             --               UHA       1          1 igb1      
 192.168.10.4             --               UHA       1          1 igb1

たまたま経路情報が偏っていたという可能性も考えられますので、念の為にルーティングのキャッシュを消去して何度も接続をテストしてみます。結果は以下の通り、何度接続し直しても経路は片方のネットワークインターフェイスにまとめられてしまいます。これが IPMP を使用しない場合の挙動です。

 <<サーバ側でルーティングテーブルのエントリを消去する>>
 # arp -d 192.168.10.3; arp -d 192.168.10.4       
 192.168.10.3 (192.168.10.3) deleted
 192.168.10.4 (192.168.10.4) deleted
 # arp -d 192.168.10.3; arp -d 192.168.10.4       
 192.168.10.3 (192.168.10.3) -- no entry
 192.168.10.4 (192.168.10.4) -- no entry
 <<再びクライアントから接続した後にルーティングテーブルを確認>>
 # netstat -ar | egrep '192.168.10.3|192.168.10.4'
 192.168.10.3             --               UHA       2          6 igb1      
 192.168.10.4             --               UHA       2          6 igb1

まとめ

単純にネットワークにインターフェイスを繋いだ場合、クライアントから送られて来たパケットは宛先 IP アドレスの通りのインターフェイスを通り、サーバから返信されるパケットは常に一つのインターフェイスだけを通る事が分かりました。言い換えれば、受信パケットは 2 つのインターフェイスの帯域を有効に利用しているのに対し、返信パケットが使用出来る帯域はポート 1 つ分しかないことになります。

[検証2] IPMP を組んだ場合の挙動

ご覧頂きました通り、ここまでの構成では返信パケットは常に片方のポートしか通りませんでした。これでは帯域の利用効率は良くありません。IPMP のアウトバウンド・ロード・スプレッディングを使用する事で両方のポートを利用する様に変更する事が可能です。見てみましょう。

構成

これまでの構成に加えて、サーバの igb1 と igb2 で IPMP を組みます。IPMP の構成は簡単です。ifconfig の group オプションに適当な IPMP グループ名を指定するだけで完了します。グループ名は ipmp としましたが、その他の文字列でも構いません。

 # ifconfig igb1 group ipmp
 # ifconfig igb2 group ipmp

IPMP を組んで ifconfig でインターフェイスの一覧を見ると groupname ipmp と表示され、インターフェイスが IPMP に参加している事が分かります。

 # ifconfig -a
 ...
 igb1: flags=1000843<UP,BROADCAST,RUNNING,MULTICAST,IPv4> mtu 1500 index 3
         inet 192.168.10.1 netmask ffffff00 broadcast 192.168.10.255
         groupname ipmp
         ether 0:14:4f:cb:16:b1 
 igb2: flags=1000843<UP,BROADCAST,RUNNING,MULTICAST,IPv4> mtu 1500 index 4
         inet 192.168.10.2 netmask ffffff00 broadcast 192.168.10.255
         groupname ipmp
         ether 0:14:4f:cb:16:b2 

HTTP で接続する

先ほどと同じ様に HTTP で接続してテストを行います。クライアント A は 192.168.10.1 に、クライアント B は 192.168.10.2 に、同時に接続します。返信パケットが負荷分散されるかどうか確認しましょう。

経路情報を確認する

netstat コマンドで経路情報を見てみると、今度はクライアント A(192.168.10.3) への接続は igb1 を、クライアント B(192.168.10.4) への接続は igb2 を通る様になっている事が分かります。IPMP を組む前は両方とも igb1 を通る様になっていましたが、IPMP を組むと別々のインターフェイスを使う様になりました。

 <<サーバ>>
 # netstat -ar | egrep '192.168.10.3|192.168.10.4'
 192.168.10.3             --               UHA       1          1 igb1      
 192.168.10.4             --               UHA       1          1 igb2

実際にどの経路を通っているかを確認する

続いて dladm コマンドを使用して、実際にどの経路を通って通信が行われているのかを見てみます。以下の出力結果を見ると、igb1 の opackets も igb2 の opackets も 0 以外の数値になっています。IPMP を組んだことにより、返信のパケットも 2 つのネットワークインターフェイスに股がって分散される様になりました。

 <<サーバ>>
 # dladm show-dev -s -i 1 igb1
                 ipackets  rbytes         ierrors opackets        obytes      oerrors
 igb1            369       23616       0       718       1067024     0       
                 ipackets  rbytes         ierrors opackets        obytes      oerrors
 igb1            384       24576       0       746       1107084     0       
                 ipackets  rbytes         ierrors opackets        obytes      oerrors
 igb1            384       24576       0       746       1104508     0
 # dladm show-dev -s -i 1 igb2
                 ipackets  rbytes         ierrors opackets        obytes      oerrors
 igb2            542       34688       0       1056      1555293     0       
                 ipackets  rbytes         ierrors opackets        obytes      oerrors
 igb2            414       26496       0       806       1189420     0       
                 ipackets  rbytes         ierrors opackets        obytes      oerrors
 igb2            452       28928       0       882       1308835     0

igb1 を snoop すると、igb1 のインターフェイスはクライアント A(192.168.10.3) と 192.168.10.1 の間の通信のみに使われている事が分かります。

 # snoop -d igb1 -o /var/tmp/snoop_igb1.dump
 # snoop -i /var/tmp/snoop_igb1.dump host 192.168.10.1
   1   0.00000 192.168.10.3 -> 192.168.10.1 HTTP C port=42878 
   2   0.00002 192.168.10.1 -> 192.168.10.3 HTTP R port=42878 
   3   0.00010 192.168.10.3 -> 192.168.10.1 HTTP C port=42878 
   4   7.98546 192.168.10.3 -> 192.168.10.1 HTTP GET /001.txt HTTP/1.0
   5   0.00001 192.168.10.1 -> 192.168.10.3 HTTP R port=42878 
   ...
 # snoop -i /var/tmp/snoop_igb1.dump host 192.168.10.2    <-- 192.168.10.2 との通信には使用されていない
 # snoop -i /var/tmp/snoop_igb1.dump host 192.168.10.3
   1   0.00000 192.168.10.3 -> 192.168.10.1 HTTP C port=42878 
   2   0.00002 192.168.10.1 -> 192.168.10.3 HTTP R port=42878 
   3   0.00010 192.168.10.3 -> 192.168.10.1 HTTP C port=42878 
   4   7.98546 192.168.10.3 -> 192.168.10.1 HTTP GET /001.txt HTTP/1.0
   5   0.00001 192.168.10.1 -> 192.168.10.3 HTTP R port=42878 
   ...
 # snoop -i /var/tmp/snoop_igb1.dump host 192.168.10.4    <-- 192.168.10.4 との通信には使用されていない

一方 igb2 はクライアント B(192.168.10.4) と 192.168.10.2 の間の通信にのみ使用されている事が分かります。

 # snoop -d igb2 -o /var/tmp/snoop_igb2.dump
 # snoop -i /var/tmp/snoop_igb2.dump host 192.168.10.1    <-- 192.168.10.1 との通信には使用されていない
 # snoop -i /var/tmp/snoop_igb2.dump host 192.168.10.2
   1   0.00000 192.168.10.4 -> 192.168.10.2 HTTP C port=37303 
   2   0.00001 192.168.10.2 -> 192.168.10.4 HTTP R port=37303 
   3   0.00009 192.168.10.4 -> 192.168.10.2 HTTP C port=37303 
   4   6.40605 192.168.10.4 -> 192.168.10.2 HTTP GET /001.txt HTTP/1.0
   5   0.00001 192.168.10.2 -> 192.168.10.4 HTTP R port=37303 
   ...
 # snoop -i /var/tmp/snoop_igb2.dump host 192.168.10.3    <-- 192.168.10.3 との通信には使用されていない
 # snoop -i /var/tmp/snoop_igb2.dump host 192.168.10.4
   1   0.00000 192.168.10.4 -> 192.168.10.2 HTTP C port=37303 
   2   0.00001 192.168.10.2 -> 192.168.10.4 HTTP R port=37303 
   3   0.00009 192.168.10.4 -> 192.168.10.2 HTTP C port=37303 
   4   6.40605 192.168.10.4 -> 192.168.10.2 HTTP GET /001.txt HTTP/1.0
   5   0.00001 192.168.10.2 -> 192.168.10.4 HTTP R port=37303
   ...

まとめ

IPMP を使用すると、サーバからの返信時もネットワーク負荷が複数のインターフェイスに分散される事が分かりました。これで複数のネットワークインターフェイスの帯域を最大限に有効利用する事が出来ます。

チューニングパラメータ

一度クライアントまでの経路が決定されると、その後の接続はキャッシュを元に経路が選択される様になります。既に見た通り、キャッシュは netstat -ar で確認、arp -d で削除する事が可能です。このキャッシュの有効期限は ip_ire_arp_interval と arp_cleanup_interval パラメータで設定する事が出来ます。もし経路に偏りが発生していた場合は有効期限を短く設定すると経路選択の機会が増えて偏りが解消されるかもしれません。ip_ire_arp_interval と arp_cleanup_interval の確認方法と設定方法は以下の通りです。

 # ndd -get /dev/ip ip_ire_arp_interval
 1200000
 # ndd -set /dev/ip ip_ire_arp_interval 60000
 # ndd -get /dev/arp arp_cleanup_interval
 300000
 # ndd -set /dev/arp arp_cleanup_interval 30000

おわりに

今回は IPMP のアウトバウンド・ロード・スプレッディング機能による負荷分散をご紹介しました。要件次第では、簡単にネットワークの実行帯域を増やす事が可能です。特にウェブサーバやファイルサーバは、IPMP のアウトバウンド・ロード・スプレッディングだけで十分かもしれません。サンのサーバの多くは最小構成でもネットワークインターフェイスを4ポート装備しています。Solaris の機能を活用し、是非それらのポートを有効利用して下さい。

補足

  • IPMP の作成は簡単でしたが、削除も簡単に出来ます。ifconfig コマンドの group オプションに空文字を指定してください。groupname エントリが消えていれば IPMP も解除されています。
  •  # ifconfig igb1 group ''
     # ifconfig igb2 group ''
    
  • IPMP のアウトバウンド・ロード・スプレッディングでどの経路を通るかはラウンドロビンで決定されます。例えば igb1, igb2 の 2 つのインターフェイスを使用した IPMP で、2 台のクライアントからコネクションが張られていた場合、最初のクライアントからの接続が igb1 を経由して返ったなら、次のクライアントからの接続は igb2 を経由して返ります。この時、クライアントからの接続を受けたインターフェイスとは別のインターフェイスを経由してクライアントに返る事もあります。また、ラウンドロビンで経路が決まるのは、コネクションが一本しか無い場合も例外ではありません。igb1 で受け取ったコネクションが igb2 から出て行くというケースも普通に発生します。その場合も、返信パケットの送信元 IP アドレスが書き変わったりする事はありません。
  • コネクション数が少ない場合は通信が片方のインターフェイスに偏る事があります。例えば 3 台のクライアントから接続があり、その内 2 台への返信パケットが igb1 を通り、残りの 1 台が igb2 を通っていた場合、igb2 を使用していたコネクションが終了すると、クライアントは 2 台あるのに、インターフェイスは片方しか使用されていない状態になります。アウトバウンド・ロード・スプレッディングは、インターフェイスの負荷を監視してロードバランスするのではなく、単純にラウンドロビンでロードスプレッドしているだけなので、インターフェイスの数に対してクライアントの数が多ければ多いほど効率よく帯域を使用する事が出来ます。
  • IPMP のアウトバウンド・ロード・スプレッディングは片方のインターフェイスにのみ IP アドレスを振った場合にも有効になります。構成の仕方は こちら をご参照下さい。以下の例では、igb2 には IP アドレスが設定されていませんが、192.168.10.4 宛の経路には igb2 が選択されています。サーバ側で受信用の帯域がそれ程必要とされておらず、使用する IP アドレスを減らしたい場合は、この構成が便利です。
  •  # ifconfig -a
     ...
     igb1: flags=1000843<UP,BROADCAST,RUNNING,MULTICAST,IPv4> mtu 1500 index 3
             inet 192.168.10.1 netmask ffffff00 broadcast 192.168.10.255
             groupname ipmp
             ether 0:14:4f:cb:16:b1 
     igb2: flags=1000843<UP,BROADCAST,RUNNING,MULTICAST,IPv4> mtu 1500 index 5
             inet 0.0.0.0 netmask ff000000 
             groupname ipmp
             ether 0:14:4f:cb:16:b2 
     # netstat -ar | egrep '192.168.10.3|192.168.10.4'
     192.168.10.3             --               UHA       2         25 igb1      
     192.168.10.4             --               UHA       2         25 igb2
    
  • アウトバウンド・ロード・スプレッディングによる負荷分散はクライアントの IP アドレス毎です。コネクション毎ではありません。
  • クライアントとサーバの間にルータを挟んだ場合もアウトバウンド・ロード・スプレッディングは有効です。パケットの送信に使われるインターフェイスはクライアントの IP アドレス毎に決定されます。以下の例はクライアント A(192.168.11.2) とクライアント B(192.168.12.2) が、ルータ (192.168.10.3) を経由して、IPMP を構成したサーバへアクセスした際の、サーバのルーティングテーブルです。クライアントへの経路が igb1 と igb2 に分かれている事が分かります。
  •  <<サーバ>>
     # netstat -ar | egrep '192.168.11.2|192.168.12.2'
     192.168.11.2         192.168.10.3         UHA       1          1 igb2      <-- ルータ経由のクライアント A
     192.168.12.2         192.168.10.3         UHA       1          1 igb1      <-- ルータ経由のクライアント B
    
  • IPMP に参加出来るポートの数は 2 つに限定されている訳ではありません。オンボードのインターフェイスを全て同一の IPMP に入れる事も可能ですし、もっと多くのポートを参加させる事も可能です。
  • arp -d で ARP のエントリを削除するとルーティングの情報も削除され、即座に経路が変更されます。netstat -ar で表示される一番上のエントリが経路として使用され、arp -d で一番最初に消去されます。
  • サーバ側からコネクションを張る場合は IP ソースアドレス・セレクションという別の機能を使って負荷分散されます。サーバがクライアントにコネクションを張る際に、送信元 IP アドレスを自分の複数のインターフェイスからラウンドロビンで決定する事で、クライアントが返信する先の IP アドレスを操作する事が出来、結果的にサーバが受信する際の負荷分散になります。これは IPMP の機能ではなく IP の機能です。コネクションを張る時に IP モジュールより上位で送信元 IP アドレスが設定されていない事が条件です。
  • IPMP のアウトバウンド・ロード・スプレッディングはリンクアグリゲーション (dladm(1M)) と併用する事も可能です。また、OpenSolaris の ILB(Integrated Load Balancer) と組み合わせて使うと更に便利かもしれません。

参考文献

http://blogs.sun.com/yappri/date/20091028 2009年 10月 28日 水曜日

Calendar Server 7 を iPhone から使ってみよう

サンは今年9月末にCommunications Suite 7 を新しく発表しました。

今回は、その Communications Suite 7 に含まれる新しい CalDAV 対応カレンダサーバである Calendar Server 7 をインストール・設定し、iPhone, Thunderbird Lightining から利用してみましょう。

Calendar Server 7 のインストールシナリオを確認すると、カレンダサーバのインストールには下記の手順が必要になります。

  1. Directory Server のインストール
  2. GlassFish Enterprise Server 2.1 のインストール
  3. Calendar Server と MySQL のインストール
  4. MySQL の設定
  5. Calendar Server の設定

また、Calendar Server のユーザ管理には Delegated Administrator が使えます。

Delegated Administrator では、Access Manager を利用する Schema 2 を利用する構成方法と、Access Manager を使わず Directory Server だけで利用できる Schema 1 を利用する構成方法があります。

今回は単純に Calendar Server のみ使えれば良いので、Scheme 1 を利用する設定にて Delegated Administrator をインストールしましょう。

  1. ソフトウェアの入手
  2. Directory Server のインストール
  3. GlassFish Enterprise Server 2.1 のインストール
  4. Delegated Administrator のインストール
  5. Calendar Server と MySQL のインストール
  6. MySQL の設定
  7. Calendar Server の設定
  8. カレンダユーザの作成
  9. iPhone からの設定
  10. Thunderbird Lightning からの設定

1. ソフトウェアの入手

まずは、入手先より Communications Suite 7, GlassFish Enterprise Server 2.1 Patch 02, Directory Server 6.3.1 をダウンロードします。

sw-79# pwd
/var/tmp/Comm7
sw-79# ls
126748-05.zip                       ci-7.0-0.02-SunOS_sparc.zip
DSEE.6.3.Solaris-Sparc-full.tar.gz  sges_ee-2_1-p02-solaris-sparc.bin
sw-79# 

2. Directory Server のインストール

Direcotry Server は、Communications Suite 7 Installation Scenario - Directory Server に沿って、インストールします。

インストールログへのリンク

3. GlassFish Enterprise Server 2.1 のインストール

GlassFish も同様に、Communications Suite 7 Installation Scenario - GlassFish Enterprise Server に沿って、インストールします。

JDK6 がインストールされていない場合は、入手先よりパッケージをダウンロードしてドキュメントを参考にインストールしてください。

インストールログへのリンク

4. Delegated Administrator のインストール・設定

Delegated Administrator は、Direct LDAP モードで設定するので、下記のログを参考にインストールしてください。

インストール・設定が完了したら http://hostname:8080/da にて管理画面にログインできます。

インストールログへのリンク

5. Calendar Server と MySQL のインストール

インストールログへのリンク

6. MySQL の設定

設定ログへのリンク

7. Calendar Server の設定

設定ログへのリンク

8. カレンダユーザの作成

DAの管理画面(http://hostname:8080/da)より、サービスパッケージを割り当ててユーザを作成しましょう。

まず、組織にサービスパッケージを割り当てます。

次に、サービスパッケージを割り当てたユーザを作成します。

9. iPhone からの設定

※ クライアント設定はこのドキュメントが参考になります。

設定 -> メール -> アカウントを追加を選びます。

その他を選びます。

CalDAV アカウントを追加を選びます。

サーバ欄にアカウント URL を設定・ユーザ名とパスワードを設定します。

※ 今回のテストではアカウント URL は、http://sw-79.japan.sun.com:8080/davserver/dav/principals/japan.sun.com/test1/ になります。

サーバ欄には、URL では無く、サーバ名が表示されます。

詳細設定を確認すると、ポート番号と URL が入力されている事を確認できます。

カレンダアプリを起動するとサーバが追加されていることが確認できます。

テストの予定を追加してみましょう。

10. Thunderbird Lightning からの設定

※ クライアント設定はこのドキュメントが参考になります。

Lightning から新しいカレンダを登録します。

CalDAV を選んで、URL を入力します。

※ 今回のテストではアカウント URL は、http://sw-79.japan.sun.com:8080/davserver/dav/home/test1@japan.sun.com/calendar になります。

設定を終えると、ユーザ名とパスワードを入力するウィンドウが開きます。

ただしく、ログインできると iPhone から入力したテストの予定が確認できます。

まとめ

以上で設定は終了です。

このように、新しいカレンダサーバは標準規格である CalDAV をサポートしているため、今回設定した iPhone, Lightning のほかにも Apple の iCal など多様なクライアントから利用可能です。

ぜひ新しい、カレンダサーバをお試し下さい!

http://blogs.sun.com/yappri/date/20091027 2009年 10月 27日 火曜日

User/Group単位で ZFS の quota を設定する方法

今回は、前回に引き続き Solaris 10 10/09(Update 8) でサポートされた ZFS 新機能の関連記事で、ZFS の quota 管理がユーザ/グループ単位で設定できる ようになりましたので紹介します。

今まで ZFS のファイルシステムに対する quota による容量制限は、 プール内のファイルシステム単位という制約があり、 管理者がユーザ/グループ単位で容量制限を行うには、ユーザ/グループ毎に それぞれファイルシステムを作成するしか方法がありませんでした。
しかし、この方法では多くのユーザがいる場合、ファイルシステムの数が多く なってしまい、管理の面であまり実現的ではありませんでした。

今までの quota 設定方法(ファイルシステム単位)
# zfs set quota=10g tank/home


今回、ユーザ/グループ単位で ZFS ファイルシステムの quota を設定できるように なりましたので、その手順を紹介します。

ユーザによる quota 設定方法
# zfs set userquota@user01=100m tank/home

# zfs get userquota@user01
NAME       PROPERTY          VALUE             SOURCE 
tank       userquota@user01  none              local 
tank/home  userquota@user01  100M              local 

グループによる quota 設定方法
# zfs set groupquota@staff=500m tank/home

# zfs get groupquota@staff
NAME       PROPERTY          VALUE             SOURCE 
tank       groupquota@staff  none              local 
tank/home  groupquota@staff  500M              local

最後に、 quota 動作の確認として、ユーザ単位で quota 制限がかかって いるか mkfile を使って確認してみます。
今回は、上記で設定で user01 に 100MB の quota 制限を設定しましたので、 user01 で 150MB のファイルを作成してみます。
% /usr/ucb/whoami
user01 
 
% mkfile 150m /tank/home/aaa
aaa: initialized 111108096 of 157286400 bytes: Disc quota exceeded 

「ユーザ単位の quota 制限が発生しました」 とメッセージが出てこないのは 残念ですが、きちんとユーザ単位で quota 制限が設定されている事が確認できました。


(参考情報)
過去の 「やっぱり Sun がスキ!」blog 記事一覧はこちらを参照下さい。 http://wikis.sun.com/display/yappri/Home


http://blogs.sun.com/yappri/date/20091026 2009年 10月 26日 月曜日

Solaris 10 で ZFS の Hybrid Storage Pool を作る方法

今回は、今月リリースした Solaris 10 10/09(Update 8) でサポートされた ZFS の L2ARC(Level 2 Adaptive Replacement Cache) 設定方法を紹介します。

L2ARC とは、ZFS Hybrid Storage Pool の Read 用二次キャッシュとして機能 し、通常 SSD が使用され Read 速度の向上を促します。




今まで Solaris 10 で ZFS のプールをする場合、Write 速度の向上させる ZIL(ZFS Intent Log)は作成できましたが、L2ARC を作成する事ができませんでした。
(L2ARC を使用したい場合は OpenSolaris を選択するしかありませんでした。)

Solaris 10 が 10/09 になり、L2ARC がサポートされました ので、これで Solaris 10 上で完全な Hybrid Storage Pool を作成できるように なりました。

それでは早速 Hybrid Storage Pool を作ってみましょう。とっても簡単です。
作成方法は、ZFS pool 作成時のオプションに ZIL を log で指定、L2ARC を cache で指定するだけです。
(通常、log と cache のデバイスは SSD を使用します。)

# zpool create tank raidz c2t0d0 c2t1d0 c2t2d0 c2t3d0 log c2t4d0 cache c2t5d0
# zpool status 
  プール: tank 
 状態: ONLINE 
 スクラブ: 何も要求されませんでした 
構成: 
 
        NAME        STATE     READ WRITE CKSUM 
        tank        ONLINE       0     0     0 
          raidz1    ONLINE       0     0     0 
            c2t0d0  ONLINE       0     0     0 
            c2t1d0  ONLINE       0     0     0 
            c2t2d0  ONLINE       0     0     0 
            c2t3d0  ONLINE       0     0     0 
        logs 
          c2t4d0    ONLINE       0     0     0 
        cache 
          c2t5d0    ONLINE       0     0     0 
 
エラー: 既知のデータエラーはありません

はい、これで Hybrid Storage Pool が作成できました。logs(ZIL) と cache(L2ARC) にデバイスが 割り当てられております。

ちなみに、ZIL をミラーしたい場合は以下のように指定します。

# zpool create tank raidz c2t0d0 c2t1d0 c2t2d0 c2t3d0 log mirror c2t4d0 c2t5d0 
# zpool status 
  プール: tank 
 状態: ONLINE 
 スクラブ: 何も要求されませんでした 
構成: 
 
        NAME        STATE     READ WRITE CKSUM 
        tank        ONLINE       0     0     0 
          raidz1    ONLINE       0     0     0 
            c2t0d0  ONLINE       0     0     0 
            c2t1d0  ONLINE       0     0     0 
            c2t2d0  ONLINE       0     0     0 
            c2t3d0  ONLINE       0     0     0 
        logs 
          mirror    ONLINE       0     0     0 
            c2t4d0  ONLINE       0     0     0 
            c2t5d0  ONLINE       0     0     0 
 
エラー: 既知のデータエラーはありません

L2ARC に SSD を使用する事により、 DRAM だけでキャッシュを構築する場合と比較して 大容量で低価格な構成を提案できるようになります。
Read が多い NFS サーバには最適ですね。



(参考情報)
過去の 「やっぱり Sun がスキ!」blog 記事一覧はこちらを参照下さい。 http://wikis.sun.com/display/yappri/Home


http://blogs.sun.com/yappri/date/20090928 2009年 9月 28日 月曜日

OpenSolaris 2009.06 で Zone を作成する

今回は、OpenSolaris 2009.06 で Solarisコンテナを作成する手順を纏めてみましたので 紹介します。

今まで、「やっぱり Sun がスキ!」ブログでは、Solaris 10 ベースの Solarisコンテナに 関する TIPS をいろいろ紹介して来ましたが、OpenSolaris 2009.06 では Solaris 10 と 少し変わっていますので、Solaris 10 との違いを中心に説明していきます。

ステップ1 zonepath 用のディレクトリを作成する

Zone 作成時に一番重要なプロパティは zonepath ですが、Solaris 10 の ように適当なディレクトリを mkdir で作成して zonepath に設定してはいけません。
OpenSolaris 2009.06 では、zonepath に ZFS のデータセット名を指定する 必要があります。例えば、zonepath に /rpool/zones/test-zone と指定したい 場合は、あらかじめ下記の設定が必要になります。

# zfs create rpool/zones
# zfs create rpool/zones/test-zone
# chmod 700 /rpool/zones/test-zone

ステップ2 Zone の作成を行う

次に zonecfg で Zone名 test-zone の作成を行います。
# zonecfg -z test-zone
test-zone: No such zone configured
Use 'create' to begin configuring a new zone.
zonecfg:test-zone> create
zonecfg:test-zone> set zonepath=/rpool/zones/test-zone

ここで設定値を確認してみます。
zonecfg:test-zone> info
zonename: test-zone
zonepath: /rpool/zones/test-zone
brand: ipkg
autoboot: false
bootargs: 
pool: 
limitpriv: 
scheduling-class: 
ip-type: shared
hostid: 

info の出力結果で注目する箇所は、Solaris 10 と違いブランド名が native でなく ipkg 形式に なっている事です。従いまして、inherit-pkg-dir のプロパティも無くなっています。

zonecfg の設定を保存した後、zone の状態を確認してみます。
# zoneadm list -cv
  ID NAME             STATUS     PATH                           BRAND    IP    
   0 global           running    /                              native   shared
   - test-zone        configured /rpool/zones/test-zone         ipkg     shared
test-zone のブランドが native でなく ipkg になっています。

ステップ3 Zone のインストールを行う

Zone の作成が完了しましたので、インストールを行います。
# zoneadm -z test-zone install
   Publisher: Using opensolaris.org (http://pkg.opensolaris.org/release/).
       Image: Preparing at /rpool/zones/test-zone/root.
Sanity Check: Looking for 'entire' incorporation.
  Installing: Core System (output follows)
DOWNLOAD                                    PKGS       FILES     XFER (MB)
Completed                                  20/20   3021/3021   42.55/42.55 

PHASE                                        ACTIONS
Install Phase                              5747/5747 
  Installing: Additional Packages (output follows)
DOWNLOAD                                    PKGS       FILES     XFER (MB)
Completed                                  37/37   5598/5598   32.52/32.52 

PHASE                                        ACTIONS
Install Phase                              7329/7329 

        Note: Man pages can be obtained by installing SUNWman
 Postinstall: Copying SMF seed repository ... done.
 Postinstall: Applying workarounds.
        Done: Installation completed in 1075.241 seconds.

  Next Steps: Boot the zone, then log into the zone console
             (zlogin -C) to complete the configuration process

Zone のインストールを実行すると、パッケージのリポジトリからパッケージのダウンロードが開始されます。
そして、ログにはインストールにかかった時間が表示されるようになりました。
インストールが完了すれば、後は Solaris 10 と同様に Zone をブートして使用可能になります。
# zoneadm -z test-zone boot
# zlogin -C test-zone

おまけ Zone のクローンを作成する

せっかく zonepath が ZFS ベースなので、Solarisコンテナのクローン機能が ZFS のクローンと 連携出来ているか確認してみました。
# zoneadm -z test-zone halt
# zonecfg -z test-zone export -f /var/tmp/clone-zone.cfg
export したファイル内の zonepath を /rpool/zones/test-zone から /rpool/zones/clone-zone に 変更します。
# zonecfg -z clone-zone -f /var/tmp/clone-zone.cfg
# zoneadm -z clone-zone clone test-zone
sys-unconfig started 2009年09月27日 01時29分53秒
rm: cannot remove `/rpool/zones/clone-zone/root/etc/vfstab.sys-u': No such file or directory
grep: /rpool/zones/clone-zone/root/etc/dumpadm.conf: No such file or directory
sys-unconfig completed Sun Sep 27 01:29:53 2009
Zone のクローン作成は、即時に作成できましたので ZFS のクローンときちんと連携できているようです。
ZFS の状態を確認してみます。
# zfs list -t snapshot
NAME                                          USED  AVAIL  REFER  MOUNTPOINT
rpool/ROOT/opensolaris@install                146M      -  2.82G  -
rpool/zones/test-zone/ROOT/zbe@clone-zone_snap      0      -   341M  -

# zfs get origin | grep clone
rpool/zones/clone-zone                       origin    -                                            -
rpool/zones/clone-zone/ROOT                  origin    -                                            -
rpool/zones/clone-zone/ROOT/zbe              origin    rpool/zones/test-zone/ROOT/zbe@clone-zone_snap  -
rpool/zones/test-zone/ROOT/zbe@clone-zone_snap  origin    - 

Zone と ZFS の連携って素敵です!

http://blogs.sun.com/yappri/date/20090918 2009年 9月 18日 金曜日

DTrace を使ってロックの競合を調査する

はじめに

今回は DTrace を使用してロックの競合を調査する初歩的な手順をご紹介したいと思います。DTrace には豊富なプローブが用意されていますので、アイデアさえあれば無限にスクリプトを作成することが可能です。その全てを解説することは難しいですが、DTrace を使ってマルチスレッドプログラムの動きを調べる際の基礎となるスクリプトを幾つかご紹介したいと思います。

plockstat コマンドの実装から

先日ご紹介した plockstat コマンド はその機能の殆どを DTrace を利用して実装しています。OpenSolaris.org にある plockstat コマンドの ソースコード を見てみると、1000 行ほどのコードの内の 300 行ほどが DTrace スクリプトの定義で、残りはヘルパー関数になっています。実は、この plockstat コマンドに含まれている DTrace スクリプトは plockstat コマンドと独立して使用することも可能です。まず始めに、その使い方から見ていきましょう。

mutex ロックの競合を調査する DTrace スクリプト

plockstat コマンドのソースコードから mutex ロックの競合を検知している部分を抜き出して、単独で実行できる様に修正したのが下記のスクリプトです。plockstat$target::: で始まるプローブが並んでいますが、plockstat コマンドに含まれるスクリプトは DTrace の plockstat プロバイダの機能を利用しています。plockstat プロバイダは mutex ロックと reader/writer ロックを監視する為の DTrace のプロバイダです。plockstat コマンドは plockstat プロバイダの機能を全面的に使用しているため、plockstat コマンドは plockstat プロバイダのラッパーと言って良いかもしれません。続いて個々のプローブを見ていきましょう。plockstat$target:::mutex-block はスレッドが mutex ロックでブロックされる直前に呼び出されるプローブです。一方 plockstat$target:::mutex-blocked はブロックから解放された直後に呼び出されますので、mutex-block と mutex-blocked で timestamp の差分を取ると mutex ロックでブロックされていた時間を割り出す事が出来ます。なお、plockstat$target:::mutex-blocked の arg1 が 0 の場合は何らかのエラーで mutex ロックが取得できなかったことを意味するため、ブロックされていた時間には加算されていません。

 #!/usr/sbin/dtrace -s
 
 plockstat$target:::mutex-block
 {
   self->mtxblock[arg0] = timestamp;
 }
 
 plockstat$target:::mutex-blocked
 /self->mtxblock[arg0] && arg1 != 0/  /* arg1 is not 0 => lock is acquired */
 {
   @mtx_block[arg0, ustack(5)] =
     sum(timestamp - self->mtxblock[arg0]);
   @mtx_block_count[arg0, ustack(5)] = count();
   self->mtxblock[arg0] = 0;
   mtx_block_found = 1;
 }
 
 plockstat$target:::mutex-blocked
 /self->mtxblock[arg0]/               /* arg1 is 0 => error */
 {
   self->mtxblock[arg0] = 0;
 }
 
 END
 /mtx_block_found/
 {
   trace("Mutex block");
   printa(@mtx_block, @mtx_block_count);
 }

実際に動かしてみる

このスクリプトを先日の plockstat の記事 で使用したテストプログラムに対して実行してみた結果が以下の出力です。上から、"134614312" が plockstat コマンドの "Lock" のカラムに相当する mutex ロックのアドレス、次のスタックフレームが "Caller" のカラムを作成する為のデータです。"30002403544" は mutex ロックでブロックされていた総時間です。最後の "3" は plockstat コマンドの "Count" のカラムに相当するブロックされた回数です。先ほどの総時間をブロックされた回数で割ると、ブロックされた平均時間となり plockstat コマンドの "nsec" のカラムが求まります (30002403544 nsec / 3 Count = 10000801181 nsec)。きちんと plockstat コマンドに相当する情報が得られている事が分かります。

 # ./plockstat_mb.d -c ./mutex_test   <-- トレース対象のプログラムは -c オプションで指定します
 dtrace: script './plockstat_mb.d' matched 7 probes
 done
 done
 done
 dtrace: pid 9964 has exited
 CPU     ID                    FUNCTION:NAME
   1      2                             :END   Mutex block                      
         134614312  <-- "Lock"
               libc.so.1`mutex_lock_queue+0x24a
               libc.so.1`mutex_lock_impl+0x102
               libc.so.1`mutex_lock+0x1a
               mutex_test`func+0x13   <-- "Caller"
               libc.so.1`_thr_setup+0x4e
       30002403544                3  <-- "Count"
       ~~~~~~~~~~~ total time blocked

スレッドの毎に統計情報をまとめるスクリプト

先ほどのスクリプトを少しだけ書き換えて、ブロックされていた時間をスレッド毎に集計するスクリプトを作ってみました。下記のスクリプトを実行すると、プロセスを 10 秒間監視し、ブロックが発生した回数とブロックされていた時間をスレッド単位で出力します。先ほどのスクリプトからの変更点は、@mtx_block, @mtx_block_count 集積体のキーに tid を使用して、スレッド ID 毎に情報を集計しています。また、ブロックされた時間を保持していた self->mtxblock はスレッド毎に一つの値だけを保持すれば良いので、単純なスレッド固有変数にしてあります。tick-1sec は DTrace の profile プロバイダのプローブで、1 秒毎に呼び出されます。ここでは 10 秒間を数えるのに使用しています。

 #!/usr/sbin/dtrace -qs
 
 int ntick;
 
 BEGIN
 {
   ntick = 0;
 }
 
 plockstat$target:::mutex-block
 {
   self->mtxblock = timestamp;
 }
 
 plockstat$target:::mutex-blocked
 /self->mtxblock && arg1 != 0/  /* arg1 is not 0 => lock is acquired */
 {
   @mtx_block[tid]= sum(timestamp - self->mtxblock);
   @mtx_block_count[tid] = count();
   self->mtxblock = 0;
 }
 
 plockstat$target:::mutex-blocked
 /self->mtxblock/               /* arg1 is 0 => error */
 {
   self->mtxblock = 0;
 }
 
 tick-1sec
 {
   ntick += 1;
 }
 
 tick-1sec
 /ntick == 10/
 {
   trace("Count");
   printa(@mtx_block_count);
 
   trace("nsec");
   printa(@mtx_block);
 
   exit(0);
 }

DTrace スクリプトの実行結果

書き換えたスクリプトを plockstat コマンドの解説で使用した general.c で試してみます。Count 以下がブロックされた回数を表しており、nsec 以下がブロックされた時間を表しています。いずれも測定時間 10 秒辺りの結果です。Count の結果は、左側のカラムがスレッド ID、右側のカラムがブロックされた回数です。スレッド ID が 8 番のスレッドは 10 秒の間に 15197 回ブロックされていた事が分かります。nsec の結果も左のカラムはスレッド ID です。右側のカラムはナノ秒単位でブロックされていた総時間が出力されます。ここではスレッド ID 5 番のスレッドが 10 秒間のうち 5.8 秒間ブロックされていたことが見て取れます。

 # ./plockstat_mbt.d -c ./general
 Count
         8            15197
         2            15395
         3            15397
         7            15418
         6            15480
         4            15604
         9            15633
         5            16011
 nsec
         5       5849251426
         6       5891593031
         9       5904264724
         4       5912264400
         3       5953877535
         2       5983468423
         7       6004800234
         8       6067032588

plockstat プロバイダでトレースできないケースへの対応

plockstat プロバイダはその名の通り mutex ロックと reader/writer ロックを監視対象としており、条件変数でブロックされている場合は監視する事が出来ません。マルチスレッドプログラムでは条件変数も良く利用されていますので、mutex ロックや reader/writer ロックと同様に監視できると便利です。

条件変数のテストプログラム

以下のテストプログラムでは、各スレッドは main() 関数の中で res がインクリメントされるまで条件変数でブロックされます。

 /*
  * cv_test.c : a test program for cv tracing.
  *   compile : cc cv_test.c -o cv_test
  */
 #include <stdio.h>
 #include <unistd.h>
 #include <atomic.h>
 #include <pthread.h>
 
 void *func();
 
 volatile uint_t res = 0;
 pthread_mutex_t mp;
 pthread_cond_t cv;
 
 int main() {
 
   pthread_t tid1, tid2;
 
   pthread_mutex_init(&mp, NULL);
   pthread_cond_init(&cv, NULL);
   pthread_create(&tid1, NULL, func, NULL);
   pthread_create(&tid2, NULL, func, NULL);
 
   sleep(10);
   atomic_inc_uint(&res);
 
   pthread_mutex_lock(&mp);
   pthread_cond_broadcast(&cv);
   pthread_mutex_unlock(&mp);
 
   pthread_join(tid1, NULL);
   pthread_join(tid2, NULL);
 
   pthread_mutex_destroy(&mp);
   pthread_cond_destroy(&cv);
 
   exit(0);
 }
 
 void *func() {
 
   pthread_mutex_lock(&mp);
   while(res == 0) {
     pthread_cond_wait(&cv, &mp);
   }
   puts("done");
   pthread_mutex_unlock(&mp);
 
   pthread_exit(0);
 }

コンパイル

上記のテストプログラムを cv_test.c という名前で保存し、以下の様にコンパイルして下さい。

 # gcc cv_test.c -o cv_test

prstat で調査する

prstat で見てみると LWPID 1 のスレッドがスリープ ("SLP") し、LWPID 2 と 3 のスレッドがロック待ち ("LCK") しているのが分かります。

 # ./cv_test&; prstat -mL -p $!
    PID USERNAME USR SYS TRP TFL DFL LCK SLP LAT VCX ICX SCL SIG PROCESS/LWPID 
  10189 root     0.0 0.0 0.0 0.0 0.0 100 0.0 0.0   0   0   0   0 cv_test/3
  10189 root     0.0 0.0 0.0 0.0 0.0 100 0.0 0.0   0   0   0   0 cv_test/2
  10189 root     0.0 0.0 0.0 0.0 0.0 0.0 100 0.0   0   0   0   0 cv_test/1

plockstat で調査する

cv_test プログラムを plockstat コマンドと共に実行した結果が以下です。prstat コマンドではロック待ちをしていることが明らかに見て取れましたが、plockstat コマンドではロックの競合についての情報は一切出力されていません。

 # plockstat -Cv ./cv_test
 plockstat: tracing enabled for pid 19839
 done
 done
 plockstat: pid 19839 has exited

条件変数を調査する DTrace スクリプト

plockstat コマンドの代わりに以下の様な DTrace スクリプトを作成すれば条件変数を捕捉することが可能です。DTrace の pid プロバイダを使用して pthread_cond_wait() 関数の出入りを監視しています。同様に pthread_cond_timedwait() や pthread_cond_reltimedwait_np() についてスクリプトを作成すれば、汎用的に条件変数を監視することが可能です。

 #!/usr/sbin/dtrace -s
 
 pid$target::pthread_cond_wait:entry
 {
  self->start = timestamp;
  self->addr = arg0;
 }
 
 pid$target::pthread_cond_wait:return
 /self->start/
 {
  @time[tid,self->addr] = sum(timestamp - self->start);
  @cnt[tid,self->addr] = count();
  self->start = 0;
 }
 
 END
 {
   printf("¥n");
   printf("¥n");
   printf("tid¥tcv address¥ttime(nsec)¥n");
   printa("%d¥t%p¥t¥t%@u¥n", @time);
 
   printf("¥n");
   printf("tid¥tcv address¥tcount¥n");
   printa("%d¥t%p¥t¥t%@u¥n", @cnt);
 }

DTrace スクリプトの実行結果

上記スクリプトを cv.d という名前で保存し、実行権限を付けて、以下の様に実行して下さい。結果を見ると、tid 2 のスレッドの time(nsec) が 10000060220 (=> 10 sec)、tid 3 のスレッドの time(nsec) が 10000693754 (=> 10 sec) なので、各スレッドがそれぞれ 10 秒程ずつ待たされている事が分かります。また、tid 2, tid 3 のスレッドの count が 1 なので、両スレッドとも 1 回ずつ pthread_cond_wait() で待たされていたことになります。

 # chmod +x ./cv.d
 # ./cv.d -c ./cv_test      <-- トレース対象のプログラムは -c オプションで指定します
 dtrace: script './cv.d' matched 3 probes
 done
 done
 dtrace: pid 19939 has exited
 CPU     ID                    FUNCTION:NAME
   6      2                             :END 
 
 tid     cv address      time(nsec)      <-- pthread_cond_wait() で待った時間を条件変数とスレッド毎に集計
 2       8060fb8         10000060220
 3       8060fb8         10000693754
 
 tid     cv address      count      <-- pthread_cond_wait() で待った回数を条件変数とスレッド毎に集計
 2       8060fb8         1
 3       8060fb8         1

おわりに

以上、DTrace を使用してロックの競合を監視する基本的な方法をご紹介しました。ご覧頂きました通り、plockstat コマンドは DTrace の plockstat プロバイダを全面的に使用して実装されています。また、plockstat コマンドに含まれる DTrace スクリプトは DTrace のスクリプトとして単独でも使用可能であることも見て頂きました。更に、plockstat プロバイダでは捕捉できない条件変数のブロックを DTrace の pid プロバイダを使用したスクリプトで監視する方法もご紹介しました。DTrace にはここで紹介した以外にも proc プロバイダの lwp-create, lwp-start, lwp-exit と言ったプローブや、mutex_owned(), rw_write_held() 等のサブルーチンが備わっており、マルチスレッドプログラムの解析には非常に有用なツールです。是非ご活用下さい。

関連文書

マニュアル

plockstat プロバイダのマニュアル

『マルチスレッドのプログラミング』

ソースコード

plockstat コマンドのソースコード

plockstat プロバイダのソースコード

DTrace の解説

http://blogs.sun.com/yappri/date/20090917 2009年 9月 17日 木曜日

plockstat コマンドでロックの競合を調べる

はじめに

今回は Solaris の plockstat(1M) コマンドを使ってユーザプロセスのロックの競合を解析する方法をご紹介したいと思います。大規模 SMP マシンや CMT マシンで長く運用されて来た歴史柄、Solaris は CPU スケーラビリティに非常に気を使っている OS です。カーネルサービスがスケールするだけでなく、ユーザプロセスの処理の並列度が十分に上がらない場合の分析ツールも揃っています。今回紹介する plockstat もそういったツールの一つで、プロセス内のロックを解析する為のコマンドです。plockstat コマンドを使うことで、プロセス内のロックの取得と競合の回数や原因を調査する事が可能です。

plockstat の 'p' はプロセスの 'p' なので私は「ぴーろっくすたっと」と読んでいますが、'p' が付かない lockstat というコマンドもあり、こちらはカーネル内のロックを観測するコマンドです。どちらもベンチマークやトラブルシューティングでは非常に良く使用するコマンドですので、是非ご活用頂ければと思います。

plockstat コマンドについて

plockstat コマンドを使用すると、プロセス内のロックの『取得』と『競合』の発生を監視できます。ロックの取得では、1) 取得されたロックのアドレス、2) プログラムのどの部分からロックが取得されたか、3) そのロックが取得された回数、4) そのロックが解放されるまでの平均時間、を見る事が出来ます。一方ロックの競合は、1) 競合が発生しているロックのアドレス、2) プログラムのどの部分がロックの競合を発生させているか、3) そのロックで何回競合が発生したか、4) そのロックでブロックされていた平均時間、を見る事が出来ます。特にロックの競合(あるスレッドが保持しているロックを別のスレッドが取得しようとすること)はプログラムの実行性能に直結する、とても重要な要素です。その為、今回の記事ではロックの競合に重点を置いて見て行きたいと思います。

なお、plockstat コマンドで観察できるロックの種類は mutex ロックと reader/writer ロックです。ロックの競合は『コンテンション』と呼ばれる場合もあります。ロックの競合が発生して待たされている状態を『ロック待ち』と呼んだり、『(スレッドがロックで)ブロックされている』と表現する事があります。

plockstat コマンドの使い方

plockstat コマンドを使用する前に

plockstat はプロセス内のロックを観察するコマンドです。plockstat コマンドを使用する前にどのプロセスでロックの競合が発生しているかを確認しておく必要があります。ロックの競合が発生しているプロセスは prstat(1M) コマンドで探す事が出来ます。prstat コマンドには "-mL 1" オプションを付けて実行して下さい。"-m" オプションがロック待ちを検出する為のオプションです。prstat コマンドの出力の "LCK" のカラムがロックの競合で待たされている時間で、単位は % です。スレッド数やアプリケーションの実装にもよりますが、この値が定常的に高い値を示していたらロックの競合を気にした方が良いかもしれません。LCK が高くなっているプロセスを見つけたら、プロセス名とプロセス ID を調べてください。LCK の値が高くなっている行の "PROCESS" が該当プロセスのプロセス名、"PID" がプロセス ID、"LWPID" がスレッド ID です。

以下は prstat コマンドを実行した例です。ここでは "general" というプロセスのスレッド ID 2 番から 9 番のスレッドで約 50% のロック待ちが発生しています。

 # prstat -mL 1
    PID USERNAME USR SYS TRP TFL DFL LCK SLP LAT VCX ICX SCL SIG PROCESS/LWPID 
  20904 root      48 4.0 0.0 0.0 0.0  47 0.0 1.1  3K   6  7K   0 general/6
  20904 root      45 3.8 0.0 0.0 0.0  50 0.0 1.1  3K  20  7K   0 general/5
  20904 root      45 3.9 0.0 0.0 0.0  50 0.0 1.2  3K  28  7K   0 general/3
  20904 root      45 3.8 0.0 0.0 0.0  50 0.0 1.2  3K  32  7K   0 general/7
  20904 root      45 3.8 0.0 0.0 0.0  51 0.0 1.2  3K  18  7K   0 general/2
  20904 root      44 3.8 0.0 0.0 0.0  51 0.0 1.2  3K  12  6K   0 general/8
  20904 root      44 3.7 0.0 0.0 0.0  51 0.0 1.2  3K  11  7K   0 general/4
  20904 root      44 3.8 0.0 0.0 0.0  51 0.0 1.2  3K  12  7K   0 general/9
  20903 root     0.1 0.2 0.0 0.0 0.0 0.0 100 0.0  52   0 365   0 prstat/1

prstat コマンドを実行しても "LCK" が出ていない場合はプロセスのロックの競合は問題ない可能性があります。他に問題が無いか検討してみてください。例えばカーネル内のロックが問題である場合は lockstat コマンドを試してみて下さい。また、"LCK" が出ていてもロックが最優先の課題では無い場合もあります。例えば vmstat コマンドで CPU の id が 0 の場合はたとえロックの競合を解消できたとしてもこれ以上は性能が上がらないかもしれません。その場合は CPU を追加する等の処置が必要です。どんな症状の時にロックが問題であるかの判別手順は、別の機会に改めてご紹介したいと思います。

一般的な使い方

plockstat コマンドの基本的な使い方は "plockstat -Cve 5 -x bufsize=10k -x aggsize=2m -p <PID>" です。-C オプションはロックの競合の発生を調べる為の基本オプションです。-v オプションは verbose オプションで、トレースの開始を出力する為に付けています。-e <SEC> で plockstat コマンドを実行する時間を指定します。"-e 5" と指定すると 5 秒間計測します。計測が終わると plockstat コマンドは終了しますが、測定対象のプロセスはそのまま稼働を続けます。-e オプションを付け無い場合は、プログラムが終了するまでトレースを続けます。-x bufsize=<SIZE> -x aggsize=<SIZE> でコマンドの内部バッファを増やしています。このオプションが無くても問題の無いことが多いですが、取得する統計情報の量が大き過ぎて溢れてしまうことを予防する為に付けています。-p <PID> で解析対象のプロセスを指定します。<PID> には解析したいプロセスのプロセス ID を指定します。

 # plockstat -Cve 5 -x bufsize=10k -x aggsize=2m -p <PID>
 ... -C は競合を調査
 ... -v は冗長出力
 ... -e <SEC> は plockstat コマンドの実行時間
 ... -x bufsize=<SIZE> -x aggsize=<SIZE> は内部バッファの拡大
 ... -p <PID> は調査対象のプロセスを指定

ロック発生時のスタックフレームを記録する

plockstat コマンドではロックが発生した際のスタックフレームを記録する事も出来ます。ロックに至るまでの関数コールの履歴を見ることで、ロックの発生原因を詳細に特定することが可能になります。スタックフレームの保存には -s <DEPTH> オプションを付けます。

 plockstat -Cvs 5 -p <PID>
 ... -s <DEPTH> で保存するスタックの深さを指定

出力行数を制限したい場合は

出力行数が多すぎてログが見にくい場合は -n オプションを付けてください。

 # plockstat -Cve 5 -n 10 -p <PID>
 ... -n オプションで出力する行数を制限

プログラム実行時に plockstat コマンドを使用する

plockstat コマンドの引数にプログラムを指定する事も出来ます。実行時間が短いプログラムや、プログラム開始時のロックを観測したい場合に便利です。<COMMAND> には調査したいコマンドを指定します。

 # plockstat -Cv -x bufsize=10k -x aggsize=2m <COMMAND>
 ... コマンドの最後にプログラム名を指定すると、そのプログラムを実行

ロックの取得を観測する

ロックの競合ではなくロックの取得を監視したい場合は -H オプションを付けます。ロックの取得は競合に比べて発生頻度が高いため、plockstat コマンドの実行負荷が高くなる事があり、それがプログラムの実行に影響を与えてしまう事があります。注意して使用してください。

 # plockstat -Hve 5 -p <PID>
 ... -H オプションでロックの『取得』を観測

全ての情報を一度に取得したい場合は…

ロックの競合と取得の両方を監視したい場合は -A オプションを付けます。-A オプションは -H オプション以上に実行負荷が高いので注意して使用してください。

 # plockstat -Ave 5 -p <PID>
 ... -A オプションでロックの『取得』と『競合』の両方を観測

plockstat コマンドの使用例 その1

実際に plockstat コマンドを使用する例をテスト用のプログラムを使ってご覧頂きます。

mutex ロックのテストプログラム

以下のテストプログラムでは main() 関数を実行しているスレッドが mutex ロックを保持した状態で 10 秒間休眠し、それ以外のスレッドはその間ブロックされます。これが競合が発生している状態です。ロックの競合は main() のスレッドが停止している間 10 秒ほど続きます。main() のスレッドが mutex ロックを解放すると、ロックの競合は解消され、その他のスレッドがロックを取得します。各スレッドは "done" というメッセージを出力してプログラムは終了します。mutex を保持しながら sleep() したり、エラー処理を省いたりしておりますが、plockstat コマンドのテスト用に分かり易さを優先しました。ご了承下さい。

 /*
  * mutex_test.c : a test program for plockstat.
  *      compile : # cc mutex_test.c -o mutex_test
  */
 #include <stdio.h>
 #include <unistd.h>
 #include <pthread.h>
 
 void *func();
 
 pthread_mutex_t mp;
 
 int main() {
 
   pthread_t tid1, tid2, tid3;
 
   pthread_mutex_init(&mp, NULL);
 
   pthread_mutex_lock(&mp);
   pthread_create(&tid1, NULL, func, NULL);
   pthread_create(&tid2, NULL, func, NULL);
   pthread_create(&tid3, NULL, func, NULL);
 
   sleep(10);  /* you should not do this. */
   pthread_mutex_unlock(&mp);
 
   pthread_join(tid1, NULL);
   pthread_join(tid2, NULL);
   pthread_join(tid3, NULL);
 
   pthread_mutex_destroy(&mp);
 
   exit(0);
 }
 
 void *func() {
 
   pthread_mutex_lock(&mp);
   puts("done");
   pthread_mutex_unlock(&mp);
 
   pthread_exit(0);
 }

コンパイル

上記のテストプログラムを mutex_test.c というファイル名で保存し、以下の様にコンパイルして下さい。

 # gcc mutex_test.c -o mutex_test

plockstat コマンドの実行

次の様に plockstat コマンドにテストプログラムを渡してください。

 # plockstat -Cv ./mutex_test

実行結果とその見方

plockstat コマンドの実行結果は以下の様になります。一番最後の行が一番重要な行です。左から、ロックの競合が発生した回数 (Count)、競合が発生した時間の平均 (nsec)、競合したロックの名前またはアドレス (Lock)、ロックの競合が発生した関数 (Caller) を示しています。下記の例では mutex_test の実行中にロックの競合は 3 回発生しています。競合が発生してから解消されるまでの時間は平均 10 秒 (10000255945 nsec) で、競合が発生したロックは mutex_test プログラムの "mp" という変数、競合を発生させた関数は mutex_test プログラムの func() です。最後の +0x13 は func() 関数のエントリポイントから mutex_lock() までのオフセットです。以上、プログラムに記述した通りの出力が出ている事が分かります。

 # plockstat -Cv ./mutex_test
 plockstat: tracing enabled for pid 19830   <-- 冗長出力オプションによる出力
 done   <-- mutex_test プログラムによる出力
 done   <-- mutex_test プログラムによる出力
 done   <-- mutex_test プログラムによる出力
 plockstat: pid 19830 has exited
 
 Mutex block
 
 Count     nsec Lock                         Caller
 -------------------------------------------------------------------------------
     3 10000255945 mutex_test`mp                mutex_test`func+0x13

plockstat コマンドの使用例 その2

次にプログラムの並列度に関する古典的な問題のひとつである、メモリ割り当ての並列実行について見てみたいと思います。これはヒープコンテンションとも呼ばれている問題で、Solaris では後述の mtmalloc ライブラリによって既に解決されています。

テストプログラム

メモリ割り当てのテストに使用するプログラムは OpenSolaris の配布物から流用します。general.c はマルチスレッドプログラムで並列に malloc() を呼び出すマイクロベンチマークです。まずは以下の様にコンパイルしてください。

 # mkdir /var/tmp/malloc
 # cd /var/tmp/malloc
 # wget http://src.opensolaris.org/source/xref/onnv/onnv-gate/usr/src/lib/libmtmalloc/tests/general.c
 # gcc general.c -o general

prstat コマンドで調査する

まずは general プログラムの挙動を調べる為に prstat コマンドを使用します。prstat コマンドの -p オプションにプロセス ID を渡すと、そのプロセスだけの統計情報を見る事が出来ます。ここでは general プログラムのプロセス ID を渡しています。general プログラムは 8 つのスレッドを新たに作成し、それぞれのスレッドが 50% 前後の時間をロック待ちに費やしている事が分かります。

 # ./general&; prstat -mLp $! 1
    PID USERNAME USR SYS TRP TFL DFL LCK SLP LAT VCX ICX SCL SIG PROCESS/LWPID 
   9838 root      48 4.1 0.0 0.0 0.0  47 0.0 1.1  3K  13  7K   0 general/9
   9838 root      46 3.9 0.0 0.0 0.0  49 0.0 1.1  3K   3  7K   0 general/7
   9838 root      45 3.9 0.0 0.0 0.0  50 0.0 1.2  3K  17  7K   0 general/2
   9838 root      45 3.9 0.0 0.0 0.0  50 0.0 1.2  3K   9  7K   0 general/8
   9838 root      45 3.9 0.0 0.0 0.0  50 0.0 1.2  3K   8  7K   0 general/6
   9838 root      45 3.8 0.0 0.0 0.0  50 0.0 1.1  3K   6  7K   0 general/4
   9838 root      43 3.7 0.0 0.0 0.0  52 0.0 1.2  3K   6  7K   0 general/5
   9838 root      42 3.7 0.0 0.0 0.0  54 0.0 1.2  3K   9  6K   0 general/3
   9838 root     0.0 0.0 0.0 0.0 0.0 0.0 100 0.0   0   0   0   0 general/1

なお、このプログラムを実行したマシンの CPU 数は 8 つで、general プログラム実行中も CPU リソースに空き (CPU の空き = vmstat コマンドの id) がある状態です。

 # psrinfo
 0	on-line   since 09/03/2009 17:11:43
 1	on-line   since 09/03/2009 17:11:54
 2	on-line   since 09/03/2009 17:11:54
 3	on-line   since 09/03/2009 17:11:56
 4	on-line   since 09/08/2009 11:20:06
 5	on-line   since 09/08/2009 11:20:06
 6	on-line   since 09/08/2009 11:20:06
 7	on-line   since 09/08/2009 11:20:06
 # vmstat 1
  kthr      memory            page            disk          faults      cpu
  r b w   swap  free  re  mf pi po fr de sr s0 s1 s2 s3   in   sy   cs us sy id
  0 0 0 32014452 31721476 7 53 0 1  1  0  1 -0  0 -0 -0  468  568  577  0  0 100
  0 0 0 31950536 31614640 20 6030 0 0 0 0 0  0  0  0  0  671 56769 51060 45 5 50
  0 0 0 31926584 31590736 0 5955 0 0 0 0  0  0  0  0  0  561 56962 51203 45 5 50
  0 0 0 31902764 31566916 0 5977 0 0 0 0  0  0  0  0  0  550 57824 52201 45 5 50
  0 0 0 31878908 31543060 0 5943 0 0 0 0  0  0  0  0  0  606 57476 51873 45 5 50

plockstat コマンドで調査する

次に、prstat コマンドで見られたロック待ちの原因を plockstat コマンドで調べてみます。plockstat コマンドの出力から Lock のカラムを見てみると libc.so.1 の libc_malloc_lock で競合が発生している事が分かります。中段に "Mutex block" と書かれている様に、これは mutex ロックの競合です。また、 nsec と Count のカラムを見ると、平均 0.001 秒 (1033533 nsec) のロック待ちが約 6 千回発生していることが分かります。これはプロセス全体で 60 秒 (57903 count * 1033533 nsec = 59844661299 nsec => 60 sec) ほど待たされている計算になります。plockstat コマンドで測定していたのは 10 秒間ですが、スレッドが 9 つありますので、このプロセスの論理的な演算可能時間は 90 秒間です(CPU 数や他のプログラムの処理のことを考えると実質的な演算可能時間はもう少し少なくなります)。この演算可能な 90 秒のうち 60 秒は mutex のロックで待たされていると言うことになります。このプログラムはかなりの時間をロック待ちに費やしていると言って良いでしょう。

 # plockstat -Cve 10 ./general
 plockstat: tracing enabled for pid 9849
         0
 Mutex block
 
 Count     nsec Lock                         Caller
 -------------------------------------------------------------------------------
 57903  1033533 libc.so.1`libc_malloc_lock   general`be_thread+0x70

スタックフレームを観察する

ロックの原因をもう少し特定するため、plockstat コマンドに -s オプションも付けてスタックフレームを見てみます。右下の "Stack" のカラムを見ると、general の be_thread() 関数のオフセット 0x70 から libc.so.1 の malloc() 関数が呼び出されており、malloc() から mutex_lock() が呼び出されている事が分かります。このロック待ちは malloc() 関数が原因であることが分かります。これがヒープコンテンションが発生している状態です。

 # plockstat -Cve 10 -s 5 ./general
 plockstat: tracing enabled for pid 9862
         0
 Mutex block
 
 -------------------------------------------------------------------------------
 Count     nsec Lock                         Caller
 60065  1434766 libc.so.1`libc_malloc_lock   general`be_thread+0x70
 
       nsec ---- Time Distribution --- count Stack
       2048 |                        |   836 libc.so.1`mutex_lock_impl+0x102
       4096 |@@@                     |  9909 libc.so.1`mutex_lock+0x1a
       8192 |                        |  2316 libc.so.1`malloc+0x29   <-- これ
      16384 |@@                      |  7215 general`be_thread+0x70
      32768 |@@@@@@                  | 16034 
      65536 |                        |  1837 
     131072 |                        |  1223 
     262144 |@                       |  2702 
     524288 |@                       |  4505 
    1048576 |@                       |  3721 
    2097152 |                        |  2269 
    4194304 |                        |  2193 
    8388608 |@                       |  3411 
   16777216 |                        |  1671 
   33554432 |                        |   219 
   67108864 |                        |     4 

mtmalloc ライブラリ

このヒープコンテンションへの対策として、Solaris にはマルチスレッドプログラム用の mtmalloc ライブラリが用意されており、既に様々なアプリケーションで使用されています。mtmalloc の使用方法は、コンパイル時にライブラリをリンクするか、プログラム実行時に LD_PRELOAD で読み込みます。これにより malloc() 関数がマルチスレッドに最適化された malloc() に置き換わります。

mtmalloc を使用して計測

先ほどの general プログラムで mtmalloc を使用してみた所、mutex でブロックされる回数が大幅に減少しました。ロック待ちをしていた時間は、34 count * 1401746 nsec + 1 count * 15222 nsec = 47674586 => 0.047 秒です。また、Caller のカラムを見ると、先ほどあった libc の malloc() のロックが消えている事が分かります。

 # LD_PRELOAD_32=/usr/lib/libmtmalloc.so.1 plockstat -Cv ./general
 plockstat: tracing enabled for pid 20828
 plockstat: pid 20828 has exited
 
 Mutex block
 
 Count     nsec Lock                         Caller
 -------------------------------------------------------------------------------
    34  1401746 libc.so.1`__sbrk_lock        libmtmalloc.so.1`morecore+0x81
     1    15222 0xfefa2138                   libmtmalloc.so.1`setup_caches+0x21

mtmalloc で処理性能が大幅にアップ

mtmalloc を使ったテストでは、元の libc の malloc() をした場合と比べるとプログラムの実行時間も大幅に減少しています。ptime コマンドで計測した所、だいたい 1/10 程度の時間で処理が終了している事が分かります。plockstat コマンドで入手した情報を元にロックの競合を解消することでアプリケーションの高速化を行う事が出来ました。

 # ptime ./general
 
 real       27.474   <-- libc の malloc() を使用した場合の処理時間
 user     1:19.758
 sys         4.740
 
 # LD_PRELOAD_32=/usr/lib/libmtmalloc.so.1 ptime ./general
 
 real        3.195   <-- mtmalloc を使用した場合の処理時間
 user       24.022
 sys         1.019

おわりに

以上、plockstat コマンドを使用してロックの競合を解析する手順をご紹介しました。plockstat コマンドに prstat コマンドや vmstat コマンドを組み合わせて使うことで、ロックの競合の原因やそのロックの競合がプログラム全体の処理性能にどれだけ影響を与えているかを調べることが出来ます。また、テストプログラムや malloc() の実験を通して、plockstat コマンドの実際の使い方と競合が発生していた場合の対処方法の一例をご覧頂きました。メニーコア時代への対策として、今回ご紹介した手順を是非ご活用下さい。

なお、今回ご紹介した plockstat コマンドは DTrace の仕組みがあれば実装可能です。実際、Mac OS X にも plockstat コマンドがバンドルされています。FreeBSD でも使えるかもしれません。plockstat コマンドはとても便利なツールですし、こうしてプログラムが動いている舞台裏を覗いてみるのはコーディングやサーバ運用の役に立ちます。Solaris 以外の環境でも是非お試し下さい。 次回の記事 では更に進んで、plockstat コマンドの実装と DTrace を使ってロックを解析する方法に付いて書いてみたいと思います。

参考文献

mtmalloc ライブラリのソースコード

malloc() の並列実行ベンチマーク

http://blogs.sun.com/yappri/date/20090828 2009年 8月 28日 金曜日

Sun Desktop Access Client (Sun Ray Soft Client)

  • Sun Ray 1 Ultra-Thin Client
  • Sun Ray は、1999 年 8 月に発表された製品となり、専用ハードウェアとソフトウェアの組み合わせで構成されるシンクライアントシステムとなります。
    クライアントとなる Sun Ray 端末には何も情報を持たず、サーバ上に全てのデータが存在する仕組みとなっていますので、ネットワークケーブル 1 本を接続するだけでデスクトップ環境を利用することが可能となります。
    詳細については、下記の製品紹介をご覧ください。
    http://jp.sun.com/products/desktop/sunray/

  • ついに実現するソフトウェアクライアント
  • 専用のハードウェアが必要となる Sun Ray 環境ですが、現在テスト中の Sun Ray Software 5 では、それ以外にソフトウェアでの利用を実現することが可能となる Sun Ray Soft Client が実装されることになりました。
    Sun Ray Soft Client は、Windows 上で動作するソフトウェアとなりますが、Windows が動作しているマシンであれば、Sun Ray クライアントとなることが可能となります。 よくやった。ついにやってくれた。
    今回は、この Sun Ray Soft Client を紹介したいと思います。

  • Sun Ray Software 5 EA1 の入手
  • Sun Ray Soft Client を利用するには、現在、Early Access として公開(フィードバックを得るためのテストリリース)されている Sun Ray Software 5 EA1 と Solaris 10 SPARC/x86 が動作するシステム(あるいは、Linux) が必要となります。
    また、Sun Ray Soft Client を利用するには、Sun Ray Software 5 EA1 の機能が必要になるため、既に稼働している Sun Ray サーバを利用することができない点にご注意下さい。

    下記 URL からソフトウェアおよびドキュメント類をダウンロードすることが可能です。

    	Sun Ray Software 5 Information Center
    	
    	Sun Ray Software 5 EA1 program
    
            ※ Sun Ray Software 5 EA1 は、下記のソフトウェアで構成されています。
    	srss_4.2_solaris.zip (Sun Ray Server Software 4.2)
    	setup.exe (Sun Ray Soft Client 1.0)
    	srwc_2.2_solaris.zip (Sun Ray Connector for Windows Operating Systems 2.2)
    	※ srwc_2.2_solaris.zip は、必須ではありません。
    
    まずは、これらを入手しインストールをおこないます。

  • Sun Ray Software 5 EA1 のインストール
  • まずは、アーカイブを展開し、管理用 Web インターフェースを利用するために必要な Tomcat をインストールします。ファイルを展開し、所定のディレクトリに配置するだけです。

    # pwd
    /var/tmp/install
    # unzip -q srss_4.2_solaris.zip
    # cd srss_4.2/Supplemental/Apache_Tomcat/
    # /usr/sfw/bin/gtar zxvf apache-tomcat-5.5.20.tar.gz -C /opt
    # cd /opt
    # ln -s apache-tomcat-5.5.20 apache-tomcat
    
    また、Java SE 6 が必要になります。幸い Sun Ray Server Software 4.2 のアーカイブに同梱されていますので、インストールされていない場合は、別途、インストールをおこなってください。

    srss_4.2/Supplemental/Java_Runtime_Environment/Solaris
    jre-6u13-solaris-i586.sh
    jre-6u13-solaris-sparc.sh
    
    続いて、Sun Ray Server Software 本体のインストールをおこないます。 いくつか質問される箇所がありますので、確認しつつ作業を進めてください。

    # cd /var/tmp/install/srss_4.2
    # ./utinstall
    :
    :
    
    Sun Ray Server Software 4.2              not installed
    Sun Ray Data Store 3.1                   not installed
    
    Do you want to install Sun Ray Server Software 4.2 French Admin GUI (Y/[N]): 
    
    Do you want to install Sun Ray Server Software 4.2 Japanese Admin GUI (Y/[N]): Y
    
    Do you want to install Sun Ray Server Software 4.2 Simplified Chinese Admin GUI (Y/[N]): 
    Kiosk Mode 4.2                           not installed
    
    Enter Java v1.6 (or later) location [/usr/java]: 
    
    About to carry out the following operations:
    
    Install	 [ Sun Ray Server Software 4.2 ]
    Install	 [ Sun Ray Data Store 3.1 ]
    Install	 [ Sun Ray Server Software 4.2 Japanese Admin GUI ]
    Install	 [ Sun Ray Server Software 4.2  ]
    Install	 [ Kiosk Mode 4.2 ]
    Install	 [ Kiosk Mode 4.2 localized files ]
    Install	 [ data for utslaunch ]
    Install	 [ Sun Ray Server Software 4.2 modules for utsunmc ]
    
    
    Continue? ([Y]/N): 
    
    :
    :
    Installation of Sun Ray Server Software has completed.
    
    The system must be rebooted in order to complete this installation and
    before starting the Sun Ray Server Software.
    
    Please check for errors/warnings in
    
        /var/adm/log/utinstall.2009_08_26_17:57:17.log
    
    +++ Done.
    
    以上、ソフトウェアのインストールは完了となります。

  • Sun Ray Server Software の設定
  • ここでは、Sun Ray サーバとなるマシンおよび Sun Ray Soft Client を動作させるマシンが、192.168.77.0/24 のネットワークに接続される事を前提とします。 また、LAN 共有型 (Shared network) と呼ばれる、専用インターターコネクトを利用しない方法で設定をおこないます。
    今回は、Sun Ray Software を利用することを目的としているため、なるべく簡単にセットアップするための手順を記載しています。

    192.168.77.0/24 のネットワークに Sun Ray サービスを提供するには、下記のように設定し、Sun Ray サーバとなるマシンに設定された IP アドレスは、192.168.77.51 となります。

    utadm コマンドにて共有ネットワークを利用する設定をおこないます。

    # /opt/SUNWut/sbin/utadm -A 192.168.77.0
    ### Configuring /etc/nsswitch.conf
    ### Configuring Service information for Sun Ray
      Selected values for subnetwork "192.168.77.0" 
        net mask:		255.255.255.0
        no IP addresses offered
        auth server list:	192.168.77.51
        firmware server:	192.168.77.51
      Accept as is? ([Y]/N): 
    ### Configuring firmware version for Sun Ray
    	All the units served by "macbook" on the 192.168.77.0
    	network interface, running firmware other than version
    	"4.2_17_2009.06.24.11.48" will be upgraded at their next power-on.
    
    ### Configuring Sun Ray Logging Functions
    ### Turning on Sun Ray LAN connection
    
    NOTE: utrestart must be run before LAN connections will be allowed
    
    DHCP is not currently running, should I start it? ([Y]/N): Y
    
    続いて、サービスを提供するための設定を行います。

    # /opt/SUNWut/sbin/utconfig         
    
    Configuration of Sun Ray Core Services Software
    
    This script automates the configuration of the Sun Ray Core Services
    software and related software products.  Before proceeding, you should
    have read the Sun Ray Core Services 4.2 Installation Guide and filled
    out the Configuration Worksheet.  This script will prompt you for the
    values you filled out on the Worksheet.  For your convenience, default
    values (where applicable) are shown in brackets.
    
    Continue ([y]/n)? y
    Enter Sun Ray admin password: 
    Re-enter Sun Ray admin password: 
    
    Configure Sun Ray Web Administration? ([y]/n)?  
    Enter Apache Tomcat installation directory [/opt/apache-tomcat]: 
    Enter HTTP port number [1660]: 
    Enable secure connections? ([y]/n)? 
    Enter HTTPS port number [1661]: 
    Enter Tomcat process username [utwww]: 
    Enable remote server administration? (y/[n])? y
    
    Configure Sun Ray Kiosk Mode? (y/[n])? 
    
    Configure this server for a failover group? (y/[n])? 
    
    About to configure the following software products:
    
    Sun Ray Data Store 3.1
        Hostname: macbook
        Sun Ray root entry: o=utdata
        Sun Ray root name: utdata
        Sun Ray utdata admin password: (not shown)
        SRDS 'rootdn': cn=admin,o=utdata
    
    Sun Ray Web Administration
        Apache Tomcat installation directory: /opt/apache-tomcat
        HTTP port number: 1660
        HTTPS port number: 1661
        Tomcat process username: utwww
        Remote server administration: Enabled
    
    Sun Ray Core Services 4.2
        Failover group: no
        Sun Ray Kiosk Mode: no
    
    Continue ([y]/n)? 
    
    :
    :
    
    Creating Sun Ray Core Services Configuration ...
    Adding user account for 'utwww' (ut admin web server user) ...done
    Sun Ray Web Administration enabled to start at system boot.
    
    Unique "/etc/opt/SUNWut/gmSignature" has been generated.
    
    Restarting Sun Ray Data Store ...
    Stopping Sun Ray Data Store daemon
    .Sun Ray Data Store daemon stopped
    Starting Sun Ray Data Store daemon .
    Wed Aug 26 18:25 : utdsd starting
    Adding user admin ...
    User(s) added successfully!
    
    ***********************************************************
    The current policy has been modified.  You must restart the 
    authentication manager to activate the changes. 
    ***********************************************************
    
    Configuration of Sun Ray Core Services has completed.  Please check
    the log file, /var/adm/log/utconfig.2009_08_26_19:21:56.log, for errors.
    
    再起動することで、Sun Ray Server Software の設定は完了です。

  • Sun Ray Soft Client を利用するための設定
  • Firefox などの Web ブラウザから下記の URL にアクセスし、管理 GUI を呼び出します。

    	http://SunRayServer_IPaddress:1660/
    

    ログインのユーザ名は、admin となり、パスワードは utconfig 実行時に設定したものとなります。

    ログイン後、ブラウザ内に表示される「詳細」タブ -> 「システムポリシー」タブをクリックします。
    表示され項目の中に「非カードユーザー」に関する設定項目がありますが、その中にある「Software client Access」の「使用可能」チェックボックスをオンにし、ブラウザ内右上にある「保存」ボタンをクリックすることで設定はが反映されサービスの再起動を促されますので指示に従いサービス再起動を行います。
    以上で、Sun Ray サーバのセットアップは完了となります。

  • Sun Ray Soft Client のインストール
  • Sun Ray Server をインストールしたマシンと同じネットワークに接続した Windows マシン上で作業を行います。
    Sun Ray Soft Client は、Windows 用プログラムとなりますので、Windows 環境下で setup.exe を実行する必要がありますが、特に設定など必要なく通常の Windows アプリケーションと同様の扱い方で問題ありません。

    インストール後は、「スタート」-> プログラム -> Sun Ray から起動することが可能となります。アイコンが味気なく、まだ、開発中であるということを感じさせます。

    Sun Ray Soft Client を起動すると接続設定用のダイアログが表示されます。
    まずは、接続だけを目的とするので、下記の項目を設定してください。

    	Connection タブ ->  Automatically find server のチェックを外し
    			    Connect to Server: に Sun Ray Software を
    			  インストールしたマシンの IP アドレスを入力
    			  します。
     
    	Display タブ -> Full Screesn のチェックボックスを外す
    			※ Full Screen において接続を解除する方法
    			  は別途記載します。 
    
    上記の設定が完了したら、Connect ボタンをクリックしてください。
    Sun Ray Server の設定に問題がなければ、接続を開始し、Solaris のログイン画面が表示されます。

  • Sun Ray Soft Client の利用方法
  • 利用については特別な注意事項は特にありませんが、制限事項などについては、付属のユーザガイドを参照してください。
    Solaris にログインすることで、Solaris 環境を利用可能な他、Sun Ray Windwos connnector を Sun Ray server にインストールしていれば Windows への RDP 接続も可能です。



    このスクリーンショットでは、Solaris 上で、VirtualBox を利用した Windows 7 Guest の中で Sun Ray Soft Client を起動し、さらにその中で Sun Ray Windows Connector を利用し Windows 7 のリモートデスクトップを表示しています。
    そして、この環境、実は 1 台の Note PC で実現していたりします。

    そして、こんなことも可能です・・・
    6 つの Sun Ray Soft Client が起動しており、それぞれが別々のセッションとして利用可能です。

  • セッションの終了およびフルスクリーンの解除方法
  •    Sun Ray Soft Client で Solaris にログインした状態は、セッションとして管理されます。
    セッションを終了させる場合は、Solaris 環境からログアウトしてください。セッションを継続させたまま、Sun Ray Soft Client を終了させる場合は、ウィンドウの × ボタンを押してください。
    Sun Ray の特徴となりますが、サーバ上にセッションを保持できるためクライアントを終了してもログアウトさえしなければ、Sun Ray Soft Client を再起動するだけで即座に前回利用していたセッションを呼び出すことができます。

    フルスクリーンを解除するには、一度、Sun Ray Soft Client を終了���る必要があります。
    終了するには、下記のショートカットを入力します。

    	左 Shift + Alt + Control 
    
    画面に Disconnect ダイアログが出力されますので、Confirm をクリックすることでクライアントを終了することができます。
    こちらもセッションは保持されたままとなります。  

  • Sun Ray server のアンインストール
  •   # /opt/SUNWut/sbin/utinstall -u 
      # rm -fr /opt/apache-tomcat
      # rm -fr /opt/apache-tomcat-5.5.20
      # pkgrm 
    
    アンインストール処理完了後、システムを再起動することで Sun Ray Server をアンインストールすることが可能です。

  • 最後に
  • Sun Ray Soft Client を含む、Sun Ray Software 5 は、現在のところ フィードバックを求める Early Access 版となるため、製品としてリリースされ るまでは、もう少し時間を要するかと思いますが、ぜひ、この機会に Sun Ray を体験してみてください!

http://blogs.sun.com/yappri/date/20090824 2009年 8月 24日 月曜日

vdbench でディスクの性能を測定する

はじめに

今回は vdbench というツールをご紹介したいと思います。vdbench は Henk Vandenbergh さんが作ったストレージのベンチマークツールです。Java と JNI で書かれており Solaris(SPARC and x86) の他 Windows や Linux, VMware, Mac OS X 等の主要なプラットフォームで動作する様です。私は主に Solaris と Linux で使用しています。このツールは元々は Sun の社内用のテストツールでしたが、バージョン 5.00 からはオープンソースとして公開されており、自由にお使い頂くことが可能です。ストレージのベンチマークは他にも bonnie-64, bonnie++, Iometer, IOzone, FileBench, IOR 等がありますが、vdbench には以下の特徴があります。使い勝手が良く動作も安定しておりますので、是非お試し下さい。

  • 複数のプラットフォームで同じテストを実行できます
  • RAW デバイスとファイルシステムに対して I/O 負荷を掛ける事が出来ます
  • マルチスレッド、マルチプロセスを使用して並列に I/O を発生させる事が出来ます
  • Read / Write の比率を変更して I/O を発生させる事が出来ます
  • I/O サイズを変更して I/O を発生させる事が出来ます
  • IOPS を指定して負荷を発生させる事が出来ます
  • シーケンシャルかランダムか、I/O の特性を変更して負荷を生成する事が出来ます
  • 複数のディスクに股がって I/O を発生させる事が出来ます
  • 複数のホストから I/O を発生させる事が出来ます
  • Solaris と Linux では O_SYNC, O_DSYNC, O_RSYNC のフラグを設定してファイルを開く事が出来ます
  • テスト時間やテスト回数、I/O 発生パターン等をシナリオとして定義し、テストを自動化する事が出来ます

vdbench は http://blogs.sun.com/henk/entry/vdbench_a_disk_and_tapehttp://www.sun.com/storage/white-papers/storage_bottlenecks.pdf でも紹介されておりますので、併せてご覧下さい。

vdbench のダウンロード

vdbench のソースコードとバイナリは http://vdbench.sourceforge.net/ からダウンロードできます。2009 年 8 月現在の最新バージョンは 5.01 です。

vdbench のインストール

Solaris へのインストール

ダウンロードしたファイルを展開するだけです

 # mkdir /var/tmp/vdbench
 # cp vdbench501.zip /var/tmp/vdbench
 # cd /var/tmp/vdbench
 # unzip vdbench501.zip

Linux にインストールする

CentOS(64bit) の場合は付属の OpenJDK で vdbench を動かす事が出来ます。お使いの Linux に JavaVM が入っていない場合は http://java.sun.com から最新の JDK の 64bit Linux 版をダウンロードしてインストールしてください。

 # mkdir /var/tmp/vdbench
 # cp vdbench501.zip /var/tmp/vdbench
 # cd /var/tmp/vdbench
 # unzip vdbench501.zip

vdbench の使い方

ここでは vdbench の基本的な使用方法を解説します。より詳しい使い方を知りたい方は vdbench のアーカイブに含まれている vdbench.pdf をご覧下さい。

vdbench を動かしてみる

動作確認の意味も込めて、早速 vdbench を動かしてみましょう。./vdbench -t を実行して下さい。以下の様な出力が得られれば成功です。

 # ./vdbench -t
 
 
 Vdbench distribution: vdbench500
 For documentation, see 'vdbench.pdf'.
 
 01:07:59.273 input argument scanned: '-f/tmp/parmfile'
 01:07:59.327 Starting slave: /var/tmp/vdbench/vdbench SlaveJvm -m 10.16.67.4 -n localhost-10- 090723-01.07.58.208 -l localhost-0 -p 5570   
 01:07:59.660 All slaves are now connected
 01:08:00.763 Inserted 'rd=File_format_for_sd=sd1' to initialize new file for sd=sd1,lun=/tmp/quick_vdbench_test,size=10485760
 01:08:02.003 Starting RD=File_format_for_sd=sd1; I/O rate: 5000; Elapsed=(none); For loops: threads=8
 01:08:02.089 All sequential workloads on all slaves are done.
 01:08:02.089 This triggers end of run inspite of possibly some non-sequential workloads that are still running.
 
 Jul 23, 2009 interval        i/o   MB/sec   bytes   read     resp     resp     resp    cpu%  cpu%
                             rate  1024**2     i/o    pct     time      max   stddev sys+usr   sys
 01:08:03.051        1       9.00     9.00 1048576   0.00   11.178   15.601    5.275     2.7   1.4
 01:08:03.057  avg_2-1       0.00     0.00       0   0.00    0.000    0.000    0.000
 01:08:04.003 Starting RD=rd1; I/O rate: 100; Elapsed=5; For loops: None
 
 Jul 23, 2009 interval        i/o   MB/sec   bytes   read     resp     resp     resp    cpu%  cpu%
                             rate  1024**2     i/o    pct     time      max   stddev sys+usr   sys
 01:08:05.009        1      84.00     0.33    4096  53.57    0.009    0.040    0.005     1.1   0.3
 01:08:06.047        2      89.00     0.35    4096  50.56    0.008    0.018    0.003     0.1   0.0
 01:08:07.048        3      99.00     0.39    4096  50.51    0.008    0.017    0.003     0.1   0.0
 01:08:08.047        4      94.00     0.37    4096  51.06    0.008    0.017    0.003     0.1   0.0
 01:08:09.047        5     111.00     0.43    4096  53.15    0.008    0.024    0.003     0.1   0.1
 01:08:09.050  avg_2-5      98.25     0.38    4096  51.40    0.008    0.024    0.003     0.1   0.0
 01:08:09.357 Slave localhost-0 terminated
 01:08:09.409 Vdbench execution completed successfully. Output directory: /var/tmp/vdbench/output
  • vdbench -t は /tmp に負荷を発生させます。テスト結果は output ディレクトリにも出力されます。ウェブブラウザで output/summary.html を開いてみてください。

vdbench コマンドのオプション

テストの実行時に付けるオプションは、殆どの場合 "./vdbench -f <CONFIG FILE> -o <OUTPUT DIR>+" で十分です。以下の様にコマンドを実行すると myconf.txt から設定を読み込み、測定を実施し、測定結果を /var/tmp/resultXXX ディレクトリに書き出します。結果が出力されるディレクトリは自動的に作成されます。resultXXX の XXX は最初は空文字、2 回目以降は 000 から 999 までの値が自動的にインクリメントされます。これで同じコマンドを繰り返し実行しても(当分の間は)結果が上書きされずに済みます。

 # ./vdbench -f myconf.txt -o /var/tmp/result+

vdbench の実行時に以下のオプションを渡すことも可能です。詳細はマニュアルをご覧下さい。

 usage: 
   ./vdbench [compare][gui] [-f xxx] [-o xxx] [-e nn] [-i nn] [-j] [-jr] [-v] [-vq] [-s] [-k] [- "parmfile parameters"]
 
   '-f xxx': Workload parameter file name(s). Default 'parmfile' in current directory
   'gui':    Start Vdbench Graphical User Interface
   '-o xxx': Output directory for reporting. Default 'output' in current directory
   '-e nn':  Override elapsed=seconds. 
   '-i nn':  Override interval=seconds. 
   '-v':     Activate Data validation. 
   '-vq':    Activate Data validation, validate lba and data key (saves cpu) 
   '-j':     Activate Data validation with Journaling. 
   '-jr':    Recover existing Journal, Validate data and run workload 
   '-s':     Simulate execution. Do everything but I/O. 
   '-k':     Solaris only: Report kstat statistics on console. 

設定ファイルの書き方

I/O パターンやテスト時間等の負荷シナリオは設定ファイルに記述します。ここではその記述方法を解説します。

サンプル設定

vdbench の設定ファイルの中身は以下の様なテキストです。設定は行単位で記述し、* で始まる行はコメントです。これをファイル保存し、vdbench -f <ファイル名> で読み込ませて実行します。他にも vdbench の配布ファイル中にサンプル設定が幾つか含まれていますのでご参照下さい。設定ファイルは生成したい負荷のシナリオを書き下した物なので、負荷シナリオとも呼ばれます。

  • Example 1
  •  *Example 1: Single run, one raw disk
     
     *SD:	Storage Definition
     *WD:	Workload Definition
     *RD:	Run Definition
     *
     sd=sd1,lun=/dev/rdsk/c0t0d0s0
     wd=wd1,sd=sd1,xfersize=4096,rdpct=100
     rd=run1,wd=wd1,iorate=100,elapsed=10,interval=1
     
     *Single raw disk, 100% random read of 4k records at i/o rate of 100 for 10 seconds
    
  • Example 2
  •  *Example 2: Single run, two raw disk, two workloads.
     
     sd=sd1,lun=/dev/rdsk/c0t0d0s0
     sd=sd2,lun=/dev/rdsk/c0t0d1s0
     wd=wd1,sd=sd1,xfersize=4k,rdpct=80,skew=40
     wd=wd2,sd=sd2,xfersize=8k,rdpct=0
     rd=run1,wd=wd*,iorate=200,elapsed=10,interval=1
     
     *Two raw disks: sd1 does 80 i/o's per second, read-to-write ratio 4:1, 4k records. sd2 does 120 i/o's per second, 100% write at 8k records.
    
  • Example 3(Filesystem)
  •  fsd=fsd1,anchor=/dir1,depth=2,width=2,files=2,size=128k 
     fwd=fwd1,fsd=fsd1,operation=read,xfersize=4k,fileio=sequential,fileselect=random,threads=2 
     rd=rd1,fwd=fwd1,fwdrate=max,format=yes,elapsed=10,interval=1 
    

Raw デバイスに負荷を掛ける場合の設定手順

vdbench は Raw デバイスにもファイルシステムにも I/O 負荷を掛ける事が出来ますが、まずは Raw デバイスに負荷を掛ける場合の設定ファイルの書き方を解説します。

  • sd の設定

まずは負荷生成対象のディスクの定義を行います。ディスクは sd=<設定名> で始まる行で定義します。sd は Storage Definition の略で、この行がディスクの定義であることを示しています。設定名は後でこの定義を参照するために付けます。

 sd=sd1     <-- sd=<設定名> でディスクの定義

sd=<設定名> に続いて、カンマで区切り、負荷を掛ける対象のディスクの名前を記述します。ディスクの名前は lun=<デバイス名> の様に記述します。lun は Logical Unit Number の略です。デバイス名は Solaris では /dev/rdsk/c0t0d0s0 や /dev/md/rdsk/d10 の様にキャラクタデバイスをフルパスで指定します。Linux では raw コマンドで /dev/raw/... を作成し、それを指定します。

 sd=sd1,lun=/dev/rdsk/c0t0d0s0     <-- lun=<デバイス名> を追加

Linux の場合は更にカンマで繋げて size=<ディスクサイズ> を指定する必要があります。ディスクサイズは fdisk -l で確認できます。

 sd=sd1,lun=/dev/raw/raw1,size=733468426240     <-- Linux では size=<サイズ> が必要

基本的には、一行で指定できるディスクは一つだけです。複数のディスクに同時に負荷を掛けたい場合は sd の設定をディスクの数だけ記述する様にしてください。

更にカンマで区切って threads=<スレッド数> を指定することで、そのディスクに対する同時アクセス数を設定する事も出来ます。

 sd=sd1,lun=/dev/rdsk/c0t0d0s0,threads=32      <-- threads=<スレッド数> を追加

ここまでの説明で以下の様な設定が記述できます。ここでは sd1 と sd2 という二つの設定をしています。それぞれ c0t0d0s0 と c0t1d0s0 が負荷生成対象のディスクです。

 sd=sd1,lun=/dev/rdsk/c0t0d0s0
 sd=sd2,lun=/dev/rdsk/c0t1d0s0
  • wd の設定

次にワークロード(どんな I/O を発行するか)の定義を記述します。I/O の定義は行を wd=<設定名> で始めます。wd は Workload Definition の略です。

 wd=wd1     <-- wd=<設定名> でワークロードの定義

wd=<設定名> に続いて、カンマで区切って負荷生成対象のディスクを指定します。ディスクは先ほど設定した sd の設定名で指定します。具体的には sd=<sd の設定名> の様に記述します。複数の sd を指定する場合はカンマで区切って sd=(sd1,sd2) の様に指定することも可能です。また sd=(sd1-sd10) の様に範囲で指定する事も出来ます。

 wd=wd1,sd=sd1     <-- sd=<sd の設定名> を追加

wd には I/O サイズ、Read / Write の比率、シーケンシャルかランダムか、キャッシュヒット率等の I/O パターンを指定する事が出来ます。I/O サイズは xfersize=<サイズ> の様に指定します。Read / Write 比は rdpct=<Read の割合>、シーケンシャルかランダムかは seekpct=<ランダムシークの割合>、キャッシュヒット率は rhpct=<Read のヒット率>、whpct=<Write のヒット率> の様に指定します。seekpct の値はランダムアクセス用のディスクシークをどれだけの割合で行うかです。この値が 0 または sequential の時にシーケンシャルアクセスに、100 または random の値の時にランダムアクセスになります。seekpct=0 でシーケンシャルになるというのは直感的ではないかもしれませんので注意してください。

 wd=wd1,sd=sd1,xfersize=32k,rdpct=100,seekpct=100     <-- wd のパラメータを追加

複数の wd を同時に実行するシナリオを作成することも可能です。複数の wd を同時実行した場合にそれぞれの wd にどれだけの I/O を割り振るかを指定する skew というパラメータも用意されています。skew は skew=<この wd に割り当てる I/O の割合> の様に設定します。skew を設定せずに複数の wd を同時実行した場合は、I/O はそれぞれの wd に均等に割り当てられます。

ここまでの説明で以下の様な設定を記述する事が出来ます。この設定では c0t0d0s0 と c0t1d0s0 に対して 4KB のサイズでシーケンシャルに書き込みを行います。

 sd=sd1,lun=/dev/rdsk/c0t0d0s0
 sd=sd2,lun=/dev/rdsk/c0t1d0s0
 wd=wd1,sd=(sd1,sd2),xfersize=4k,rdpct=0,seekpct=0
  • rd の設定

最後にテストの実行に関する設定を行います。テストの実行に関する設定は rd=<設定名> で始まる行を記述します。rd は Run Definition の略です。

 rd=rd1     <-- rd=<rd の設定名> でテストの実行に関する定義

rd=<設定名> の後にカンマを置いて wd の指定を行います。wd は wd=<wd の設定名> の様に指定します。複数の wd を同時に実行する場合は wd=(wd1, wd2) の様に括弧の中にカンマ区切りで指定します。wd=(wd1-wd10) の様に範囲で指定することも可能です。複数の wd を指定する場合は、wd の設定の中で skew を設定すると、それぞれの wd に割り当てる I/O 量を変更する事が出来ます。

 rd=rd1,wd=wd1     <-- wd=<wd の設定名> を追加

rd には秒間の I/O 回数、テスト実施時間、テスト状況をレポートする間隔等を設定する事が出来ます。I/O 回数は iorate=<毎秒の I/O 回数> または iorate=max で指定します。max を指定すると無制限に I/O を発行します。テスト実施時間は elapsed=<秒>、レポート間隔は interval=<秒> で指定します。

 rd=rd1,wd=wd1,iorate=max,elapsed=600,interval=30     <-- rd のパラメータを追加

また複数のテストパターンを一括して行うための forxfersize=<サイズ>、forthreads=<スレッド数>、forrdpct=<Read の割合>、forseekpct=<ランダムシークの割合> の様なパラメータもあります。forxferseize=(4k,8k,12k,16k) と指定すると、I/O サイズが 4KB, 8KB, 12KB, 16KB のテストを順番に実施します。これらのパラメータはテストを自動化したい場合に便利です。

ここまでの説明で以下の様な設定が完成します。I/O 回数は最大、テスト実行時間は 180 秒、レポートの間隔は 10 秒おきになります。なお、* で始まる行はコメント行です。

 * Test Pattern 1 - Sequential Write, 4KB
 * 2009/07/31
 sd=sd1,lun=/dev/rdsk/c0t0d0s0
 sd=sd2,lun=/dev/rdsk/c0t1d0s0
 wd=wd1,sd=(sd1,sd2),xfersize=4k,rdpct=0,seekpct=0
 rd=rd1,wd=wd1,iorate=max,elapsed=180,interval=10
  • まとめ

以上見てきました通り、vdbench の設定はテキストファイルの中に行単位で記述します。パラメータは sd, wd, rd のカテゴリに分かれており、設定ファイルの各行は設定カテゴリの指定と設定名の記述で始めます。設定名の記述に続いて、カンマ区切りでパラメータとその値を記述します。パラメータは パラメータ名=値 という形式で指定します。* で始まる行はコメントです。

 * コメント
 設定カテゴリ名=設定名,パラメータ名=値,パラメター名=値,...

ファイルシステムに負荷を掛ける場合の設定手順

ファイルシステムに負荷を掛ける場合は sd の代わりに fsd, wd の代わりに fwd のエントリを作成し、rd の記述内容も多少変わってきます。

  • fsd の設定

fsd は Filesystem Definition の略で、負荷を生成する対象のファイルシステムの構成を指定するエントリです。ここに負荷を発生させるディレクトリ名、ファイルの個数、ファイルサイズ等を設定します。

 fsd=fsd1     <-- fsd=<fsd の設定名> でファイルシステム構成の設定

fsd の行は fsd=<設定名> で記述を始めます。設定名は後で参照する為に付ける名前です。この後ろにカンマで区切ってパラメータを設定して行きます。ディレクトリ名は anchor パラメータを使用して anchor=<ディレクトリ名> という様に指定します。ファイルの個数は files=<個数> と指定します。ファイルサイズは sizes=<サイズ> です。サイズは複数指定することが可能です。sizes=(32k,30,2m,70) と指定した場合は 32KB のファイルが 30%, 2MB のファイルが 70% の割合で作成されます。

 fsd=fsd1,anchor=/mnt/test,files=100,sizes=2m    <-- マウントポイント、ファイル数、ファイルのサイズを定義

width=<ディレクトリ数>, depth=<ディレクトリの深さ> を設定するとディレクトリの階層を指定する事が出来ます。depth で指定した深さだけサブディレクトリが作成され、それぞれのサブディレクトリには width で指定した数だけサブディレクトリが作成され、一番下の階層のディレクトリに files で指定した個数のファイルが作成されます。最終的に作成されるファイル数は "width ^ depth * files" で求まります。width=2,depth=2,files=2 の場合は 4 つのディレクトリに 8 つのファイルが作成され、width=2, depth=3, files=4 の場合は 8 つのディレクトリに股がって 32 個のファイルが作成されます。

 fsd=fsd1,anchor=/mnt/test,width=2,depth=3,files=4,sizes=10m     <-- ディレクトリ階層に付いても追加で定義

ここまでの説明で以下の様な設定を記述する事が出来ます。この設定では /mnt/test01 以下に 1GB のファイルが 10 個作成されます。/mnt/test01/ ディレクトリはテスト実施前に作成しておく必要があります。

 fsd=fsd1,anchor=/mnt/test01/,files=10,sizes=1g
  • fwd の設定

fwd は Filesystem Workload Definition の略で、発生させる I/O の中身を細かく定義する為のエントリです。I/O がシーケンシャルかランダムか、一つのファイルを open() してから close() するまでに何回ずつ I/O を発行するか、I/O を掛けるファイルを順番に選ぶかランダムに選ぶか、I/O のサイズ、read / write 等の I/O 操作の種類、I/O の並列度等を指定する事が出来ます。

 fwd=fwd1     <-- fwd=<fwd の設定名> でワークロードの定義

fwd の行は fwd=<設定名> で始めます。設定名はこの定義を後で参照する為の名前です。続いて、カンマで繋げて fsd=<設定名> で既に定義したファイルシステム構成を関連づけます。更にカンマで区切り、fileio=<I/O 特性> を指定します。I/O 特性は random または sequential を指定します。ここで random を設定するとランダム I/O に、sequential だとシーケンシャル I/O になります。close() するまでに何回 I/O を発行するかは stopafter=<回数> で指定します。ファイルの選択方法は fileselect=<選択方法> で指定します。選択方法は random または sequential のどちらかを指定します。I/O のサイズは xfersizes=<サイズ> で指定します。このサイズは複数指定することも可能です。xfersizes=(8k,30,128k,70) の様に指定した場合は 8KB 単位の I/O が 30% と 128KB 単位の I/O が 70% 発生します。I/O 操作の種類は operation=<操作> で指定します。指定できる操作は read / write / open / close / create / delete / mkdir / rmdir / setattr / getattr です。読み出し性能の試験を行うときは read を、書き込み性能の試験を行う場合は write を指定してください。最後に、I/O の並列度は threads=<スレッド数> で指定します。

ここまでの説明で以下の様な設定を作成する事が可能です。8KB のシーケンシャル書き込みを一つのファイルに対して 1000 回ずつ 8 並列で実行します。負荷生成対象のファイルの探索はシーケンシャルに行われます。

 fsd=fsd1,anchor=/mnt/test01/,files=10,sizes=1g
 fwd=fwd1,fsd=fsd1,fileio=sequential,stopafter=1000,fileselect=sequential,xfersizes=8k,operation=write,threads=8
  • rd の設定

一番最後に rd を設定します。rd は Run Definition の略です。Raw I/O の場合の rd と同じ位置付けですが、パラメータが異なります。rd には既に定義した fwd をどの様に実行するかを設定します。

 rd=rd1     <-- rd=<rd の設定名> でテストの実行に関するパラメータの設定

rd の行は rd=<設定名> から始めます。続いて、カンマで区切り、fwd=<fwd の設定名> を記述して既定の fwd を rd に関連付けます。これに続けて、秒間にどれだけの I/O 操作を発生させるかを指定します。指定方法は fwdrate=<I/O を発生させる量> です。このパラメータに max を指定するとマシンの性能が許す限りの I/O 操作を実行します。format=yes を設定すると、テスト開始前にファイルとディレクトリを作成します。これを設定しない場合は自分でファイルとディレクトリを作成しておく必要があります。ファイル数やファイルサイズが増えるとファイルの作成に時間が掛かる様になるので、一回目のテストでファイルを生成して、以降はそれを使い回すと良いかもしれません。elapsed=<秒> を設定すると、テストの実行時間を指定する事が出来ます。デフォルトは 30 秒です。ファイルシステムはキャッシュの影響が大きいので、ある程度長い時間でテストを行ってください。interval=<秒> を設定すると、統計情報の出力間隔を指定する事が出来ます。Solaris と Linux では openflags=<フラグ> を設定すると open() に渡すフラグを指定する事が出来ます。設定できるフラグは o_sync, o_dsync, o_rsync です(ソースファイルの Vdb/OpenFlags.java で確認できます)。データベース等を模したテストをする場合は O_DSYNC を付けると実環境により近付くかもしれません。

forsizes=<ファイルサイズ> を設定すると複数のファイルサイズのテストを一つの設定で実行する事が出来ます。forsizes=(128k,2m,1g) の様に指定すると、まず 128KB でテストを行い、それが終了したら 2MB のファイルサイズで、最後に 1GB のファイルサイズでテストを行います。forfiles=<ファイル数> を設定すると異なるファイル数のテストを順番に実施する事が出来ます。forfiles=(300,500,1000) と指定すると 300 個のファイルのテスト、500 個のファイルのテスト、1000 個のファイルのテストを順番に実行します。

ここまでの説明で以下の様な負荷生成シナリオができました。これをファイルに保存し vdbench コマンドの -f オプションでそのファイル名を指定することでこの負荷シナリオを実行する事が出来ます。

 fsd=fsd1,anchor=/mnt/test01/,files=10,sizes=1g
 fwd=fwd1,fsd=fsd1,fileio=sequential,stopafter=1000,fileselect=sequential,xfersizes=8k,operation=write,threads=8
 rd=rd1,fwd=fwd1,fwdrate=max,elapsed=600,interval=10,format=yes
  • 注意

繰り返しテストを実行する場合は、測定条件を合わせるため、なるべく一度ファイルシステムをアンマウントしてから次のテストを実行する様にしてください。

  • まとめ

Raw の設定ファイルでは sd, wd, rd のパラメータを設定しましたが、ファイルシステムの設定ファイルでは fsd, fwd, rd のパラメータを設定します。ファイルシステムキャッシュ等の影響があるので、試験時間は長めに、データ量は実環境になるべく近く、テストする度にマウントし直す様にしてください。

vdbench のパラメータ

前項まででご覧頂いた通り、Raw デバイスに負荷を生成する場合は SD(Storage Definition), WD(Workload Definition), RD(Run Definition) を設定する必要があります。ファイルシステムの場合は FSD(Filesystem Storage Definition), FWD(Filesystem Workload Definition), RD(Run Definition) を定義する必要がありました。実際にはそれ以外に General Parameters と HD(Host Definition) というパラメータもあります。全てのパラメータはマニュアルに記載されていますので、ご確認下さい。

テスト結果の見方

Raw の場合

出力先に指定したディレクトリに summary.html ができます。これをウェブブラウザで開いてください。

 14:55:08.000 Starting RD=rd1;
 I/O rate: Uncontrolled MAX; Elapsed=60; For loops: xfersize=32768
 
 Aug 04, 2009 interval        i/o   MB/sec   bytes   read     resp     resp     resp    cpu%  cpu%
                             rate  1024**2     i/o    pct     time      max   stddev sys+usr   sys
 14:55:18.023        1    9978.40   311.82   32768   0.00    0.798   52.837    1.960     4.5   3.8
 14:55:28.011        2    8517.60   266.18   32768   0.00    0.936   26.145    2.138     4.0   3.3
 14:55:38.011        3    8661.60   270.68   32768   0.00    0.921   33.382    2.112     4.1   3.3
 14:55:48.012        4    7870.60   245.96   32768   0.00    1.013   30.333    2.250     3.6   3.1
 14:55:58.010        5    8523.80   266.37   32768   0.00    0.935   87.823    2.317     3.9   3.3
 14:56:08.011        6    8365.10   261.41   32768   0.00    0.954   34.503    2.170     3.9   3.2
 14:56:08.014  avg_2-6    8387.74   262.12   32768   0.00    0.951   87.823    2.198     3.9   3.2
 14:56:09.296 Vdbench execution completed successfully
  • interval の値は統計情報の出力回数です。連番になっており一行出力する度に値が一つずつ増えて行きます。
  • i/o rate は I/O の回数 (IOPS) です。
  • MB/sec は I/O スループット (megabytes per second) です。
  • bytes i/o は I/O サイズ(の平均)です。上の例の場合は xfersize=32768 にしましたので、一回の I/O サイズも 32768 bytes になります。
  • read pct は Read の割合です。Write Only の時は 0 になります。
  • resp time はミリ秒単位の平均レスポンス時間です。
  • resp max はもっとも時間が掛かった処理の処理時間(ミリ秒単位)です。
  • resp stddev はレスポンス時間の標準偏差(ばらつき度合い)です。
  • cpu% sys+usr は処理に費やした CPU 時間です。
  • cpu% sys は OS 側の処理に消費した CPU 時間です。
  • avg_2-6 は、この行が 2 回目の出力から 6 回目の出力の平均であることを示しています。この行の i/o rate と MB/sec をテスト結果として採用すると良いでしょう。
  • ほぼ同じ内容が標準出力にも出力されます。そちらを見て頂いても構いません。

ファイルシステムテストの場合

出力先に指定したディレクトリに summary.html ができます。これをウェブブラウザで開いてください。以下は summary.html の一部を抜き出したものです。

 ...
 15:15:15.001 Starting RD=rd1; Elapsed=60; fwdrate=max. For loops: None
 
 15:15:25.025 Interval ....Ops..... ...cpu%... ....read.... ...write.... ..mb/sec.. .xfer. ...
 15:15:25.025            rate  resp total  sys   rate  resp   rate  resp read write   size  ...
 15:15:25.025        1  10228   0.5   4.6  4.1    0.0   0.0  10228   0.5  0.0  79.9   8192   ...
 15:15:35.011        2 3443.1   2.8   1.2  1.0    0.0   0.0 3443.1   2.8  0.0  26.9   8192   ...
 15:15:45.013        3  14297   0.6   3.8  3.2    0.0   0.0  14297   0.6  0.0 111.7   8192   ...
 15:15:55.009        4 8678.4   0.9   3.5  3.1    0.0   0.0 8678.4   0.9  0.0  67.8   8192   ...
 15:16:05.011        5 5030.4   1.0   2.2  2.0    0.0   0.0 5030.4   1.0  0.0  39.3   8192   ...
 15:16:15.009        6 3532.8   3.0   1.8  1.6    0.0   0.0 3532.8   3.0  0.0  27.6   8192   ...
 15:16:15.012  avg_2-6 6996.5   1.2   2.5  2.2    0.0   0.0 6996.5   1.2  0.0  54.7   8192   ...
 15:16:17.044 Vdbench execution completed successfully
  • Interval は出力行の番号です。出力される度にインクリメントされて行きます。
  • Ops は I/O 操作に関する統計情報です。Ops の rate は秒間の操作回数、resp は平均レスポンス時間です。
  • cpu% は CPU 使用率を表しています。total が CPU 使用率で sys はその内のシステム時間です。
  • read は読み出しに関する統計情報です。rate は毎秒の read 回数、resp は平均レスポンス時間です。
  • write は書き込みに関する統計情報です。rate は毎秒の write 回数、resp は平均レスポンス時間です。
  • mb/sec は毎秒のデータ転送量 (megabytes per second) です。read は読み出しの転送量、write は書き込みの転送量です。
  • xfer size は I/O のサイズです。単位は byte です。
  • avg_2-6 は、この行が 2 回目の出力から 6 回目の出力の平均であることを示しています。この行の read rate, write rate と mb/sec をテスト結果として採用すると良いでしょう。
  • ほぼ同じ内容が標準出力にも出力されます。そちらを見て頂いても構いません。

実際に使ってみる

Solaris の Raw デバイスのテスト

vdbench を使って Solaris の Raw デバイスの Sequential Write の性能を測定してみます。設定ファイルは以下の通りです。

 # cat conf/test01.conf
 * START "test01.conf" : sequential write - 8KB, 16KB, 24KB, 32KB
 sd=sd1,lun=/dev/rdsk/c0t2d0s2,size=733468426240
 wd=wd1,sd=sd1,rdpct=0,seekpct=0
 rd=rd1,wd=wd1,iorate=max,elapsed=180,interval=10,forxfersize=(8k,16k,24k,32k)
 * END

実行ログは以下の通りです。出力が長くなるので、一部重要な部分だけ載せています。

 # ./vdbench -f conf/test01 -o /var/tmp/output+
 ...
 Aug 17, 2009 interval        i/o   MB/sec   bytes   read     resp     resp     resp    cpu%  cpu%
                             rate  1024**2     i/o    pct     time      max   stddev sys+usr   sys
 ...
 16:43:16.030 avg_2-18   37145.06   290.20    8192   0.00    0.212    5.279    0.049    16.7  14.1
 ...
 16:46:17.017 avg_2-18   16963.02   265.05   16384   0.00    0.468   85.172    1.356     7.7   6.5
 ...
 16:49:18.013 avg_2-18   11330.82   265.57   24576   0.00    0.703   72.644    1.928     5.2   4.3
 ...
 16:52:19.015 avg_2-18    8746.64   273.33   32768   0.00    0.911  102.431    2.199     4.1   3.5

今回は単純に MB/sec の avg を結果として採用しました。まとめると以下の様になります。

 8KB write	16KB write	24KB write	32KB write
 -----------------------------------------------------------
 290MB/sec	265.05MB/sec	265.57MB/sec	273.33MB/sec

Linux の Raw デバイスのテスト

Solaris の場合と同じ様に Linux でも Sequential Write の性能を測定してみます。Linux では Solaris の /dev/rdsk/... に対応するデバイスファイルを手動で用意する必要があります。

 # ls /dev/raw
 ls: /dev/raw: No such file or directory      <-- raw デバイスはデフォルトでは存在しない
 # raw /dev/raw/raw1 /dev/sdc           <-- raw コマンドで raw デバイスを作成します
 /dev/raw/raw1:	bound to major 8, minor 32
 # ls /dev/raw
 ./  ../  raw1
 # ls -l /dev/raw
 total 0
 crw-------  1 root root 162, 1 Aug  6 00:13 raw1     <-- c で始まるのでキャラクタデバイス
 # fdisk -l /dev/sdc
 
 Disk /dev/sdc: 733.4 GB, 733468426240 bytes   <-- サイズは 733468426240 bytes
 255 heads, 63 sectors/track, 89172 cylinders
 Units = cylinders of 16065 * 512 = 8225280 bytes
 
 Disk /dev/sdc doesn't contain a valid partition table

設定ファイルと実行ログはこの様になります。

 # cat conf/test01.conf 
 * START "test01.conf" : sequential write - 8KB, 16KB, 24KB, 32KB
 sd=sd1,lun=/dev/raw/raw1,size=733468426240
 wd=wd1,sd=sd1,rdpct=0,seekpct=0
 rd=rd1,wd=wd1,iorate=max,elapsed=180,interval=10,forxfersize=(8k,16k,24k,32k)
 * END
 # ./vdbench -f conf/test01.conf -o /var/tmp/output+
 ...
 Aug 17, 2009 interval        i/o   MB/sec   bytes   read     resp     resp     resp    cpu%  cpu%
                             rate  1024**2     i/o    pct     time      max   stddev sys+usr   sys
 ...
 20:15:50.051 avg_2-18   35854.75   280.12    8192   0.00    0.222    6.433    0.053     8.6   7.3
 ...
 20:18:51.050 avg_2-18   17196.88   268.70   16384   0.00    0.464   76.286    1.312     4.2   3.6
 ...
 20:21:52.049 avg_2-18   11455.23   268.48   24576   0.00    0.697   68.371    1.885     2.9   2.4
 ...
 20:24:53.048 avg_2-18    8622.86   269.46   32768   0.00    0.927   80.978    2.256     2.3   2.0

結果をまとめると次の様になります。Sequential Write に関してはだいたい Solaris の場合と同程度の性能になっていることが分かります。

 8KB write	16KB write	24KB write	32KB write
 -----------------------------------------------------------
 280.12MB/sec	268.70MB/sec	268.48MB/sec	269.46MB/sec

Solaris のファイルシステムのテスト

次に vdbench を使ってファイルシステムの性能を測定してみます。全部で 50GB のファイルを Sequential Read します。ファイルシステムは ZFS を使用しました。

 # format
 Searching for disks...done
 
 c0t1d0: configured with capacity of 136.59GB
 
 
 AVAILABLE DISK SELECTIONS:
        0. c0t0d0 <DEFAULT cyl 17830 alt 2 hd 255 sec 63>
           /pci@0,0/pci8086,25f8@4/pci108e,286@0/disk@0,0
        1. c0t1d0 <Sun-STKRAIDINT-V1.0 cyl 17831 alt 2 hd 255 sec 63>
           /pci@0,0/pci8086,25f8@4/pci108e,286@0/disk@1,0
        2. c0t2d0 <DEFAULT cyl 44584 alt 2 hd 255 sec 126>
           /pci@0,0/pci8086,25f8@4/pci108e,286@0/disk@2,0
 Specify disk (enter its number): ^D
 # zpool create storage c0t2d0
 # mkdir /storage/test
 # cat conf/test10.conf     <-- 設定ファイル
 fsd=fsd1,anchor=/storage/test/,files=50,sizes=1g
 fwd=fwd1,fsd=fsd1,fileio=sequential,fileselect=sequential,xfersize=8k,operation=read,threads=8
 rd=rd1,fwd=fwd1,fwdrate=max,elapsed=600,interval=10,format=yes
 # ./vdbench -f conf/test10.conf -o /var/tmp/output+
 ...
 18:56:26.013 Interval ....Ops..... ...cpu%... ....read.... ...write.... ..mb/sec.. .xfer. ...mkdir... ...
 18:56:26.013            rate  resp total  sys   rate  resp   rate  resp read write   size  rate  resp  ...
 ...
 19:06:16.014 avg_2-60  39757   0.2  13.9 12.6  39757   0.2    0.0   0.0  310   0.0   8192   0.0   0.0   ...
 ...

テスト結果は 310MB/sec でした。iostat で測定したスループットも同じくらいでしたので純粋なストレージの性能が出ていると思われます。

最後に

今回は vdbench の使い方を解説しました。vdbench は定義できるシナリオの自由度が高く、動作するプラットフォームも多いので、様々な状況で役に立ちます。便利なツールですので、是非ご活用下さい。

http://blogs.sun.com/yappri/date/20090821 2009年 8月 21日 金曜日

CrossbowのVirtual NICとQoS制御を試してみる

以前より前評判の高かった Project Crossbow ですが、OpenSolaris 2009.06 の目玉機能として晴れて標準機能となり、誰でも簡単に使えるようになりま した。簡単に説明しますと、Project Crossbow とはネットワークの仮想化、 つまり仮想インターフェースの作成やネットワーク帯域分割、優先制御と いった機能を、可能であれば NICデバイスのハードウェアアシストも利用 しながら高速かつ低負荷に実現するものです。これを使えば、例えばサーバー 仮想化とあわせて、10本の GbE ケーブルを、お互いの帯域に干渉すること なく1本の 10GbEケーブルに統合するなど、管理性を向上させかつ複雑性を 排除することが可能です。例えば次のようなシチュエーションにも絶大な 効果を発揮するはずです。

  1. ある金融サービス会社がオンラインで無料情報提供サービスを開始 した。人気は上々だが、有料会員のサービスがスローダウンし、お客が 離れていった。。。
  2. ホスティングサービス会社がCPUコア数やメモリ量による価格体系を 用意したが、どのコースでもネットワーク帯域は使い放題。。。
  3. バックアップデータ転送中のため、重要な処理がネットワークタイム アウトしてしまった。。。

Virtual NIC の作成

では早速試してみましょう。必要なものは、OpenSolaris の動作する PC1台とインターネット接続だけです。まず、まだ PC に OpenSolaris を入れてない方は今すぐインストールしましょう。OpenSolaris は Live CD 形式になっているので、とりあえず手持ちのPCで動作確認した 後にボタン一発でインストールできるので、とても手軽です。

Crossbow では図のように、仮想インターフェース (VNIC: Virtual NIC) を物理インターフェース経由で直接外部ネットワークに接続することも 出来ますし、etherstub という仮想 HUB / Switch を使ってプライベート な Virtual Network を作ることも可能です。



今回は、後者の構成でプライベート・ヴァーチャル・ネットワークを 作成し、NAT 経由でインターネットと通信できるようにしてみます。

手順としては、まず、Virtual Network の要となる etherstub を作成し、 その上に Virtual NIC (vnic0, 1, 2) を作成します。vnic0 は Private Address 192.168.10.10 で up させます。

# dladm create-etherstub etherstub0   … etherstub を作成
#
# dladm show-link
LINK        CLASS    MTU    STATE    OVER
bfe0        phys     1500   down     --
etherstub0  etherstub 9000  unknown  --
#

# dladm create-vnic -l etherstub0 vnic0   … etherstub0上にvnic0を作成
# dladm create-vnic -l etherstub0 vnic1   … etherstub0上にvnic1,2を作成
# dladm create-vnic -l etherstub0 vnic2
#
# dladm show-link
LINK        CLASS    MTU    STATE    OVER
bfe0        phys     1500   down     --
etherstub0  etherstub 9000  unknown  --
vnic0       vnic     9000   up       etherstub0
vnic1       vnic     9000   up       etherstub0
vnic2       vnic     9000   up       etherstub0
#
# ifconfig vnic0 plumb   ... VNIC の設定&リンクアップ
# ifconfig vnic0 192.168.10.10 up

NAT のセットアップ

vnic1 と vnic2 が、この後作成するコンテナ(zone)に与えられるのですが、 zoneを作る前に、NAT サービスを起動しておきましょう。

# routeadm
              Configuration   Current              Current
                     Option   Configuration        System State
---------------------------------------------------------------
               IPv4 routing   enabled              enabled
               IPv6 routing   disabled             disabled
            IPv4 forwarding   disabled             disabled
            IPv6 forwarding   disabled             disabled

           Routing services   "route:default ripng:default"

Routing daemons:

                      STATE   FMRI
                     online   svc:/network/routing/ndp:default
                     online   svc:/network/routing/route:default
                   disabled   svc:/network/routing/rdisc:default
                   disabled   svc:/network/routing/legacy-routing:ipv4
                   disabled   svc:/network/routing/legacy-routing:ipv6
                   disabled   svc:/network/routing/ripng:default
#
#
# routeadm -u -e ipv4-forwarding      … ipv4-fowardingを活性化
# routeadm -u -d ipv4-routing         … ipv4-routingを非活性化
#
# routeadm
              Configuration   Current              Current
                     Option   Configuration        System State
---------------------------------------------------------------
               IPv4 routing   disabled             disabled
               IPv6 routing   disabled             disabled
            IPv4 forwarding   enabled              enabled
            IPv6 forwarding   disabled             disabled

           Routing services   "route:default ripng:default"

Routing daemons:

                      STATE   FMRI
                     online   svc:/network/routing/ndp:default
                   disabled   svc:/network/routing/route:default
                   disabled   svc:/network/routing/rdisc:default
                   disabled   svc:/network/routing/legacy-routing:ipv4
                   disabled   svc:/network/routing/legacy-routing:ipv6
                   disabled   svc:/network/routing/ripng:default
#
#
# cd /etc/ipf       … ipfilter / ipnat の設定
#
# cat ipnat.conf
map bfe0 192.168.10.0/24 -> 0/32 portmap tcp/udp auto
map bfe0 192.168.10.0/24 -> 0/32
#
#
# svcadm enable network/ipfilter       … ipfilterサービスの起動
# svcs network/ipfilter
STATE          STIME    FMRI
online         12:16:34 svc:/network/ipfilter:default
#
#
# ipnat -l
List of active MAP/Redirect filters:
map bfe0 192.168.10.0/24 -> 0.0.0.0/32 portmap tcp/udp auto
map bfe0 192.168.10.0/24 -> 0.0.0.0/32

List of active sessions:
#

これで 192.168.10.0/24 プライベートネットワークからインターネット へ出られるようになりました。

Zone と Virtual Network の作成

では zone を作りましょう。VNIC を占有するので ip-type は Exclusive IP Zone となります。

# zonecfg -z zone1                … zone1を作成 (vnic1をアタッチ)
zone1: No such zone configured
Use 'create' to begin configuring a new zone.
zonecfg:zone1> create
zonecfg:zone1> set zonepath=/export/home/zone1
zonecfg:zone1> set autoboot=true
zonecfg:zone1> set ip-type=exclusive
zonecfg:zone1> add net
zonecfg:zone1:net> set physical=vnic1
zonecfg:zone1:net> end
zonecfg:zone1> verify
zonecfg:zone1> commit
zonecfg:zone1> exit
#
# zoneadm -z zone1 verify   ... 作成した zone 構成の検証
WARNING: /export/home/zone1 does not exist, so it could not be verified.
When 'zoneadm install' is run, 'install' will try to create
/export/home/zone1, and 'verify' will be tried again,
but the 'verify' may fail if:
the parent directory of /export/home/zone1 is group- or other-writable
or
/export/home/zone1 overlaps with any other installed zones.
#
#
# zoneadm -z zone1 install   ... zone1 のインストール
WARNING: skipping network interface 'vnic1' which is used in the global zone.
A ZFS file system has been created for this zone.
   Publisher: Using opensolaris.org (http://pkg.opensolaris.org/release/).
       Image: Preparing at /export/home/zone1/root.
Sanity Check: Looking for 'entire' incorporation.
  Installing: Core System (output follows)
...

# zoneadm list -iv   ... インストールの確認
  ID NAME             STATUS     PATH                           BRAND    IP
   0 global           running    /                              native   shared
   - zone1            installed  /export/home/zone1             ipkg     excl

#
# zoneadm -z zone1 boot
#
# zlogin -C zone1      ... Zone の初期設定

                  Host name: zone1
                 IP address: 192.168.10.11
    System part of a subnet: Yes
                    Netmask: 255.255.255.0
                Enable IPv6: No
              Default Route: Specify one
          Router IP Address: 192.168.10.10

vnic1 を持った zone1 が完成しました。global zone の vnic0 とつな がっているか確認してみましょう。

zone1# ping 192.168.10.10       … global zoneの vnic0と通信できた
192.168.10.10 is alive

次に clone 機能を使ってもう1つの zone を作ります。先に zone1 を shutdown しておいてください。

# zoneadm list -iv  ........ zone1 は停止している
  ID NAME             STATUS     PATH                           BRAND    IP
   0 global           running    /                              native   shared
   - zone1            installed  /export/home/zone1             ipkg     excl


# zonecfg -z zone2      ... zone2 を作成する
zone2: No such zone configured
Use 'create' to begin configuring a new zone.
zonecfg:zone2> create
zonecfg:zone2> set zonepath=/export/home/zone2
zonecfg:zone2> set autoboot=true
zonecfg:zone2> set ip-type=exclusive
zonecfg:zone2> add net
zonecfg:zone2:net> set physical=vnic2
zonecfg:zone2:net> end
zonecfg:zone2> verify
zonecfg:zone2> commit
zonecfg:zone2> exit



# zoneadm -z zone2 clone zone1   ... zone1 を基に clone を実行
sys-unconfig started 2009年07月15日 15時52分03秒
rm: /export/home/zone2/root/etc/vfstab.sys-u: No such file or directory
sys-unconfig completed Wed Jul 15 15:52:04 2009
#

# zoneadm list -iv
  ID NAME             STATUS     PATH                           BRAND    IP
   0 global           running    /                              native   shared
   - zone1            installed  /export/home/zone1             ipkg     excl
   - zone2            installed  /export/home/zone2             ipkg     excl

zone1 と zone2 をそれぞれ起動します。zone2 は初回起動なので、パラ メータを与えます。

# zoneadm -z zone1 boot

# zoneadm -z zone2 boot     ...  zone2 を起動
                  Host name: zone2
                 IP address: 192.168.10.12
    System part of a subnet: Yes
                    Netmask: 255.255.255.0
                Enable IPv6: No
              Default Route: Specify one
          Router IP Address: 192.168.10.10

これで Virtual Network が完成しました。 ネットワーク構成は次のようになっているはずです。それぞれお互いに 接続可能か ping コマンドなどで試してみましょう。

global zone   vnic0   192.168.10.10
zone1         vnic1   192.168.10.11
zone2         vnic2   192.168.10.12

etherstub に対して snoop コマンドも使えます。

# snoop -d etherstub0


Flow Control (Network QoS) を設定する

Project Crossbow の Flow Control では帯域制御と優先制御が可能ですが、 今回は帯域制御により HTTP コンテンツダウンロードの速度を制限して みましょう。global zone 上で Apache2 Web Server を立ち上げ、zone1 のクライアントがコンテンツをリクエストします。

まず global zone に Apache2 をインストールします。パッケージ マネージャを使えば簡単です。

* Apache2 を global zone へインストールする
  - Package Manager GUI を利用し Apache2.2 をインストール
  - /etc/apache2/2.2/httpd.conf はデフォルトのまま

# svcadm enable apache22   ... Apache2 を起動
# svcs apache22
STATE          STIME    FMRI
online         11:15:27 svc:/network/http:apache22

DocumentRoot は /var/apache2/2.2/htdocs なので、ここに大きめのファイルを置くこととします。何でも良いのですが、手元にあった OpenSolaris 2009.06 の ISO イメージを使ってみました。

次に zone1 へログインし wget をインストールします。 パッケージマネージャが外部のサーバへアクセスするので DNS の設定をして置いてください。

zone1# pkg install SUNWwget     … wgetのインストール
DOWNLOAD                                    PKGS       FILES     XFER (MB)
Completed                                    1/1       41/41     0.60/0.60

PHASE                                        ACTIONS
Install Phase                                132/132

zone1# which wget
/usr/bin/wget

できました。とりあえずダウンロードしてみます。

zone1# wget http://192.168.10.10/osol-0906-x86.iso   ... global zone からデータ取得
--11:33:42--  http://192.168.10.10/osol-0906-x86.iso
           => `osol-0906-x86.iso'
Connecting to 192.168.10.10:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 709,871,616 (677M) [application/octet-stream]

100%[====================================>] 709,871,616   76.01M/s    ETA 00:00

11:33:51 (73.18 MB/s) - `osol-0906-x86.iso' saved [709871616/709871616]

73.18 MB/s ほどの転送速度がでています。ちなみにリアルタイムの I/O statistics を見るには、以下のように dladm コマンドを使います (global zone で実行してください)。

(global zone)

# dladm show-link -s -i3 vnic0    ... トラフィック監視コマンド
LINK            IPACKETS   RBYTES   IERRORS    OPACKETS     OBYTES       OERRORS
vnic0           127454     7726611  0          182076       1469324223   0
vnic0           0          0        0          0            0            0
vnic0           0          0        0          0            0            0
vnic0           0          0        0          0            0            0
vnic0           8          816      0          16           1248         0
vnic0           5339       289972   0          9863         81142533     0
vnic0           15542      841460   0          29119        239992394    0
vnic0           15484      837928   0          29291        241410706    0
vnic0           9836       532824   0          18448        152009568    0
vnic0           0          0        0          0            0            0
vnic0           0          0        0          0            0            0

では、vnic0 に http port 番号で帯域制限をかけてみましょう。

(global zone)

(vnic0_httpflow の作成)
# flowadm add-flow -l vnic0 -a transport=tcp,local_port=80 vnic0_httpflow
# flowadm show-flow
FLOW        LINK        IPADDR                         PROTO  PORT    DSFLD
vnic0_httpflow vnic0    --                             tcp    80      --


(flow に帯域制限を設定)
# flowadm set-flowprop -p maxbw=100 vnic0_httpflow
#
# flowadm show-flowprop
FLOW         PROPERTY        VALUE          DEFAULT        POSSIBLE
vnic0_httpflow maxbw           100          --             100
vnic0_httpflow priority      --             --

もう一度 wget でダウンロードしてみます。

(zone1)

zone1# wget http://192.168.10.10/osol-0906-x86.iso
--12:15:53--  http://192.168.10.10/osol-0906-x86.iso
           => `osol-0906-x86.iso.1'
Connecting to 192.168.10.10:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 709,871,616 (677M) [application/octet-stream]

100%[====================================>] 709,871,616   12.41M/s    ETA 00:00

12:16:47 (12.47 MB/s) - `osol-0906-x86.iso.1' saved [709871616/709871616]

確かに 12.47 MB/s (100Mbps) に帯域制限されています。

ではもう一度、今度は remote_ip で帯域制限を掛けてみます。 zone1(192.168.10.11) からのパケットを flow として切り出し、この帯域を50Mbpsに制限します。

(global zone)

# flowadm add-flow -l vnic0 -a remote_ip=192.168.10.11 zone1flow
    (zone1 の IP-address は 192.168.10.11)

# flowadm show-flow
FLOW        LINK        IPADDR                         PROTO  PORT    DSFLD
zone1flow   vnic0       RMT:192.168.10.11/32           --     --      --

# flowadm set-flowprop -p maxbw=50 zone1flow

# flowadm show-flowprop
FLOW         PROPERTY        VALUE          DEFAULT        POSSIBLE
zone1flow    maxbw              50          --             50
zone1flow    priority        --             --

zone1 にて wget を実行します。

zone1# wget http://192.168.10.10/osol-0906-x86.iso
--12:42:32--  http://192.168.10.10/osol-0906-x86.iso
           => `osol-0906-x86.iso.3'
Connecting to 192.168.10.10:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 709,871,616 (677M) [application/octet-stream]

100%[====================================>] 709,871,616    5.96M/s    ETA 00:00

12:44:50 (4.92 MB/s) - `osol-0906-x86.iso.3' saved [709871616/709871616]

4.92 MB/s (50Mbps) に帯域制限されています。この時の I/O statistics は次の通りです。

(global zone でtrafficを確認)

# dladm show-link -s -i3 vnic0
LINK            IPACKETS   RBYTES   IERRORS    OPACKETS     OBYTES       OERRORS
vnic0           315631     18038505 0          541417       4421741359   0
vnic0           0          0        0          0            0            0
vnic0           0          0        0          0            0            0
vnic0           0          0        0          0            0            0
vnic0           0          0        0          0            0            0
vnic0           0          0        0          0            0            0
vnic0           835        47092    0          1625         13252705     0
vnic0           1036       57864    0          2035         16657730     0
vnic0           935        52282    0          1821         14901278     0
vnic0           1012       56712    0          1969         16113494     0
vnic0           1064       59392    0          2075         16995762     0
vnic0           1004       56136    0          1958         16030980     0
vnic0           985        54982    0          1924         15742424     0
vnic0           899        50338    0          1751         14332250     0
vnic0           959        53450    0          1882         15404284     0
....


以上、いかがだったでしょうか?Crossbow / Zone 共に高機能かつ軽量な 最強コンビです。仮想化統合の定番として是非ご活用ください。



http://blogs.sun.com/yappri/date/20090730 2009年 7月 30日 木曜日

Sunグッズ紹介(22)

Sun のロゴ入りグッズ紹介の第二十二弾です。


・Sun ロゴ入り腕時計 - Open Source バージョン
 文字盤に Duke をあしらったおしゃれな時計です。
 ブロック型のケースもセンスがよく、使わないで飾っておきたくなってしまいます。




・Sun ロゴ入りキティちゃん人形
 コードネーム "Star Kitty" の名前で知られた Sun Fire 12K 発表時に作成された懐かしい
 Goods です。サンロゴの服を着たキティーちゃんはとてもレアな一品です。




・Sun ロゴ入りボールペン - Sun StorageTek 6000 シリーズ
 グリップの部分が布製になっており、ホールド感のよいボールペンです。



・Sun ロゴ入りトートバッグ
 紺色の丈夫な生地にサンのロゴが映えます。エコバックとしても使えます。



・SUN TECH DAYS 2008 ネームタグ
 2008 年に開催した SUN TECH DAYS の記念タグです。




http://blogs.sun.com/yappri/date/20090729 2009年 7月 29日 水曜日

Solaris Cryptographic Framework を GlassFish から利用する方法

今回のブログでは、GlassFish アプリケーションサーバの HTTP(SSL) リスナーにて、UltraSPARC T1/T2 Crypto Accelerator を利用する設定を まとめます。

今回使用するアプリケーションサーバは、Sun GlassFish Enterprise Server v2.1
利用するバイナリは、Sun GlassFish Enterprise Server v2.1 with HADB for Solaris SPARC, Multi-language
sges_ee-2_1-solaris-sparc-ml.bin になります。

まずは、GlassFish のインストールを行いましょう。

  # ./sges_ee-2_1-solaris-sparc-ml.bin -console -savestate /var/tmp/sges_ee-savestate

コンソールログ
savestate ファイル

インストールが終了したら GlassFish を起動します。

  # /opt/SUNWappserver/bin/asadmin start-domain --user admin domain1

Solaris Cryptographic Framework (SCF) を構成する

Solaris Cryptographic Framework (SCF) ソフトウェアトークンには 非公開情報が含まれているため、pktool(1) コマンドを使用して トークンにパスワードを設定します。 このコマンドは、アプリケーション所有者 (今回は、GlassFish を root として実行するので、root ユーザ) としてシステムにログインすることで、ユーザーのデフォルトキーストア を初期化します。

SCF ソフトウェアトークンのピンを設定する手順

pktool setpin コマンドを実行すると、$HOME/.sunw/pkcs11_softtoken/ ディレクトリ内のソフトウェアトークンデータストアが初期化されます。 これらのファイルは、その内容を保護するために、所有者だけがアクセス できるように作成されます。つまり、ユーザーが妥当なデータストアに アクセスできるように、GlassFish を実行しているのと同じ root ユーザー として初期化を実行する必要があります。

1. root ユーザーになります。

      # su - root

2. id コマンドを実行します。

      # id
      uid=0(root) gid=0(root)

3. pktool コマンドを実行します。

      # pktool setpin
      トークンパスフレーズを入力してください: <-- 既存のパスフレーズを入力 (デフォルトは changeme)
      新しいパスフレーズを作成してください:   <-- 新しいパスフレーズ
      新しいパスフレーズを再入力してください: <-- 確認の為もう一度入力します
      パスフレーズが変更されました。          <-- パスフレーズが変更されたというメッセージが表示されます
      #

4. root のホームディレクトリ (/) に移動します。

      # cd /

5. アクセス権をチェックします。

      # ls -alrR .sunw
      .sunw:
      合計 9
      drwx------   4 root     root           5  7月 27日  17:51 pkcs11_softtoken
      drwxr-xr-x  25 root     root          29  7月 27日  17:51 ..
      drwx------   3 root     root           3  7月 27日  17:51 .

      .sunw/pkcs11_softtoken:
      合計 14
      drwx------   2 root     root           2  7月 27日  17:51 public
      drwx------   2 root     root           2  7月 27日  17:51 private
      -rw-------   1 root     root         103  7月 27日  17:51 objstore_info
      drwx------   3 root     root           3  7月 27日  17:51 ..
      drwx------   4 root     root           5  7月 27日  17:51 .

      .sunw/pkcs11_softtoken/public:
      合計 6
      drwx------   4 root     root           5  7月 27日  17:51 ..
      drwx------   2 root     root           2  7月 27日  17:51 .

      .sunw/pkcs11_softtoken/private:
      合計 6
      drwx------   4 root     root           5  7月 27日  17:51 ..
      drwx------   2 root     root           2  7月 27日  17:51 .
      sw-90# 

cryptoadm を使用して Solaris Cryptographic Framework を管理する手順

cryptoadm ユーティリティーは、システムの暗号化プロバイダ情報の表示、 各プロバイダに対する機構の構成、暗号化プロバイダのインストールまたは アンインストールを行います。
Solaris Cryptographic Framework では、ユーザーレベルのプロバイダ (PKCS#11 共有ライブラリ)、カーネルプロバイダ (ロード可能なカーネル ソフトウェアモジュール)、カーネルハードウェアプロバイダ (暗号化ハード ウェアデバイス) という 3 種類のプロバイダがサポートされます。
cryptoadm ユーティリティーには、メタスロットの機能を有効化または無効化 するサブコマンド、メタスロットの構成を表示するサブコマンド、 メタスロットの機構のポリシーを構成するサブコマンドがあります。

1. Solaris Cryptographic Framework のすべてのサービスプロバイダと、各プロバイダの暗号化機構を表示するには、cryptoadm list -m を実行します。暗号化プロバイダとして ncp と pkcs11_softtoken.so が利用できることを確認してください。NCP はカーネルプロバイダなので、出力リスト内で pkcs11_kernel.so は pkcs11_softtoken.so よりも前に表示されるはずです。

      # cryptoadm list -m

      ユーザーレベルプロバイダ:
      =====================

      プロバイダ: /usr/lib/security/$ISA/pkcs11_kernel.so
      メカニズム:
      CKM_DSA                      
      CKM_RSA_X_509                
      CKM_RSA_PKCS                 
      CKM_RSA_PKCS_KEY_PAIR_GEN    
      CKM_DH_PKCS_KEY_PAIR_GEN     
      CKM_DH_PKCS_DERIVE           
      CKM_ECDSA_KEY_PAIR_GEN       
      CKM_ECDH1_DERIVE             
      CKM_ECDSA                    

      プロバイダ: /usr/lib/security/$ISA/pkcs11_softtoken.so
      メカニズム:
      CKM_DES_CBC                  
      CKM_DES_CBC_PAD              
      CKM_DES_ECB                  
      CKM_DES_KEY_GEN
      [.... 省略 ...]
      CKM_DSA
      CKM_DSA_SHA1
      CKM_DSA_KEY_PAIR_GEN
      [.... 省略 ...]
      CKM_TLS_MASTER_KEY_DERIVE_DH 
      CKM_SSL3_KEY_AND_MAC_DERIVE  
      CKM_TLS_KEY_AND_MAC_DERIVE   
      CKM_TLS_PRF                  

      カーネルソフトウェアプロバイダ:
      ==========================
      swrand: メカニズムが表示されませんでした。
      rsa: CKM_RSA_PKCS,CKM_RSA_X_509,CKM_MD5_RSA_PKCS,CKM_SHA1_RSA_PKCS,CKM_SHA256_RSA_PKCS,CKM_SHA384_RSA_PKCS,CKM_SHA512_RSA_PKCS
      md5: CKM_MD5,CKM_MD5_HMAC,CKM_MD5_HMAC_GENERAL
      sha2: CKM_SHA256,CKM_SHA256_HMAC,CKM_SHA256_HMAC_GENERAL,CKM_SHA384,CKM_SHA384_HMAC,CKM_SHA384_HMAC_GENERAL,CKM_SHA512,CKM_SHA512_HMAC,CKM_SHA512_HMAC_GENERAL
      sha1: CKM_SHA_1,CKM_SHA_1_HMAC,CKM_SHA_1_HMAC_GENERAL
      blowfish448: CKM_BLOWFISH_ECB,CKM_BLOWFISH_CBC
      arcfour2048: CKM_RC4
      aes256: CKM_AES_ECB,CKM_AES_CBC,CKM_AES_CTR
      des: CKM_DES_ECB,CKM_DES_CBC,CKM_DES3_ECB,CKM_DES3_CBC

      カーネルハードウェアプロバイダ:
      ==========================
      ncp/0: CKM_DSA,CKM_RSA_X_509,CKM_RSA_PKCS,CKM_RSA_PKCS_KEY_PAIR_GEN,CKM_DH_PKCS_KEY_PAIR_GEN,CKM_DH_PKCS_DERIVE,CKM_ECDSA_KEY_PAIR_GEN,CKM_ECDH1_DERIVE,CKM_ECDSA
      sw-90# 

2. 次のユーザーレベル機構を無効にして、その機構が NCP によって実行されるようにします。

      # cryptoadm disable provider=/usr/lib/security/'$ISA'/pkcs11_softtoken.so \
      mechanism=CKM_SSL3_PRE_MASTER_KEY_GEN,\
      CKM_SSL3_MASTER_KEY_DERIVE,CKM_SSL3_KEY_AND_MAC_DERIVE,CKM_SSL3_MASTER_KEY_DERIVE_DH,\
      CKM_SSL3_MD5_MAC,CKM_SSL3_SHA1_MAC

3.ユーザーレベル機構が無効になったことを確認します。

      # cryptoadm list -p provider=/usr/lib/security/'$ISA'/pkcs11_softtoken.so
      /usr/lib/security/$ISA/pkcs11_softtoken.so: すべてのメカニズムが有効です,
      except CKM_SSL3_SHA1_MAC,CKM_SSL3_MD5_MAC,CKM_SSL3_MASTER_KEY_DERIVE_DH,
      CKM_SSL3_KEY_AND_MAC_DERIVE,CKM_SSL3_MASTER_KEY_DERIVE,
      CKM_SSL3_PRE_MASTER_KEY_GEN. random が有効になっています。

SCF プロバイダを構成する手順

NSS では secmod.db を使用して、利用可能な PKCS#11 モジュールを管理します。NSS に付属の CLI のセキュリティーモジュール データベースツール modutil を使用すると、secmod.db ファイル内の PKCS#11 モジュール情報を管理することができます。このセキュリティーモジュールデータベースツールには、PKCS#11 モジュールの追加または削除、パスワードの変更、デフォルトの設定、モジュールの内容の表示、スロットの有効化または無効化などの機能があります。modutil CLI は Messaging Server ソフトウェアにバンドルされており、Messaging-Server-Root/sbin ディレクトリにあります。次の例では、Messaging-Server-Root/sbin ディレクトリから modutil を実行し、cert8.db、secmod.db、および key3.db が config ディレクトリ (Messaging-Server-Root/config) にあると想定します。

1. 利用可能なすべての PKCS#11 モジュールを一覧表示します。

modutil を使用すると、利用可能なすべての PKCS#11 モジュールを一覧表示できます。デフォルトでは、NSS には内部 PKCS#11 モジュールがあります。

      # cd /opt/SUNWappserver/domains/domain1/config
      # /opt/SUNWappserver/lib/modutil -dbdir . -nocertdb -list

      Using database directory ../config...
      Listing of PKCS #11 Modules
      -----------------------------------------------------------
        1. NSS Internal PKCS #11 Module
               slots: 2 slots attached
              status: loaded

               slot: NSS Internal Cryptographic Services
              token: NSS Generic Crypto Services

               slot: NSS User Private Key and Certificate Services
              token: NSS Certificate DB
      -----------------------------------------------------------

2. デフォルトの NSS ソフトウェアトークンの内容を表示します。

      # /opt/SUNWappserver/lib/certutil -L -d .
      verisignclass1ca                                             T,c,c
      thawtepersonalpremiumca                                      T,c,c
      baltimorecodesigningca                                       T,c,c
      verisignclass2g2ca                                           T,c,c
      verisignclass3g3ca                                           T,c,c
      entrustglobalclientca                                        T,c,c
      entrustsslca                                                 T,c,c
      verisignclass3g2ca                                           T,c,c
      thawtepremiumserverca                                        T,c,c
      entrust2048ca                                                T,c,c
      valicertclass2ca                                             T,c,c
      gtecybertrust5ca                                             T,c,c
      s1as                                                         u,u,u
      equifaxsecureebusinessca1                                    T,c,c
      verisignclass1g3ca                                           T,c,c
      godaddyclass2ca                                              T,c,c
      thawtepersonalbasicca                                        T,c,c
      verisignclass1g2ca                                           T,c,c
      verisignclass2g3ca                                           T,c,c
      equifaxsecureca                                              T,c,c
      entrustclientca                                              T,c,c
      verisignserverca                                             T,c,c
      geotrustglobalca                                             T,c,c
      equifaxsecureebusinessca2                                    T,c,c
      verisignclass3ca                                             T,c,c
      verisignclass2ca                                             T,c,c
      gtecybertrustglobalca                                        T,c,c
      entrustgsslca                                                T,c,c
      thawtepersonalfreemailca                                     T,c,c
      thawteserverca                                               T,c,c
      baltimorecybertrustca                                        T,c,c
      starfieldclass2ca                                            T,c,c
      equifaxsecureglobalebusinessca1                              T,c,c

Solaris Cryptographic Framework をサービスプロバイダとして追加する手順

Messaging Server は、NSS 組み込み型ソフトウェアトークンを暗号化に使用するように構成されています。NSS 組み込み型ソフトウェアトークンでは、PKCS#11 を使用して暗号にアクセスします。Solaris Cryptographic Framework のユーザーレベル暗号化フレームワークを使用するように、Messaging Server の構成を変更することができます。その方法は簡単で、/usr/lib/libpkcs11.so ライブラリにリンクして、PKCS#11 の機能に直接アクセスするだけです。つまり、Solaris Cryptographic Framework を PKCS#11 モジュールとして登録します。

1. PKCS#11 ライブラリ /usr/lib/libpkcs11.so を Messaging Server ソフトウェアに登録し、Sun Metaslot というスロットを有効にします。

      # ./modutil -dbdir ../config/ -nocertdb -add "Solaris Crypto Framework" -libfile
      /usr/lib/libpkcs11.so -mechanisms RSA

      WARNING: Performing this operation while the browser is running could cause corruption
      of your security databases. If the browser is currently running,
      you should exit browser before continuing this operation. Type 'q ' to abort,
      or  to continue:

      Using database directory ../config...

      Module "Solaris crypto Framework" added to database.

Sun Metaslot スロットを有効にする手順

1. 次の modutil コマンドを実行します。

      # ./modutil -dbdir ../config/ -nocertdb -disable "Solaris Crypto Framework"

      WARNING: Performing this operation while the browser is running
      could cause corruption of your security databases.
      If the browser is currently running,
      you should exit browser before continuing this operation.
      Type 'q ' to abort, or  to continue:
      Using database directory ../config...

      Slot "Sun Metaslot" disabled.
      Slot "ncp/0 Crypto Accel Asym 1.0" disabled.

2. 次の modutil コマンドを実行します。

      # ./modutil -dbdir ../config/ -nocertdb -enable "Solaris Crypto Framework" -slot "Sun Metaslot"
      WARNING: Performing this operation while the browser is running
      could cause corruption of your security databases.
      If the browser is currently running,
      you should exit browser before continuing this operation.
      Type 'q ' to abort, or  to continue:

      Using database directory ../config...
      Slot "Sun Metaslot" enabled.

3. 次の modutil コマンドを実行し、Solaris Crypto Framework が正常に追加されたことを確認します。

      # ./modutil -dbdir ../config/ -nocertdb -list

      Using database directory ../config...

      Listing of PKCS #11 Modules
      -----------------------------------------------------------
        1. NSS Internal PKCS #11 Module
               slots: 2 slots attached
              status: loaded

               slot: NSS Internal Cryptographic Services
              token: NSS Generic Crypto Services

               slot: NSS User Private Key and Certificate Services
              token: NSS Certificate DB

        2. Solaris Crypto Framework
              library name: /usr/lib/libpkcs11.so
               slots: 2 slots attached
              status: loaded

               slot: Sun Metaslot
              token: Sun Metaslot

               slot: ncp/0 Crypto Accel Asym 1.0
              token: ncp/0 Crypto Accel Asym 1.0
      -----------------------------------------------------------

NSS ソフトウェアトークンから証明書と鍵のペアをエクスポートする手順

1. SSL のリスナーとして設定されている http-listener-2 の Cert nickname を確認する

      # /opt/SUNWappserver/bin/asadmin get server.http-service.http-listener.http-listener-2.ssl.\*
      管理ユーザー名を入力してください>admin
      管理パスワードを入力してください>
      server.http-service.http-listener.http-listener-2.ssl.cert-nickname = s1as
      server.http-service.http-listener.http-listener-2.ssl.client-auth-enabled = false
      server.http-service.http-listener.http-listener-2.ssl.ssl2-ciphers = 
      server.http-service.http-listener.http-listener-2.ssl.ssl2-enabled = false
      server.http-service.http-listener.http-listener-2.ssl.ssl3-enabled = true
      server.http-service.http-listener.http-listener-2.ssl.ssl3-tls-ciphers = 
      server.http-service.http-listener.http-listener-2.ssl.tls-enabled = true
      server.http-service.http-listener.http-listener-2.ssl.tls-rollback-enabled = true
      # 

2. 次のコマンドを実行し、内部トークンに含まれる s1as の証明書を PKCS#12 形式のファイルにエクスポートします。

      # /opt/SUNWappserver/lib/pk12util -o /tmp/s1as -n s1as -d /opt/SUNWappserver/domains/domain1/config
      Enter Password or Pin for "NSS Certificate DB": <-- GlassFish インストール時に指定したマスターパスワードを入力
      Enter password for PKCS12 file:                 <-- 出力する PKCS#12 形式のファイルのパスワードを設定
      Re-enter password:                              <-- 確認の為再度入力
      pk12util: PKCS12 EXPORT SUCCESSFUL
      sw-90#

証明書と鍵のペアを Sun Metaslot (SCF) にインポートする手順

1. 次の pk12util コマンドを実行します。

      # /opt/SUNWappserver/lib/pk12util -i /tmp/s1as -d /opt/SUNWappserver/domains/domain1/config -h "Sun Metaslot"
       Enter Password or Pin for "Sun Metaslot":  <-- pktool setpin にて入力したパスワードを入力
       Enter password for PKCS12 file:            <-- PKCS#12 形式のファイルのパスワードを入力
       pk12util: PKCS12 IMPORT SUCCESSFUL

証明書と鍵のペアが正常にインポートされたことを確認する手順

この手順では、証明書と鍵のペアがトークンに正常にインポートされたことを確認します。

1. 次の certutil コマンドを実行します。

      # /opt/SUNWappserver/lib/certutil -L -d /opt/SUNWappserver/domains/domain1/config -h "Sun Metaslot"
      Enter Password or Pin for "Sun Metaslot": <-- pktool setpin にて入力したパスワードを入力
      Sun Metaslot:s1as                                            u,u,u

2. 次の certutil コマンドを実行します。

      # /opt/SUNWappserver/lib/certutil -K -d /opt/SUNWappserver/domains/domain1/config -h "Sun Metaslot"
      Enter Password or Pin for "Sun Metaslot": <-- pktool setpin にて入力したパスワードを入力
      <0> s1as

GlassFish から Solaris Crypto Framework を利用する為の手順

1. SSL のリスナーとして設定されている http-listener-2 の Cert nickname を Sun Metaslot 内にある s1as に変更

      # /opt/SUNWappserver/bin/asadmin set --user admin server.http-service.http-listener.http-listener-2.ssl.cert-nickname="Sun Metaslot:s1as"
      管理パスワードを入力してください>
      server.http-service.http-listener.http-listener-2.ssl.cert-nickname = Sun Metaslot:s1as
      # 

2. GlassFish を再起動

      # /opt/SUNWappserver/bin/asadmin stop-domain
      ドメイン domain1 が停止しました。
      # /opt/SUNWappserver/bin/asadmin start-domain --user admin domain1
      ドメイン domain1 を起動しています。お待ちください。
      Default Log location is /opt/SUNWappserver/domains/domain1/logs/server.log.
      管理パスワードを入力してください>
      マスターパスワードを入力してください>
      NSS スロット Sun Metaslot のパスワードを入力してください>
      出力を /opt/SUNWappserver/domains/domain1/logs/server.log にリダイレクトしています
      ドメイン domain1 が起動しました。
      ドメイン [domain1] はその設定で [Sun GlassFish Enterprise Server v2.1 (9.1.1) (build b60e-fcs)] を実行しています。ログは [/opt/SUNWappserver/domains] にあります。
      管理コンソールは [https://localhost:4848] で使用できます。
      "asadmin" コマンドにも同じポート [4848] を使用します。
      ユーザーの Web アプリケーションは次の URL で使用できます:
      [http://localhost:8080 https://localhost:8181 ]。
      次の web-contexts を使用できます:
      [/web1  /__wstx-services ]。
      標準の JMX クライアント (JConsole など) はドメイン管理のために JMXServiceURL:
      [service:jmx:rmi:///jndi/rmi://sw-90.japan.sun.com:8686/jmxrmi] に接続できます。
      ドメインは少なくとも次のポートで接続を待機しています:
      [8080 8181 4848 3700 3820 3920 8686 ]。
      ドメインはアプリケーションサーバークラスタおよびその他のスタンドアロンインスタンスをサポートします。

      sw-90# 

Crypto Accelerator が正常に動作している事を確認する

1. 設定した HTTP リスナーのポート番号を確認する

      # //opt/SUNWappserver/bin/asadmin get server.http-service.http-listener.http-listene rr-2.\*
      管理ユーザー名を入力してください>admin
      管理パスワードを入力してください>
      server.http-service.http-listener.http-listener-2.acceptor-threads = 1
      server.http-service.http-listener.http-listener-2.address = 0.0.0.0
      server.http-service.http-listener.http-listener-2.blocking-enabled = false
      server.http-service.http-listener.http-listener-2.default-virtual-server = server
      server.http-service.http-listener.http-listener-2.enabled = true
      server.http-service.http-listener.http-listener-2.external-port = 
      server.http-service.http-listener.http-listener-2.family = inet
      server.http-service.http-listener.http-listener-2.id = http-listener-2
      server.http-service.http-listener.http-listener-2.port = 8181
      server.http-service.http-listener.http-listener-2.redirect-port = 
      server.http-service.http-listener.http-listener-2.security-enabled = true
      server.http-service.http-listener.http-listener-2.server-name = 
      server.http-service.http-listener.http-listener-2.xpowered-by = true
      # 

2. SSL アクセス前の利用状況を確認する

      # kstat -n ncp0 -s rsaprivate
      module: ncp                             instance: 0
      name:   ncp0                            class:    misc
              rsaprivate                      1

      #

3. https://sw-90.japan.sun.com:8181/ へアクセスする

4. SSL アクセス後、再度利用状況を確認する
※ rsaprivate の値が増えているので、正常に利用している事が確認できる

      # kstat -n ncp0 -s rsaprivate
      module: ncp                             instance: 0
      name:   ncp0                            class:    misc
              rsaprivate                      2

      #

参考情報

http://blogs.sun.com/yappri/date/20090728 2009年 7月 28日 火曜日

Solaris で Oracle を効率的に稼働させるための仕組み

はじめに

wikis.sun.com に Oracle on Sun に関する面白い情報がありましたのでご紹介したいと思います。Oracle データベースを Solaris 上で効率よく安定して稼働させるために、Solaris にどんな技術が実装されて来たのかが時系列でまとまっています。全ての技術を網羅している訳ではありませんが、皆さまのご参考になれば幸いです。元の文書に大きく加筆修正を施していますので、もしお時間があれば元文書もご覧下さい。

初期の技術革新

1993 年、Solaris 2.2 の ISM

Solaris 2.2 で ISM(Intimate Shared Memory) が実装されました。

ISM は、共有メモリ のアドレス変換テーブルをプロセス間で共有する機能です。ISM を使用しない場合は共有メモリのアドレス変換テーブルはプロセス毎に作成されますが、沢山のプロセスが同じ共有メモリを使用すると同じマッピング情報が重複して保持される事になり、カーネルメモリと TLB が無駄になります。SGA に接続する Oracle プロセスの数が多い大規模なシステムでは ISM の共有ページテーブル機能は非常に効果的です。また、ISM を使用して確保されるメモリページは物理メモリ上にロックされるという利点もあります。これにより ISM を使用する Oracle の SGA は常に物理メモリ上に常駐し、スワップアウトされないことが保証されます。これも大規模システムで性能を発揮するのに効果的です。ISM のもう一つの特徴として、大規模ページのサポートがあります。SPARC システムのデフォルトのページサイズは通常 8KB ですが、ISM を使用すると可能な場合は 4MB など、より大きなページサイズが適用されるようになります。大規模ページサイズを使用することでキャッシュミスを抑制し処理の効率が上がります。共有ページテーブル、大規模ページサポート、ページのロックという大規模システムでデータベースを効率よく実行するたに必要が技術が ISM によって実現しました。

試しに Oracle のサーバプロセスに pmap コマンドを実行すると "ism" が使用されていることが分かります。

 # pmap -xs 21323
 21323:  oraclefoo (LOCAL=NO)
          Address     Kbytes        RSS       Anon     Locked Pgsz Mode   Mapped File
 ...
 0000000380000000    2592768    2592768          -    2592768   4M rwxsR    [ ism shmid=0x64 ]
 # ipcs -ma
 IPC status from  as of Tue Jul  7 10:50:01 JST 2009
 T         ID      KEY        MODE        OWNER    GROUP  CREATOR   CGROUP NATTCH      SEGSZ  CPID  LPID   ATIME    DTIME    CTIME 
 Shared Memory:
 m        100   0x8bba0d8c --rw-r-----   oracle      dba   oracle      dba     21 2654994432   423  4957  0:00:01  0:01:11 12:35:55

Jim Mauro が書いた Sun World の記事には ISM の詳細が解説されています。併せてご覧下さい。

ISM の実装は OpenSolaris のソースコードを "ISM" で検索して下さい。

"Oracle Database 管理者リファレンス" の中にも ISM に関する記述があります。

SDC(Solaris Developer Connection) にも ISM の使い方が詳解されています。

Java で ISM を使用する方法はこちらをご覧下さい。

ラッチ保有時のプリエンプション抑制

schedctl_init(3C) を始めとする schedctl API は Solaris のスケジューラによるプリエンプションを抑制するインターフェイスです。クリティカルなロックを保持しているスレッドがプリエンプトされてスレッドの優先順位の逆転が起きてしまうのを防ぎます。ロックを保持しているアプリケーションは schedctl API を使って、スケジューラに対してもう少し長い間 CPU を使用できるように依頼することができます。schedctl API は Oracle ではラッチで使用されています。ラッチを保持しているスレッドがスケジューラによって休眠させられて並列度が制限されることを防ぎます。

64bit Solaris

10 年以上前のことですが、Solaris は 2.7 から 64bit 実装が登場しました。64bit Solaris 上で 64bit Oracle を使用することにより、4GB 以上のメモリを搭載したマシンでもリソースを有効に使える様になりました。当時のフラッグシップだった Sun Enterprise 10000 は最大 64GB のメモリを搭載可能なマシンでした。一方、32bit 版の Oracle では genksms コマンドを使用して初めて 2GB 以上の SGA を確保する事が出来ました。

10 万 tpmC の壁を突破

1999 年、Sun と Oracle は TPC-C で 10 万 tpmC を超えて業界最高性能を達成しました。その時の記録は Sun Enterprise 10000 上の Solaris 2.7 と Oracle 8i の組み合わせで 115,395.73 tpmC です。この結果は、64bit Solaris, ISM, schedctl API 等、ここまで挙げて来た全ての技術革新の結実でした。

当時のプレスリリースはこちらです。

Solaris 9 が 2002 年に登場してから

DISM(Dymanic ISM)

DISM は共有ページテーブル、大規模ページサポートなどの ISM の機能に加えて、必要に応じてサイズを増やしたり、ページをロック/解除する機能を備えています。DISM は ISM の恩恵を受けつつ Oracle 9i から導入された動的 SGA を使用するために必要な機能です。Oracle の SGA_MAX_SIZE, MEMORY_TARGET または MEMORY_MAX_TARGET パラメータが適切に設定されている場合に DISM が使用されます。

また、Solaris の RCM(Reconfiguration Coordination Manager) のスクリプトと DISM, 動的 SGA を組み合わせることで、動的システムドメインからメモリを取り除く際も Oracle を停止せずに運用することが可能です。

Solaris 8 1/01 の機能比較表に DISM についての記載があります。

  • http://jp.sun.com/practice/software/solaris/jp/fcc/ucc-details101.html
  •  DISM には 共有ページテーブル、大規模ページサポート、ページのロックなど、
     ISM と同じ特長があります。DISM ではこれに加え、他のアプリケーションに
     メモリを解放するためにページのロックが解除され、共有メモリセグメントの
     サイズを増やすために新たなページがロックされます。この機能により、Oracle
     データベースのパフォーマンスが著しく向上します。
    

Oracle 11g の管理者リファレンスにも DISM についての記載があります。

NUMA システムへの最適化

Solaris 9 9/02 から MPO(Memory Placement Optimization) という機能が実装されました。MPO は NUMA システム上でアプリケーションを効率よく動作させるための仕組みです。CPU からメモリまでの距離に応じて lgroup という区画を設定し、アプリケーションが使用するメモリの割り当て位置やスレッドがスケジュールされる CPU を 最適化します。lgroup へのインターフェイスとして lgrp_init(3LGRP) を始めとする lgroup API も実装されています。

Oracle では SGA のアクセスに lgroup API と madvise(3C) を使用することで NUMA マシンで性能が向上しています。これは Opteron や Nehalem を搭載した NUMA 構成のマシンでも非常に有効です。

Dual Core Opteron x4CPU のマシンで lgroup を表示させた例です。この場合は lgroup が 4 つ作成されています。

 # uname -m
 i86pc
 # kstat -p 'unix:0:system_misc:ncpus'
 unix:0:system_misc:ncpus        8
 # kstat -p 'lgrp:::cpus'             
 lgrp:1:lgrp1:cpus       2
 lgrp:2:lgrp2:cpus       2
 lgrp:3:lgrp3:cpus       2
 lgrp:4:lgrp4:cpus       2

MPO のドキュメントには Oracle を使用する際の MPO の利点が詳解されています。

Oracle と MPO の関係に関するプレゼンテーション資料もご覧下さい。

こちらは Jonathan Chew による MPO のオーバービューです。

大規模ページサポート

既に ISM, DISM の項で取り上げましたが、Solaris 9 より複数のメモリページサイズがサポートされています。この機能は MPSS(Multiple Page Size Support) と呼ばれ、プロセスが 8KB 以上のページサイズを使用することが可能になっています。特に Oracle の様にメモリの使用量が大きいソフトウェアでは MPSS を使用することで TLB ミスを抑制し、性能を向上することが可能です。

Sun SPARC Enterprise T5220 でサポートされているページサイズは以下の通りです。T5220 では最大 256MB までのページサイズを指定することが可能になっており、CMT マシンでは MPSS は特に有効です。

 # uname -im
 sun4v SUNW,SPARC-Enterprise-T5220
 # pagesize -a
 8192
 65536
 4194304
 268435456

Oracle の SGA が ISM, DISM により大規模ページサイズを使用していることは既にご紹介している通りです。Oracle 10g では memcntl(2) 関数を通じて PGA にラージページを使用することが可能です。Oracle のパラメータに "_realfree_heap_pagesize_hint=4M" を指定すると mmap() されたヒープがデフォルトの 8KB ではなく 4MB のページを使用する様になります。

MPSS についてはこちらもご覧下さい。

poll の改良

アプリケーションのスケーラビリティを高めるために poll インターフェイスにも改良が加わっています。Solaris 固有の機能として /dev/poll が用意されています。また Solaris 10 では Event Completion API が追加されました。

  • /dev/poll デバイス
  •  % ls -l /dev/poll
     lrwxrwxrwx   1 root     root          29 Dec 12  2007 /dev/poll -> ../devices/pseudo/poll@0:poll
    

スケジューラの改善

Solaris のスケジューラにも改善が施されています。

FX (固定優先順位)スケジューリングクラスは、優先順位の逆転を軽減するために導入されました。FX スケジューリングクラスを使用すると Oracle LGWR プロセスを他の標準的なプロセスより高い優先順位を与えることが可能になります。優先順位の設定は priocntl(1) コマンドを使用します。設定したスケジューリングクラスは子プロセスにも継承されるので、Oracle のリスナープロセスに適切な優先順位を設定しておけば、サーバプロセスも同じ優先順位で実行されます。

また、Oracle CRS ではリアルタイムスケジューリングクラスを使用しています。Oracle RAC は LMS デーモンにリアルタイムクラス使用しており、キャッシュフュージョンは高い優先順位で行われます。

  • Solaris で用意されているスケジューリングクラスの一覧
  •  % priocntl -l
     CONFIGURED CLASSES
     ==================
     
     SYS (System Class)
     
     TS (Time Sharing)
             Configured TS User Priority Range: -60 through 60
     
     RT (Real Time)
             Maximum Configured RT Priority: 59
     
     FX (Fixed priority)
             Configured FX User Priority Range: 0 through 60
     
     IA (Interactive)
             Configured IA User Priority Range: -60 through 60
    

UFS direct I/O の改良

UFS direct I/O は Solaris 2.6 から提供されていますが、Solaris 8 Update 3 から read / write 共に並列実行される様に改良されました。従来の direct I/O はファイル毎に書き込み時の並列度を 1 に制限するロックが設けられており、データベースの更新が多い場合は、このロックの取得が大きなオーバーヘッドになっていました。改善された direct I/O ではファイルサイズに変更がない場合は並列に処理されます。この改善により direct I/O は raw デバイスに対する I/O に近い性能を発揮できる様になりました。

Oracle で direct I/O を使用するには FILESYSTEMIO_OPTIONS 初期化パラメータに SETALL を指定するか UFS のマウントオプションに directio を指定します。

UFS direct I/O の並列処理について詳しくまとめられています。

Solaris Internals のフォローアップページにも詳しく記載されています。

MONITORING AND TUNING ORACLE のブループリントです。

以下は Oracle のドキュメントに記載されている FILESYSTEMIO_OPTIONS 初期化パラメータ情報です。

Solaris 10 が 2005 年に登場してから

MPSS サポートの拡張

MPSS がサポートするページサイズが 256MB まで拡大されました。この最適化はデフォルトで有効になっており、CMT サーバ上で Oracle を稼働させる際に特に性能が向上します。

Ravi の Blog には Sun Fire T2000 サーバ上でデータベースを稼働させた場合のラージページの恩恵がまとめられています。

プロセス間通信用パラメータの管理

project を利用することで、プロセス間通信のパラメータを reboot 無しに変更できる様になりました。Oracle 用のカーネルパラメータも project で管理します。

細かな変更点

その他の修正はこちらにも記載されていますので是非ご参照下さい。

dbwr が aio_waitn(3RT) API を使うときの効率性が上がりました。

おわりに

ID 4503970 の レポート にある "There is also a request by Oracle..." という言葉が示しているように、データベースを効率よく動かすために Solaris に実装された仕組みは沢山あります。ここに挙っていない技術でも DTrace, ZFS, FireEngine, Crossbow, Zone, KAIO, libaio, proc tools, Sun Cluster, QFS 等、Solaris には Oracle を運用する上で便利だったり性能向上に貢献する技術が沢山含まれています。また、Solaris が元々備えている高いスケーラビリティや高負荷時の安定性、障害解析能力などの特徴はデータベースを稼働させるのに非常に役立ちます。こういった話もいずれ機会があればご紹介させて頂きたいと思います。

以上、Oracle と Solaris の歴史を機能実装的な側面からご紹介しました。何故 Solaris なのかという疑問が浮かんだ時に一つの答えとなれば幸いです。

http://blogs.sun.com/yappri/date/20090724 2009年 7月 24日 金曜日

DTrace の flowindent オプション紹介

今回は、DTrace の flowindent オプションを紹介します。

flowindent オプションは、モニタリングするプログラムに対して、関数の開始(entry)と 終了(return)の出力をインデント(->) を付けて表示させる事により、関数の 入れ子関係を把握しやすくする効果があります。

実際に使用する方法は、 D スクリプトの中に flowindent を定義するか、DTrace 実 行時に -F オプションを付与して実行します。

それでは、実際に flowindent の効果を確認してみましょう。
モニタリングするサンプルとして、下記テストプログラムを実行ファイル名 a.out で作成します。

テストプログラム
int main()
{
    func1();
    func2();
}

int func1()
{
    func3();
}

int func2()
{
    func1();
}

int func3()
{
    func4();
}

int func4()
{
}

テストプログラムに対する関数の入れ子を確認する為に下記 D スクリプト を作成します。今回は、D スクリプトの中に flowindent オプションを 埋め込みました。また、関数の呼び出しを確認する為に pid プロバイダを 使用しております。

サンプル D スクリプト(flowindent.d)
#pragma D option flowindent

pid$target:a.out::entry
{
}

pid$target:a.out::return
{
}

早速 flowindent.d を実行してみましょう。

flowindent.d 実行結果
# dtrace -s flowindent.d -c a.out

dtrace: script 'pid.sample.d' matched 15 probes
dtrace: pid 1196 exited with status 16
CPU FUNCTION
  0  -> _start
  0    -> __fsr
  0    <- __fsr
  0    -> func1
  0      -> func3
  0        -> func4
  0        <- func4
  0      <- func3
  0    <- func1
  0    -> func2
  0      -> func1
  0        -> func3
  0          -> func4
  0          <- func4
  0        <- func3
  0      <- func1
  0    <- func2
  0  <- main

このように、関数の入りと出がインデントを付けて表示されました。
DTrace を使用しないで関数の入れ子状態を確認するデバッグには、よく関数の入りと出の 部分に printf() 文を入れて確認していましたが、DTrace を使用すればソースプログ ラムにデバッグ用のコード を埋め込む修正も必要ないですし、当然、確認の為にリコンパイルも必要ありません。

また、各関数を呼び出した回数や、各関数毎の実行時間の累積も DTrace を使うと 集積関数を使って簡単に計測する事ができます。

関数の呼び出し回数を計測するスクリプト(func_count.d)
pid$target:a.out::entry
{
    @counts[probefunc] = count();
}

pid$target:a.out::return
{
}

func_count.d 実行結果
# dtrace -s func_count.d -q -c a.out

  __fsr                                         1
  _start                                        1
  func2                                         1
  func1                                         2
  func3                                         2
  func4                                         2
このように、各関数を実行した回数を集計して表示する事ができます。

関数の実行時間累積を計測するスクリプト(exectime.d)

pid$target:a.out::entry
{
    self->ts[probefunc] = timestamp;
}

pid$target:a.out::return
/probefunc != "main"/
{
    @sum[probefunc] = sum(timestamp - self->ts[probefunc] );

    self->ts[probefunc] = 0;
}

exectime.d 実行結果

# dtrace -s exectime.d -q -c a.out

  __fsr                                      9923
  func4                                     10553
  func2                                     32865
  func3                                     34526
  func1                                     57725


このように、各関数を実行した累積時間を集計して表示する事ができます。

ここで使用した PID プロバイダは、ユーザが作成したプログラムのパフォーマンス 計測にとても便利ですので、開発者の方は是非マスターして下さい。


(参考情報)
過去の 「やっぱり Sun がスキ!」blog 記事一覧はこちらを参照下さい。 http://wikis.sun.com/display/yappri/Home