Windows server上に、Windowsドメイン認証のできるSubversionサーバを構築してハマる。
主な環境
サーバ
CPU | Intel(R) Xeon(R) CPU 5160 @ 3.00GHz |
---|---|
Memory | 3.00GB |
O/S | Microsoft Windows Server 2003 Standard Edition Service Pack2 |
その他 | Apache/2.2.13 (Win32), SVN/1.6.4 |
Compiled in modules: core.c mod_win32.c mpm_winnt.c http_core.c mod_so.c|
httpd.confに追加
LoadModule sspi_auth_module modules/mod_auth_sspi.so LoadModule dav_svn_module modules/mod_dav_svn.so LoadModule authz_svn_module modules/mod_authz_svn.so <Location /svn> DAV svn SVNPath "E:/svnrepository" AuthType SSPI AuthName "Authentication request from Subversion." SSPIAuth On SSPIAuthoritative On SSPIDomain MYDOMAIN SSPIOfferBasic On Require valid-user </Location>
クライアント(代表例)
CPU | Intel(R) Pentium(R) 4 CPU 2.80GHz |
---|---|
Memory | 2.50GB |
O/S | Windows XP Professional Version 2002 Service Pack 2 |
その他 | TortoiseSVN 1.6.5, Subversion コマンドラインクライアント, バージョン 1.6.4. |
一応、こんな感じで基本動作はするようになった。
が、念のため、開発ピーク時の負荷を想定して、20人がかりで一斉にcheckoutをかけてみたところ、Apacheがダウン。
ここから、ハマり道が始まった。
同時接続数が多すぎて捌ききれないのかもしれない
httpd-mpm.confで、以下の値を調整。
<IfModule mpm_winnt_module> # 150→500 ThreadsPerChild 500 # 0→10000 MaxRequestsPerChild 10000 </IfModule>
もう一度同じ負荷をかけたところ、Apacheが落ちなかったので、これで安心と思ったら、しばらくたつとやっぱり落ちた。
どこに負荷がかかっているのだろうか
Windows タスク マネージャでパフォーマンスタブを確認。
PF使用量が非常に高い。
プロセスタブを確認すると、httpd.exeが最大で約2GBも使用している。パフォーマンスモニターのカウンタログで、Processor/Memory/PhysicalDiskの負荷状況を記録すると、Memory/Available MBytesがどんどん落ちていく。CPU/HDDの負荷は許容範囲内。
負荷テストを毎度20人がかりで行うのはしんどい
use strict; use warnings; my $pid; for(my $i = 0; $i < 10; $i++){ mkdir($i); if ($pid = fork()){ print "親プロセス(" . $pid . ")です。\n"; } elsif(defined $pid){ print $i . ":子プロセス(" . $pid . ")です。\n"; chdir($i); system("svn co http://foo/svn --username bar --password baz"); last; } else{ print "親でも子でもないです。\n"; } }
同一IPからのアクセスになるが、ちゃんと別々のフォルダにcheckoutされ、負荷テストの状況(メモリ空き容量がどんどん減っていく)を再現できた。処理速度的にはクライアント側のDisk I/Oが律速になっているようだが、問題は再現する。
問題の元がメモリリークならば
何度も負荷テスト→負荷テスト→負荷テスト→サーバのメモリ逼迫→何かの拍子にApacheダウンというのを繰り返してみて、感覚的に"svn coするとどんどんApacheがメモリを喰う"という線が濃厚になってきた。
Windows + Apacheの環境では、AcceptEXが問題になるケースがあるというが、今回の環境では、error.logにはその兆候は出ていない。
(参考)mpm_winnt - Apache HTTP サーバ
メモリリークに対して根本対策をする(hackする?)スキルがないため、プロセスをリセットすることで暫定対策を図る。
httpd-mpm.confで、以下の値を調整。
<IfModule mpm_winnt_module> # 10000→5 MaxRequestsPerChild 5 </IfModule>
何度も負荷テスト→負荷テスト→負荷テスト→…と繰り返すと、確かにプロセスがリセットされ、メモリ逼迫が解除された。
svn coの場合は、いくつファイルをダウンロードしようが、コマンド1回で、Request1回とカウントされている気がする(KeepAliveが効いてるせいかもしれない)が、コマンド5回でリセットがかかるかというとそうではないようだ。
おそらく仕掛かりのセッションが全部終わったときに、処理したRequest数が閾値を超えていればリセット、としている気がする。
長いことリセットがかからないときがある
何度も負荷テスト→負荷テスト→負荷テスト→…と繰り返すと、どうもリセットがかからないことがある。
前回の想像(仕掛かりのセッションが全部終わったときにリセット)が正しければ、何かのセッションが残ったままのはず。
httpd.confで、以下の値を有効化(#を外す)
LoadModule status_module modules/mod_status.so
ブラウザでhttp://foo/server-statusにアクセスすると、Mode of operationが"K" Keepalive (read)のままのやつがいる。
RequestはPROPFIND /CVS HTTP/1.1などとなっていて、svnサービスと関係ないリソースに対して行っている。
CVSの文字から、TortoiseCVSを疑ってみる。
共有フォルダを見たときに、TortoiseCVSが何かのバグで、WebDAVアクセスを試みているのでは?と思い、TortoiseCVS 1.2.2からTortoiseCVS 1.10.10にアップデートしてみた。
すると、PROPFIND /CVS HTTP/1.1は現れなくなった。
が、なぞのPROPFIND投げっぱなしKeep攻撃は他にもまだやってくる。
またMicrosoftか
一体なんのプログラムがPROPFIND攻撃をしてくるのか確かめるべく、access.logでユーザーエージェントを記録するようにhttpd.confで、以下の値を変更(common→combind)
CustomLog "logs/access.log" combined
そうして待ち構えたところ、見事にトラップにはまってくれたのがコレ。
xxx.xxx.xxx.xxx - - [21/Oct/2009:12:21:33 +0900] "OPTIONS / HTTP/1.1" 200 - "-" "Microsoft-WebDAV-MiniRedir/5.1.2600" xxx.xxx.xxx.xxx - - [21/Oct/2009:12:21:33 +0900] "PROPFIND /hoge HTTP/1.1" 405 346 "-" "Microsoft-WebDAV-MiniRedir/5.1.2600"
またMicrosoftか。また、Microsoftか。
server-statusとaccess.logを付き合わせたところ、これが"K" Keepalive (read)のままのやつというのはほぼ疑いない。
Microsoft-WebDAV-MiniRedirでググると、栄えある検索結果第1位がコレ。
Windows XPからファイルサーバへの接続が非常に遅い
こいつだ。間違いない。
お前の相手などしていられない
ということで、UserAgent指定でサービス拒否を行う。
# MicrosoftのWebDAVエージェントからリクエストが来た場合、サービスを拒否 SetEnvIf User-Agent "^Microsoft-WebDAV-MiniRedir/5.1.2600" deny_agent <Location /> Order allow,deny Allow from all Deny from env=deny_agent </Location> # MicrosoftのWebDAVエージェントからリクエストについて、Keep-Aliveを許可しない BrowserMatch "^Microsoft-WebDAV-MiniRedir/5.1.2600" nokeepalive
完全にWindows XP決め打ちだが、もしVistaとか7とかから別のやつがやってきたら、そいつらもブッタKill。
ついでに自分のPCからもこのエージェントを切っておく。
[コントロールパネル]-[管理ツール]-[サービス]を開き、WebClientという名前の虫垂炎を無効にする。
perlスクリプトをドラッグ&ドロップで実行する
処理対象ファイルをバッチファイルにドラッグ&ドロップすると、そのファイルを引数にしてperlスクリプトが実行される。
例:WindowsのパフォーマンスモニタのログをCSVに変換する場合
処理対象ファイル(SystemLog20090122_000004.blg)をバッチファイル(feedme.bat)にドラッグ&ドロップすると、そのファイルを引数にしてperlスクリプト(myscript.pl)が実行され、CSVファイル(20090122.csv)が作成される。
file name: feedme.bat
rem "myscript.pl"を書き換えてご使用ください echo off cd /d %0\.. perl myscript.pl %1 pause
file name: myscript.pl
#パフォーマンスモニタのログファイルをCSV形式に変換する use strict; use warnings; my $input = $ARGV[0]; $input =~ /SystemLog([\d]+)/; my $output = $1 . '.csv'; print '$input: ' . $input . "\n"; print '$output: ' . $output . "\n"; system("relog $input -f csv -o $output\n"); __END__
MS-AccessのテーブルをMySQL(ODBC接続)にエクスポートする
テーブルを格納したファイル(database.mdb)とフォームを格納したファイル(form.mdb)があるときに、database.mdbのデータをMySQL(ODBC接続)にエクスポートした手順を記述します。
実験環境
Clientマシン
Serverマシン
- O/S:Windows XP Professional
- サーバソフト:XAMPP for Windows 1.6.2 (※2009/01/16現在、最新は1.7.0です)
- MySQL 5.0.41
- phpMyAdmin 2.10.1
CatalystでAjaxに挑戦してみた
おぞくてダサくてヘボいですが、メモ。
MyApp.pmの記述
use Catalyst qw/ConfigLoader Static::Simple FormValidator Authentication Authentication::Credential::Password Authentication::Store::DBIC Authorization::Roles Session::CGISession Prototype Email::Japanese/;
テンプレートの記述
[% c.prototype.define_javascript_functions %] <input type="text" name="regist_user_ID"><img src="[% static_root %]/common/img/check.gif" alt="確認" width="36" height="21"> [% c.prototype.observe_field('regist_user_ID', { url => '/get_account_info', with => "'search_key=name&id_code='+value", update => 'regist_user_name', }) %] <span id="regist_user_name"></span>
コントローラの記述
sub get_account_info : Global { my ( $self, $c ) = @_; my @applicants = MyApp::Model::CDBI::User_detail->search( 'id_code' => $c->req->param('id_code'), ); my $ret; foreach my $applicant (@applicants){ $ret = $applicant->get($c->req->param('search_key')); Encode::from_to($ret, "sjis", "utf8"); # 文字コード変換処理 ACCESS(Shift_JIS) -> Catalyst(UTF-8) } if($ret){ $c->res->output($ret); } else{ $c->res->output('該当がありません'); } }
参考になりましたハラショーリンク
- (ThinkIT) 第7回:Ajaxで登録フォーム (1/3)
- mizzy.org : Catalystでajax (HTML::Prototypeモジュールの使い方)
- Catalyst で作る簡単 Web アプリケーション: Feed2JS 解説 - antipop
- prototype.js を使ったインクリメンタル・オート Selecter クラス :: Drk7jp
- Catalyst::Plugin::Prototype - Plugin for Prototype - search.cpan.org
- HTML::Prototype - Generate HTML and Javascript for the Prototype library - search.cpan.org
- AJAX Select Chaining with Catalyst - Dev411: The Code Wiki
- mizzy.org : Catalystでajax - 実践編
- Rails' Wiki - AjaxOnRails
Catalyst::Plugin::Email::Japaneseでハマった
Catalyst::Plugin::Email::Japaneseを利用してメール送信する際に、Modelクラスから引いてきたShift-JISの文字列が文字化けしていた。
Template-Toolkitのカスタムフィルタで、Shift-JIS to UTF-8の変換をかけようと、下記サイトを参考に設定を試みたがうまくいかなかった。
へぼへぼCTO日記 - stash による検索結果 : 4件
(ThinkIT) 第5回:テンプレートの作成 (2/2) 設定はconfigオブジェクトにより、リスト15のようにアクセスできます。
Re: (Catalyst) Using a custom TT filter
Catalyst::View::TTでのフィルター作成時のメモ
Catalyst::View::TT::ForceUTF8に設定を握りつぶされる - holidays-l開発ブログ
クイック&ダーティなCatalystチュートリアル
use Template; - 今日のCPANモジュール
Template Toolkit Manual -テンプレートツールキット和訳マニュアル-
Template::Toolkit用独自フィルタを作る - ζ*’ワ’)ζ<うっうー遅レス。
Template Toolkit 使用時 に FILTER を追加する方法 - cooldaemonの備忘録
Template::Manual::Filters
で、結局いろいろトライ&エラーしてみた結果。
結論:
Catalyst::Plugin::Email::JapaneseはどうもMyApp::View::TTを使っていないワナ。
MyApp::View::TTとは別途FILTERSの設定が必要。
my %options = ( FILTERS => { 'sjis2utf8' => \&MyApp::View::TT::sjis2utf8, } ); # メール送信 $c->email( To => $To_field, # To => $c->stash->{'send_to'}, # Cc => $c->stash->{'send_cc'}, Subject => $title . '(#' . $c->req->args->[2] . ')', TmplOptions => \%options, );
CatalystアプリをWebサーバにのっけてみる(5) Catalyst on IIS編
Catalystで作ったアプリをIISでCGI動作で動かしてみたら、動作が変。htmlの出力が途中で途切れる。
HTTPヘッダを見てみる(ieHTTPheadersで確認)とどうもContent-Lengthが少ない。というか、途切れている部分までのバイト数と一致している。で、いろいろ調べまわった結果、たぶん、結論はこれ、たぶん。
IISがあほうな改行コード変換をしているせいで、CatalystがContent-Lengthを計算した後に、実態のデータ量が増えている。
ばかー、ふぁっきん、さのばびーっち!
で、しょうがないのでCatalyst側に対策しました。
site/lib/Catalyst.pmのContent-Length計算部分に、$c->response->bodyの改行文字数を足すように変更。↓これ。
# Content-Length if ( $c->response->body && !$c->response->content_length ) { # get the length from a filehandle if ( blessed( $c->response->body ) && $c->response->body->can('read') ) { if ( my $stat = stat $c->response->body ) { $c->response->content_length( $stat->size ); } else { $c->log->warn('Serving filehandle without a content-length'); } } else { # Hack to fix fxxk'n IIS return code issue. my $n; my $fh = $c->response->body; $n = $fh =~ s/\n/\n/g; $c->response->content_length( bytes::length( $c->response->body ) + $n ); } }
参考になりました:
◆2.9 小さな親切大きなお世話?! - CGIの要点整理
CGIがデータを送信するときは,すでに,改行コードが [CRLF] になっているデータを送信すると,"CRCRLF"に変換して送信してしまいます.これにより, CGIが Content-Length で指定したサイズよりデータサイズが大きくなってしまいます.
◆CGIでHTTPヘッダー「Content-Length」を出力するには?
Windows 環境で、出力する HTML が50行程度ならば、改行コードでしょう。
試してないけど length($data) + $data=~s/\n/\n/g とするか、binmode(STDOUT) かな。
CatalystアプリをWebサーバにのっけてみる(4) Catalyst on IIS編
Catalystで作ったアプリをIIS上でCGI動作で動かしてみたら、動作が変。
例えば、sub move : Path('move')というアクションを作ったときに、http://domain/MyApp_cgi.pl/move にアクセスすると、なぜかdefaultに飛ばされる。
IIS上での動作とApacheサーバ上での動作を比較したところ、IISが環境変数PATH_INFOをスクリプトパスを含んだ形で、CGIスクリプトに渡してくるため、期待する動作になっていないようだ。
このIISのPATH_INFOの問題は、結構いろいろなところ*1で地雷になってるらしく、スクリプト側で対処している方が多いので、それに習ってCatalyst::Request.pmで、以下の行(# <- modifiedのある行)を変更。
=head2 $req->path Returns the path, i.e. the part of the URI after $req->base, for the current request. =head2 $req->path_info Alias for path, added for compability with L<CGI>. =cut sub path { my ( $self, $params ) = @_; if ($params) { $self->uri->path($params); } else { return $self->{path} if $self->{path}; } my $path = $self->uri->path; my $location = $self->base->path; $path =~ s/^(\Q$location\E)?//; # hack to fix broken path info in IIS # <- modified if ($path =~ /$ENV{'SCRIPT_NAME'}/){ # <- modified $path =~ s/$ENV{'SCRIPT_NAME'}\///; # <- modified } # <- modified else{ # <- modified $path =~ s/^\///; # <- modified } # <- modified $self->{path} = $path; return $path; }
◆BBS-サポート掲示板/686 - FreeStyleWiki
◆6.1.6. PATH_INFO (経路情報) (CGI/1.1 draft 03)
メモ欄に「IIS は正しい PATH_INFO をくれないことがあるそうです。」
◆blosxomサイトの日本語訳::FAQ - IISのPATH_INFOの問題にも関わらずBlsoxomを動作させるにはどうすれば良いのですか?
◆CGI アプリケーションの PATH_INFO および PATH_TRANSLATED を使用する
Micro$oftの見解「これは、セキュリティを目的とした仕様」「CGI 仕様に明記されたとおりに PATH_INFO および PATH_TRANSLATED を使用する必要がある場合は、〜〜〜」
MS独自仕様ですか、そうですか。繰り返すこれはバグではない、繰り返すこれはバグではない。
*1:いろいろなところの詳細