2009年6 月3日  |  Written by matsumoto  |  under Perl Yahoo!ブックマークに登録    はてなブックマーク - [perl] ありんく的モダンPerl

牧大輔 さんの名著 モダンPerl入門 を熟読している最近なのですが、熱いです。実務に直結したノウハウが満載です。

弊社もPerlをメインに開発を行っています。開発の過程で色々と方向性などのキメを繰り返してきているのですが、大体同じ方向になるよう、緩やかな決まり事を作っています。
「モダン」という言葉にちょっとあやかってみて、弊社内の決まり事をまとめて「ありんく的モダンPerl」を挙げてみます。

といっても、MooseやCatalystゴリゴリでという方向ではないです。昔からある手法やモジュールを使っててもよいですし、最新のモジュール利用もしています。

ポリシー

ざっくりとしたポリシーは、おおよそ以下でしょうか。

  • プログラムが高速に動作する
  • ハードウェアやOSに依存しない
  • 後からも読みやすい。可読性が高い
  • 頻繁に仕様変更しているモジュールに極度に依存しない
  • 1つのサブルーチンの中は15ステップまで

Perlに限った話でなく、どの言語でも言える事ですね。永遠のテーマかも。

スタイル

ウェブアプリケーションの開発以外にも、統計データの解析や資料用データの作成の業務などもあるのですが、ほとんどをPerlでコーディングします。
使い捨てスクリプトから大規模アプリケーションまでベースとするスタイルは共通化させています。

  • 強制的な指定がない限り、文字コードはUTF8で書く
  • Perlのバージョンは5.8.8
  • shebang は #!/usr/local/bin/perl
    • /usr/bin/perl にPerlが存在する場合は、シンボリックリンクを張る
  • 以下3つの宣言は必須
    use utf8;
    use strict;
    use warnings;
  • インデントは"タブ"ではなく"半角スペースx4"
  • 全てのスクリプトはsubversionなどのバージョン管理システムに登録する

use utf8 させていますので、スクリプト自体にutf8フラグが立った状態になります。

利用するモジュールなど

  • 文字コード変換にはEncodeを利用する

    • Jcode.pm、jcode.plは使わない
  • 日時計算まわりはDateTimeモジュールを利用する
    • timelocal、Time::Piece、Posixは使わない。
  • Class::Accessor::Fast、Cache::Memcached::Fast など、シンプルでオーバーヘッドが小さいモジュールを利用する
  • Cache接続用のinstanceを作成する場合は、必ずsingletonにする
  • XSがある場合はXS対応のモジュールを使う
    • Text::CSV_XS、JSON::XS URI::Escape::XS XML::LibXMLなど

      • ※XSをインストールできない場合はそれぞれのPurePerl版を利用する

Webアプリ用フレームワーク

  • Sledge を利用する

    • ※UTF8に対応する必要があります

O/Rマッパー

  • DBIx::MoCo を利用する

    • まだ仕様変更が多いため DBIx::Classは使っていない
    • ※昔のプロジェクトではClass::DBIを未だに利用しています。
  • PagerはPagesetを利用する
  • DBとのやり取りは全てDBIを経由して行い、SQLもDBに依存した書き方をしない。
    • なるべくMySQL、PostgreSQLで同じに動作するようにする。(SQLiteも含みますが・・・)

テンプレート

まとめ

非常にざっくり過ぎるのですが、おおよそこの形でコーディングを行っています。
大してモダンなスタイルでもないのですが、この形であれば極端に遅くなることもないかと思います。

利用しているOSがCentOSとFreeBSDですので、WindowsのActivePerlは考慮していません。

画像処理周りなど、思いっきり落としていますね・・・・
多分他にも書き忘れている部分が多々あるので、追記していきたいと思います。

2009年6 月14日  |  Written by matsumoto  |  under Perl Yahoo!ブックマークに登録    はてなブックマーク - [perl] SledgeのCookieベースのSessionを永続的に持たせる

SledgeでSessionをCookieで制御する場合
デフォルトだと、Cookieの有効期限は

ブラウザを閉じたときまで保持

だと思います。

Formに持たせる情報など、一時的なものであれば全く構わないのですが、
ログイン情報など、半永続的に持たせたい情報の場合はCookieの有効期限を調整できた方が何かと便利です。

ということで、ブラウザを閉じてもSessionを維持できるようにCookieを少し変更しました。

変更点

Sledge::SessionManager::Cookieで、Cookieの有効期限をコントロールできるように変更しました。

