進捗管理ってどうしたらいいんだろうか
数年前から、PJリーダーとして仕事をすることが多くなり、「進捗管理」で悩むことが多かったのですが、以下の記事を読んで、少し光明が見えた気がしたので、忘れないように書いておこうと思います。
SEの残業しない仕事術 : モニタリング&コントロール(1)
SEの残業しない仕事術 : モニタリング&コントロール(2)
SEの残業しない仕事術 : モニタリング&コントロール(3)
基本的な考え方
基本的な考え方としては、CMMIがベースになっています。CMMIには「進捗管理」という項目はなく、進捗管理に相当するものとして、「Project Monitoring and Control(PMC)」という定義があるようです。
- ちゃんと状況を見て(=モニタリング)
- 状況に応じて対処しなさい(=コントロール)
定期的に、メンバーの進捗を確認するのではなく、リアルタイムに状況を把握し、問題が発生した時点で対策を打てるように心掛けることが大事ということだと感じました。
モニタリング
状況を把握することが大事というのは、誰もが分かっていることだと思います。では何を把握すればいいのでしょう?上記の記事では、以下の項目が挙げられています。これらの項目をWBSに追加した上で日々集計し、その状況を観察し続けます。
※この項目は、有名な「Joel on Software」を参考にしたそうです。
コントロール
リアルタイムに状況を把握(観察)し続けるだけでは意味がありません。そのPJがスケジュール的にヤバイかどうかをどう判断すればいいでしょうか?その判断ポイントとなるのが、「残工数 = 残り時間の合計」と「残稼動時間 =(納期までの残り日数)×(1日の稼動時間)」です。
残工数/残稼動時間
このように、この2つの値を比較することにより、「自分たちに残された時間(残稼動時間)に対して、やらなければならない残タスクのボリューム(残工数)が溢れていないか」をチェックすることができるようになります。
これは、普段自分たちが行っている進捗管理とはかなり観点が異なると思いました。普段は、「何が終わったか?」という報告をさせることが多いですが、よく考えれば、「何が残っているか?」「エンドまでにそのタスクをやり切れるのか」という視点に切り替える必要がありますね。
まとめ
もちろん、単純に上記の比率だけでは判断はできないかもしれません。各タスクには因果関係があるので、PJ全体の工数計算上は辻褄が合っていても、ボトルネックのタスクに絡んだものが多いと、結局納期に間に合わないということが考えられるからです。
ですが、重要なことは、リーダーがそのPJに対して明確な判断基準を持つ必要があり、そのために必要な数字を把握するために、日々観察を続けなければならないということだと思いました。
最後に、
以下の引用にもある通り、プレイヤー⇒リーダーになる段階での育成について、もっと組織的な取り組みが必要だと思いました。リーダー研修に参加させるだけでは、身につかないでしょう。OJT的な取り組みを取り入れる方向がいいのかもしれません。
ほとんどの人は、進捗管理の仕方について、トレーニングをうけないまま、リーダーとなっていきます。だいたい、きちんと進捗管理された覚えもない人も、多いのです。わからないのも当然です。
SEの残業しない仕事術 : モニタリング&コントロール(1)
MySQLのクエリキャッシュについて
今まで、MySQLのクエリキャッシュを利用することに何の懸念も持っていなかったのだけど、以下のような意見があり、今後は少し気をつけようと思っています。
確かにヒット率という観点でみると、エンドユーザ向けのWebサービスでは、意味が無さそう。
今までMySQLのクエリキャッシュはは有効にしてたんですが、Webサービスだとキャッシュヒットするようなクエリはそんなに多くないし、どこかで見かけたんですが(失念…)クエリキャッシュをオフにしたら(逆に)パフォーマンスが上がっただか負荷が下がっただかというのも目にしたので、今度クエリキャッシュはオフにしようと思ってました。(どのみちヒット率悪いし)
MySQLで、指定したときだけクエリキャッシュする - (ひ)メモ
マルチコア環境という視点でも見てみると、DBサーバにある程度のCPU数があるならば、変にクエリキャッシュすると、マルチコア環境を活かせない可能性があるということですかね。
クエリキャッシュはCPUスケーラビリティが悪いので、8コアぐらいのマシンでInnoDB Pluginを入れて単純なクエリをゴリゴリ回すとクエリキャッシュなしの方が速い場合があります
はてなブックマーク - sh2のブックマーク / 2010年4月6日
設定方法
my.cnfの「query_cache_type」パラメータで設定するようです。「DEMAND」で設定しておくのがいいのかもしれません。
FTP用ユーザの作成時の考慮点
FTP用のユーザを作成する時に考慮すべきポイントは、以下の2つです。
シェルログインを不可に設定
この設定は、セキュリティの観点で必要な設定です。
FTP用ユーザにまで、ログインさせる権限を与える必要はありません。従って、そのようなユーザの作成時には、以下のコマンドで設定します。
[root]# useradd -s /sbin/nologin*1 testuser
homeディレクトリを変更
FTPで受信したファイルの冗長化をどう実現するかについては、いくつか方法があります。
例えば、HA構成にして、DRBDを用いてミラーリングする方法がありますが、DRBDではある特定のディレクトリ配下をミラーリング対象とします。従って、FTPで利用されるディレクトリは、DRBD領域に変更する必要があります。
そのような場合に、以下のコマンドで、FTP用ユーザのhomeディレクトリを変更します。
[root]# usermod -d /var/ftp/data testuser ※/var/ftp/data以下がDRBD領域だと仮定
*1:/sbin/nologinは、シェルログインを禁止できるプログラムで、アカウントは必要だがログインはさせたくない、というユーザーのデフォルトシェルとして設定すると、シェルログインを禁止できます。
PgpoolでのPreparedStatementのDEALLOCATE問題
Pgpool(ver 2.3.3)を使っていて、たまにDEALLOCATE*1に関するエラーが発生するので調査していました。
状況
- Pgpoolログに、「DEALLOCATE pdo_stmt_00000001 message: prepared statement "pdo_stmt_00000001" does not exist」とエラー出力される
- PostgreSQLログに、「ERROR: prepared statement "pdo_stmt_00000001" does not exist」とエラー出力される
- このエラーが検知されるのは、全てスレーブノード
原因
以下、引用にもある通り、Pgpoolを「マスター/スレーブモード」で利用している場合、PreparedStatementがマスターノードで作成されたにも関わらず、DEALLOCATE処理をスレーブにも実行していることが原因だろうということです。
Some notices: First we issue a prepared SELECT NEXTVAL in a transaction.
http://pgfoundry.org/pipermail/pgpool-hackers/2010-May/000311.html
This is only send to the master. Later we try to deallocate the prepared
statement. This works on the master, but fails on the slaves. Seems to
be a logic flaw.
少し詳しく書くと、以下のようになります。
- 更新系SQL、または、トランザクション中のSELECTは、(Pgpoolの機能によって)マスタノードで実行される。
- そのSQLがPreparedStatementを用いている場合、PreparedStatementもマスタノードで作成される。
- にも関わらず、DEALLOCATE処理は、マスタ、そしてスレーブにも実行されてしまう。
- そのため、スレーブ側で存在しないPreparedStatement(SQLの雛形)の削除(メモリの解放)に失敗する。
下記の通りこの件は、ver2.3.2で修正されている*2ようなのですが、2.3.3では発生してしまいます。。
2.3.2 (tomiteboshi) 2010/02/07
http://pgpool.projects.postgresql.org/pgpool-II/doc/pgpool-ja.html
バグ修正
master/slaveモードの際に、DEALLOCATEをすべてのノードに送らないようにして、kind mismatchエラーを防ぐようにしました(Tatsuo)
影響
このエラーについて、有識者に確認したところ、
- PDOなどの接続ライブラリが、名前付きのPreparedStatementを作成して、それを明示的に DEALLOCATE するタイプの場合に発生する。
- このエラーが発生したからと言って、データ不整合が発生しているというわけではない。
- また、このエラーによって、データ不整合が発生してしまうこともない。
- この不具合は、Pgpool3.0 で修正されている。
対策
- (できれば使いたいが)PreparedStatementを使用しない
- (実害は無いようなので)サーバ監視でこのエラーを無視
- Pgpool3.0以上にバージョンアップ(または2.3.2にダウン)
Apacheで圧縮してコンテンツ配信しよう
最近、スマートフォン向けのサイトなどが多くなりつつありますが、スマートフォンの場合、「(携帯キャリアの3G回線などの)細い回線」かつ「(JavaScriptなどを含んだ)リッチコンテンツ」という事情から、コンテンツのダウンロードが重く感じるということがあります。
その対策として、Webサイトの高速化 ルール4 コンポーネントを圧縮しよう! にある通り、JavaScript、CSSの外部ファイルを圧縮して送信することで、パフォーマンスを向上しようという話です。
但し、導入時には、運用しているサーバのCPUリソース状況を確認する必要があります。なぜなら、圧縮する度にCPU消費する事になり、結果CPU使用率は上がるからです。CPU使用率は余裕で、かつ、ネットワーク帯域は節約したい場合にはいい手段だと思います。
以降で、Apacheへの圧縮用モジュール追加、設定内容、動作確認の順で説明していきます。
Apacheへのモジュール追加
mod_so の確認
まず、モジュール追加するには、「mod_so」というモジュールが必須ですので、その存在確認を行います。*1
[root]# /usr/local/apache2/bin/httpd -l ・ ・ mod_alias.c mod_so.c
追加モジュールのコンパイル
今回利用するモジュールは、以下の2つです。
- mod_deflate.so(※要モジュール追加)
- コンテンツを圧縮するモジュール
- mod_filter.so(※Apache本体に組み込まれているはずなので追加不要)
- コンテンツタイプによって、圧縮対象を指定するためのモジュール
apxs を使って、mod_deflate.so をコンパイルします。「-c」オプションはコンパイル、「-i」オプションはインストール(/usr/local/apache/modules に移動などしてくれる)という意味です。
参考にしたサイトでは、「-a」オプションがつけられていることが多かったのですが、個人的には勝手に、httpd.confにLoadModuleディレクティブを追加されたりするのは嫌なのでつけていません。
[root]# /usr/local/apache2/bin/apxs -c -i /root/modules/httpd-2.2.11/modules/filters/mod_deflate.c /usr/local/apache2/build/libtool --silent --mode=compile gcc -prefer-pic -DLINUX=2 -D_REENTRANT -D_GNU_SOURCE -D_LARGEFILE64_SOURCE -g -O2 -pthread -I/usr/local/apache2/include -I/usr/local/apache2/include -I/usr/local/apache2/include -c -o /root/modules/httpd-2.2.11/modules/filters/mod_deflate.lo /root/modules/httpd-2.2.11/modules/filters/mod_deflate.c && touch /root/modules/httpd-2.2.11/modules/filters/mod_deflate.slo /usr/local/apache2/build/libtool --silent --mode=link gcc -o /root/modules/httpd-2.2.11/modules/filters/mod_deflate.la -rpath /usr/local/apache2/modules -module -avoid-version /root/modules/httpd-2.2.11/modules/filters/mod_deflate.lo /usr/local/apache2/build/instdso.sh SH_LIBTOOL='/usr/local/apache2/build/libtool' /root/modules/httpd-2.2.11/modules/filters/mod_deflate.la /usr/local/apache2/modules /usr/local/apache2/build/libtool --mode=install cp /root/modules/httpd-2.2.11/modules/filters/mod_deflate.la /usr/local/apache2/modules/ cp /root/modules/httpd-2.2.11/modules/filters/.libs/mod_deflate.so /usr/local/apache2/modules/mod_deflate.so cp /root/modules/httpd-2.2.11/modules/filters/.libs/mod_deflate.lai /usr/local/apache2/modules/mod_deflate.la cp /root/modules/httpd-2.2.11/modules/filters/.libs/mod_deflate.a /usr/local/apache2/modules/mod_deflate.a chmod 644 /usr/local/apache2/modules/mod_deflate.a ranlib /usr/local/apache2/modules/mod_deflate.a PATH="$PATH:/sbin" ldconfig -n /usr/local/apache2/modules
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
mod_deflate.so ができたかどうか確認してみます。
[root]# pwd /usr/local/apache2/modules [root]# ls -ltr -rwxr-xr-x 1 root root 129705 5月 14 2009 mod_rewrite.so -rwxr-xr-x 1 root root 42292 5月 14 2009 mod_headers.so -rwxr-xr-x 1 root root 30367 5月 14 2009 mod_expires.so -rwxr-xr-x 1 root root 13191193 5月 14 2009 libphp5.so -rwxr-xr-x 1 root root 48770 11月 12 12:44 mod_deflate.so
Apache設定変更
モジュールのコンパイルが完了したので、httpd.confを修正します。設定の際に配慮するのは、以下の4点です。
- 追加モジュールのロード
- 圧縮コンテンツ非対応ブラウザ(古いブラウザ)への配慮
- 画像コンテンツは圧縮しない(元々圧縮されているので)
- 対象コンテンツのタイプを指定して圧縮を行う
[root]# vi /usr/local/apache2/conf/httpd.conf ##### 追加モジュールのロード ##### # mod_filterは「BASE」モジュールなので含まれているはずです。 # LoadModule filter_module modules/mod_filter.so*2 LoadModule deflate_module modules/mod_deflate.so ##### 圧縮コンテンツ非対応ブラウザ(古いブラウザ)への配慮 ##### BrowserMatch ^Mozilla/4 gzip-only-text/html BrowserMatch ^Mozilla/4\.0[678] no-gzip BrowserMatch \bMSIE !no-gzip !gzip-only-text/html ##### 画像コンテンツは圧縮しない(CPUを節約) ##### SetEnvIfNoCase Request_URI\.(?:gif|jpe?g|png)$ no-gzip dont-vary ##### 対象コンテンツのタイプを指定して圧縮 ##### FilterDeclare Compression CONTENT_SET FilterProvider Compression DEFLATE Content-Type $text/css FilterProvider Compression DEFLATE Content-Type $application/javascript FilterChain Compression
[設定内容の補足]
- FilterDeclareディレクティブ
- Compressionという名前のフィルター群のタイプがCONTENT_SETであると宣言しています。
- FilterProviderディレクティブ
修正後、Apacheプロセスを再起動します。
[root@sidgen01 conf]# /etc/init.d/httpd restart apache restart use /usr/local/apache2/conf/httpd.conf... OK.
動作確認
FireFoxの「Live HTTP headers」で確認できます。確認の観点は以下の3点です。
以下が「Live HTTP headers」での確認例です。
http://example.com/prototype.js GET /prototype.js HTTP/1.1 Host: example.com User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; ja; rv:1.9.2.10) Gecko/20100914 Firefox/3.6.10 Accept: */* Accept-Language: ja,en-us;q=0.7,en;q=0.3 Accept-Encoding: gzip,deflate Accept-Charset: Shift_JIS,utf-8;q=0.7,*;q=0.7 Keep-Alive: 115 Connection: keep-alive HTTP/1.0 200 OK Date: Fri, 12 Nov 2010 05:37:27 GMT Last-Modified: Wed, 24 Oct 2007 01:33:59 GMT Etag: "41da34-17837-43d331c950fc0"-gzip Accept-Ranges: bytes x-ua-compatible: IE=7 Cache-Control: no-cache,max-age=0 Vary: Accept-Encoding Content-Encoding: gzip Content-Length: 22037 Content-Type: application/javascript Connection: close
または、Apacheのアクセスログでも確認可能です。例えば、「prototype.js」で絞り込んだ上で、ファイルサイズの部分を確認するといいでしょう。設定変更前よりも、ファイルサイズが小さくなっていると思います。
[root]# grep prototype.js accesslog.20101112 ・ ・ 192.168.1.1 - - [12/Nov/2010:16:58:29 +0900] "GET /prototype.js HTTP/1.0" 200 96311 192.168.1.1 - - [12/Nov/2010:16:58:31 +0900] "GET /prototype.js HTTP/1.0" 200 96311 ---------- 設定変更を実施 ---------- 192.168.1.1 - - [12/Nov/2010:17:00:57 +0900] "GET /prototype.js HTTP/1.0" 200 22037 192.168.1.1 - - [12/Nov/2010:17:00:57 +0900] "GET /prototype.js HTTP/1.0" 200 22037
負荷検証
今回の対応によるサーバ負荷状況について、以下の観点で確認しておいたほうがいいと思います。
- CPU消費
- Apacheの各プロセスのメモリ消費
方法としては、Apacheのabコマンドを使って、負荷をかけます。
[root]# /usr/local/apache2/bin/ab -n 1000 -c 50 -H 'Accept-Encoding: gzip,deflate' 'http://localhost/prototype.js'
そして、負荷をかけている状態で、topコマンドを使って、以下の3つの値を確認します。
参考URL:httpd.confによるWebサーバの最適化 (3/3):実用 Apache 2.0運用・管理術(3) - @IT
環境によって異なりますが、おそらく、この対応で極端に負荷が上昇することはないと思います。手元の環境では、CPU使用率が元の2割程度(4X%⇒5X%)上昇していました。
但し、サーバ構成として、前段にキャッシュサーバがあれば、圧縮された結果をキャッシュしてくれますので、毎回圧縮処理が走ることにはなりません。JavaScript、CSSの外部ファイルはほとんどの画面で共通に読み込まれるものですので、高いキャッシュヒット率が期待できます。
この対応の効果
大きめのJavaScriptライブラリであるprototype.js(約95K)のサイズが、75%程度削減され、22K程度になっていました。大量にアクセスがあるサイトで考えるとかなりのネットワークリソースの節約になります。サーバのCPUにある程度余裕があれば、ぜひ導入すべき設定だと思います。
*1:mod_so は、apache コンパイル時にしか組み込む事ができないため、もし存在しなければApache本体の再コンパイルになります。
*2:この行を有効にすると、設定ファイル修正後、Apache再起動の際、「httpd: Syntax error on line 19 of /usr/local/apache2/conf/httpd-rewrite.conf: module filter_module is built-in and can't be loaded」というエラーが出ます。つまりもう組み込まれているよというエラーです。「/usr/local/apache2/bin/httpd -l」で確認しておくとよいです。もし無ければ、「/usr/local/apache2/bin/apxs -c -i /root/modules/httpd-2.2.11/modules/filters/mod_filter.c」でコンパイルしましょう。
*3:mod_deflateモジュールを読み込むと、ap_register_output_filter関数でDEFLATEというプロバイダー名が自動的に登録されます(そのプロバイダーがdeflateを行います)
*4:今回はHTTPレスポンスヘッダのContent-typeがtext /plainに部分一致した場合のみ、フィルターを実行するという条件をつけています。
*5:つまりFilterChainディレクティブを使わないと、定義されたフィルター群は実行されません
CakePHPで複数テーブルに対するトランザクションを使う場合
CakePHPでトランザクションを使用する必要があったのですが、一般的に用いられている方法だと、複数のテーブルを1つのトランザクションとして更新したい場合、コントローラ内での実装がとても分かりにくくなると感じ、異なる実装方法をとってみたので、ご紹介します。
一般的な実装方法とその課題
一般的な実装方法としては、app/models/app_model.phpに、下記のようなトランザクション管理用のメソッドを追加することが多いと思います。基本的に、各モデルクラスは、AppModelクラスを継承しているため、これらのメソッドをどのモデルからも利用可能になります。
function begin() { $db = & ConnectionManager::getDataSource($this->useDbConfig); $db->begin($this); } function commit() { $db = & ConnectionManager::getDataSource($this->useDbConfig); $db->commit($this); } function rollback() { $db = & ConnectionManager::getDataSource($this->useDbConfig); $db->rollback($this); }
しかし、この方法では、各モデルクラスのトランザクション管理用メソッドを利用するため、下記の例のようになります。
$this->User->begin(); $this->User->save($userData); $this->Company->save($companyData); $this->User->commit();
どうでしょうか?上記の実装を見て、Userモデルと、Companyモデルのトランザクションの関係を判断できますか?
答えは、Userモデルと、Companyモデルが同じDB接続設定(database.phpの設定)を利用していれば、同じトランザクションとなります。*1
ですが、そうは見えませんよね?これは混乱の元ですし、不具合につながる危険性もあります。
トランザクションの管理をコントローラ層に
コントローラ層でトランザクション境界を明確にする目的で、コンポーネントとして新たに「Transactionクラス」を用意します。
beginメソッドだけ抜粋して載せておきます。この実装であれば、もし、Userモデルと、Companyモデルが異なる接続先設定を参照していたとしても、基本的には問題なく動作します。*2
public function begin($models) { // インスタンス変数へ格納⇒commit,rollback時に利用する $this->models = $models; //各モデル毎にトランザクションを開始 foreach ($models as $model) { if (!$this->_begin($model)) { //トランザクション開始失敗時の処理 break; } } }
※$this->_beginの中で、各モデルの接続先単位でトランザクション開始するように実装されるイメージ。
コントローラでの実装は以下のようになります。このように、Userモデルと、Companyモデルが同じトランザクションにいるということがハッキリします。
$this->Transaction->begin(array($this->User,$this->Company)); $user = $this->User->save($userData); $company = $this->Company->save($companyData); if($user !== false && $company !== false){ /* * Transactionコンポーネントの内部実装としては、 * ・両モデルが同じ接続先:1度だけcommitが実行される * ・両モデルが異なる接続先:2つのDBにcommitが実行される */ $this->Transaction->commit(); }else{ // commitと同様の動作 $this->Transaction->rollback(); }
プロセス確認に便利なコマンド
プロセス確認する時、こんな感じでgrepしたりしますよね。でも若干面倒ですね。
[root]# ps -aef | grep httpd | grep -v grep
そこで、「pgrep」コマンドを使います。
[root]# pgrep -lf httpd 1890 /usr/local/apache2/bin/httpd -f /usr/local/apache2/conf/httpd.conf -k start 1912 /usr/local/apache2/bin/httpd -f /usr/local/apache2/conf/httpd.conf -k start 1913 /usr/local/apache2/bin/httpd -f /usr/local/apache2/conf/httpd.conf -k start 1914 /usr/local/apache2/bin/httpd -f /usr/local/apache2/conf/httpd.conf -k start 1915 /usr/local/apache2/bin/httpd -f /usr/local/apache2/conf/httpd.conf -k start 1924 /usr/local/apache2/bin/httpd -f /usr/local/apache2/conf/httpd.conf -k start 16275 /usr/local/apache2/bin/httpd -f /usr/local/apache2/conf/httpd.conf -k start 17307 /usr/local/apache2/bin/httpd -f /usr/local/apache2/conf/httpd.conf -k start 19698 /usr/local/apache2/bin/httpd -f /usr/local/apache2/conf/httpd.conf -k start 25100 /usr/local/apache2/bin/httpd -f /usr/local/apache2/conf/httpd.conf -k start
以上です。
参考URL:pgrep