$options{'-expires'} = $config->cookie_expires if $config->cookie_expires;

以下の場所に追記しています。

.........
$options{'-domain'} = $config->cookie_domain if $config->cookie_domain;
++ $options{'-expires'} = $config->cookie_expires if $config->cookie_expires;

my $cookie = CGI::Cookie->new(%options);
.........

これにより、Config内で

$C{COOKIE_EXPIRES} = '+100d';

のように設定しておけば、SessionのCookieの有効期限を自由に設定できます。

※MemcachedをSessionに設定している場合、Memcached自体のExpireを
Cookieと同じか、それ以上に設定しておく必要があります。

patch

一応ですが、 diff を取ってPatch化してみました。

*** Cookie.pm   Sun Jun 14 23:25:00 2009
— Cookie.pm   Sun Jun 14 23:27:50 2009
***************
*** 21,31 ****
      my($self, $page, $sid) = @_;
      my $config = $page->create_config;
      my %options = (
!   -name   => $config->cookie_name,
          -value  => $sid,
          -path   => $config->cookie_path,
      );
      $options{'-domain'} = $config->cookie_domain if $config->cookie_domain;

      my $cookie = CGI::Cookie->new(%options);
      $page->r->header_out('Set-Cookie' => $cookie->as_string);
— 21,32 —-
      my($self, $page, $sid) = @_;
      my $config = $page->create_config;
      my %options = (
!         -name   => $config->cookie_name,
          -value  => $sid,
          -path   => $config->cookie_path,
      );
      $options{'-domain'} = $config->cookie_domain if $config->cookie_domain;
+     $options{'-expires'} = $config->cookie_expires if $config->cookie_expires;

      my $cookie = CGI::Cookie->new(%options);
      $page->r->header_out('Set-Cookie' => $cookie->as_string);
 

2009年6 月19日  |  Written by matsumoto  |  under Apache, Perl Yahoo!ブックマークに登録    はてなブックマーク - [perl] SledgeのApache2(mod_perl2)対応+UTF8

Sledgeというperl用のフレームワーク(MVC)があります。

このMVCは主に Apache1.3+mod_perlで運用されることが多いのですが、
今回 Apache2(mod_perl2)に対応させました。且つUTF8にも対応させます。

mod_perlはApache/Apache2のAPIの仕様が全くと言っていいほど互換性が無いため、
そのAPI周りの記述方法を変えた、別パッケージ群を作成しています。

Sledge自体はCompatを通さず、mod_perl2のみを利用し、UTF8コードを前提としています。

既存部分からの変更点

既存に存在するコードをミックスさせて作成しました。

SledgeのUTF8化について

Apache2の対応について

Dispatcherについて

利用法・ソースコードなどはSledgeのApache2(mod_perl2)対応+UTF8ありんく tech-wiki) をご覧下さい。

mod_perl2などのインストールについて

Apache1、2について (ありんく tech-wiki) をご覧下さい。

※CentOSを利用しています。できるだけyum(rpm)扱いにするようにしています。

mod_perl2の参考ドキュメント

参考リンク

2009年6 月20日  |  Written by matsumoto  |  under Perl, サーバー Yahoo!ブックマークに登録    はてなブックマーク - [perl] Apache2+mod_perl2 のメモリ消費量を調べる

mod_perl2+Sledgeでテストを繰り返しているのですが、各プロセスでのメモリ消費量が激しく気になります。
特に多くのperlモジュールをロードする場合、プロセスサイズが肥大化するので随時チェックしています。

mod_perlのメモリ消費状況を知る

今回は INCDiffを使って、startup.plを精査しつつ mod_perl2を抱えたhttpdのメモリ消費量をチェックしました。

httpd.confの設定

httpd.confはpreforkモードで使っていてプロセスの塩梅は以下です

   StartServers      20
   MinSpareServers    20
   MaxSpareServers   50
   ServerLimit      1000
   MaxClients       1000
   # MaxRequestsPerChild  100
   MaxRequestsPerChild  1000

MaxRequestPerCHildがやや多めなのですが、mod_perl2 のメモリ消費量検証用ということで多くしています。
コメントアウトしていますが、常用では100を設定しています。

httpdが「起動」している状態の メモリ使用状況

# vmstat
procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu------
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 0  0      0 4672652 373464 1781576    0    0     0     1    2    2  0  0 100  0  0

httpdを停止します。

# service httpd stop
httpd を停止中:                                            [  OK  ]

httpdが「停止」している状態の メモリ使用状況

# vmstat
procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu------
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 1  0      0 5610844 373464 1781332    0    0     0     1    2    2  0  0 100  0  0

httpdが抱えていたメモリを逆算します。

freeが 4672652 から 5610844 に増えていますので、httpdが抱えていたメモリは

938192 (約916.2MB)

この916.2MBをどう使っているのか。

重要な点は共有メモリか、各プロセスが独自にそれぞれメモリを消費しているかで、効率の度合いが変わってくる点です。
共有メモリの利用率が高ければ 効率的にメモリを使えますので、起動プロセス数を増加できます。

共有メモリか、プライベートなメモリかを測定するperlスクリプトを利用して計測します。

このプログラムを利用して、メモリを計測すると以下の結果が出ました。

# shared_memory_size.pl `pgrep httpd`

PID     RSS     SHARED
4719    57988   46392 (80%)
4721    69024   46260 (67%)
4722    67740   45948 (67%)
4723    69256   45276 (65%)
4724    68772   45948 (66%)
4725    68360   45884 (67%)
4726    68276   46188 (67%)
4728    68972   46260 (67%)
4729    69924   45944 (65%)
4730    69564   45884 (65%)
4731    69728   46260 (66%)
4732    68712   45948 (66%)
4733    68748   46260 (67%)
4734    68792   46256 (67%)
4735    70572   45948 (65%)
4737    69092   46188 (66%)
4738    68412   45948 (67%)
4739    69696   46184 (66%)
4740    69004   46256 (67%)
4744    67352   46188 (68%)
4746    67116   45948 (68%)
4751    66608   45948 (68%)
4752    66892   45948 (68%)
4755    65280   46260 (70%)
4782    64092   46260 (72%)
4785    63876   46260 (72%)
4786    63720   46260 (72%)
4788    62784   46260 (73%)
4790    61736   46260 (74%)
4791    63404   46260 (72%)
4794    61432   46260 (75%)
4797    60604   46188 (76%)
4799    60648   46260 (76%)
4804    58880   46288 (78%)
4808    60236   45900 (76%)
4815    58368   46228 (79%)
4816    58940   46228 (78%)
4829    57608   46228 (80%)
4842    55128   46228 (83%)
4843    55124   46224 (83%)
4844    55128   46228 (83%)
4845    55128   46228 (83%)
4848    55124   46224 (83%)
4849    55128   46228 (83%)
4850    55128   46228 (83%)
4851    54780   46156 (84%)
4855    54696   46156 (84%)
4856    54608   46232 (84%)
4857    54604   46232 (84%)
4858    54604   46232 (84%)
4859    54604   46232 (84%)

それぞれ、「プロセスのID」、「各プロセスの全体メモリ」、「その中の共有メモリ」、「プロセス内の共有メモリの割合(%)」になります。

内訳は

  • 共有メモリ

    • 85.6MB
  • プライベートメモリ
    • 830.5MB

になります。

httpdの起動数が最大50ですのえ、これを細かく計算すると (830.5 / 50) 1プロセス辺り、平均で

16.61MB

のプライベートメモリを利用している計算になります。

4GBのメモリを搭載しているサーバで、webサーバ専用であれば、このプロセスサイズのhttpdを扱う場合

  • OS、その他のメモリ 1GB
  • httpd用のメモリ 3GB

と仮定し、

3GB = 共有メモリ(約100MB) +  ( プライベートメモリ(@16.61MB) x  174 )

になるので、最大プロセス数を174までは理論上は起動できることになります。

※174という数字は参考値として捉えています。この数値ですと、全く余裕がない設定なので、実際はプロセス数を50前後から少しづつ増やしていっています。

まとめ&mod_perlはメモリを食います

mod_perlの設定では、perlモジュールのロードによるメモリ消費量が一番クリティカルだと思います。
このコントロールを意識しないと、どんなに良い性能のサーバでもスワップが起こってしまいます。

対象方法としては、以下でしょうか。

なるべく共有メモリを使う

  • startup.plでのモジュール読み込みで共有メモリになるべく多くのモジュールをロードさせます

プライベートメモリの肥大化を防ぐ

MaxRequestPerChildの方は、防ぐというよりも増加したメモリを強制的にkillしています。

  • (当たり前ですが) strict を宣言して、グローバルで変数を使わないようにします

    • 全ての pl/pm ファイルで use strict するようにしています。 no strict もよっぽどの例外でない限り禁止しています。

      package App::Pages::Some;
      use strict;
      .
      .
      .
  • Config、Cacheコネクションなどの毎回同じものはInstance化する
  • MaxRequestsPerChild を少なめに設定し、リクエスト処理数の増加とプロセスサイズ増加にLimitをかけます

    • 通常は最大リクエスト数を100程度で設定してから調整を行っています。

      MaxRequestsPerChild  100

プロセス数の増加を防ぐ

プロセスの増加自体がメモリを消費するベースになります。無駄な増加は増やさないようにします。

  • KeepAlive をOFFにする

    • Apacheをフロントとバックエンドの2つに分けて バックエンド側でmod_perlだけのリクエストを処理します。
      この際にバックエンド側では

      KeepAlive off

      を設定し、フロントからのコネクションを無駄に占有させることが無いようにしています。

但しAPIを多用するようなサイトの場合、KeepAlive On での設定の方が効率的な場合があります。

KeepAlive On
MaxKeepAliveRequests 10
KeepAliveTimeout 2

この場合の「MaxKeepAliveRequests」は、ブラウザで1ページを表示させる際に必要とする mod_perlへのリクエスト数を
割り当てます。

また、KeepAliveTimeoutを短く設定することで、フロントサーバからのコネクションを無駄に発生させないようにしています。

参考リンク

2009年6 月21日  |  Written by matsumoto  |  under Perl Yahoo!ブックマークに登録    はてなブックマーク - [perl] mod_perlとプロセスサイズ

mod_perlとプロセスサイズについて検証してみました。

前提

MVC(Sledge)を使って検証

TT、CGI、DateTimeなどの大きいモジュールは startup.pl でloadしています。

各Appsも File::Findで use しています。

DBをDBIx::MoCoで読み込んで、100件をTTで表示という処理を行います。

サーバーは Q6600 メモリ8G

検証結果

mod_perl1 と mod_perl2(prefork)ではスピード、メモリ消費量共にほぼ同じ。

ORマッパ(DBIx::MoCo)を抱えると大きくなる。多分CDBIとかDBICとかだと更に大きくなる。
単純な処理でも大体1プロセスサイズが50Mになる(共有メモリ含む)

各プロセスが抱えるhttpdの共有メモリはおおよそ2/3。
起動時は90%以上 → だんだんプライベートメモリが増えていきます。アプリによっては50%を切る場合もあります。

かりっかりにstartup.plで抱え込むことができれば 共有メモリ70%ぐらいで推移していきます

同じURLに対して局所的に集中するようなアクセス(F5連打など)には強いと思います。

abによるベンチでは、以下になった。それぞれでプロセス単位でのメモリの増減はあまり無いです。

DBへのアクセス無し
250 req / sec
DBへのアクセスあり(Memcached)
50 req / sec
DBへのアクセスあり
10 req / sec

結論

WebシステムはMemcachedありきが良いです。

preforkモデルでは mod_perl1 mod_perl2 共に同じでした。
趣味でどちらでもという感じです。

  • mod_extract_forwordedのバージョン違いに注意。

mod_perlはメモリを食います、メモリを多めに搭載できるサーバでないと厳しいと思います。
メモリ搭載量にもよるけど、MaxRequestPerChildは大体 100以下が現実的。

チューニング具合にもよるけど、大よそ総メモリ2G以下のシステムではSwapを起こすのでは。

Apache::Registry(ModPerl::Registory)を使っているシステムだと FastCGI、SpeedyCGIも検討の価値があると思います。

そもそもなのだけど、プログラムをメモリ上に確保するということは

システム全体のコード量が大きくなっていけばいくほど、
1プロセス辺りのメモリ消費量が増していくことになるので

「細かく異なる複数のperlモジュールが大量使われる」というシステムは
ロード&コンパイルするperlモジュールが多くなり、プロセスサイズが大きくなります。
→こういう使い方には向いてないんじゃないだろうかと・・・

シンプルなページを高速に処理するのであればとても向いているソリューションだと思います。

※Mixiは実現できているじゃないかというアレもあるのですが、どうやっているのかなあ。
URL(機能)ごとにmod_perlのサーバを分けたりしているのかな、L7バランサみたいな。
もしくは 「.pl」で終わっているURLに単純にハンドラがリライトしているだけだったりして。

ということでPHP(mod_php)も検証してみたいと思います。

私はチーム・マイナス6%です