2017年7月26日水曜日

ADempiereのIEブラウザ対応


ADempiereは、FireFoxやChromeには対応しており問題なく動作するが
IEとなると少し問題がある。
グリッド表示やテキストボックスの表示がおかしくなったり、画面が真っ白になってしまうことがある。

ただし、これは、コミュニティ版のADempiereの場合で、SMBアソシエイツ社では、IEでも問題なく正常動作するように修正を加えている。

ADempiereをIEで正常動作させるためには、以下の対応が必要となる。


■Compatible対応のヘッダー出力をしない
 WebUIプロジェクトのindex.zulで、IE8へのCompatible対応タグが記述されているので、これを削除する。
 具体的には、index.zulの7行目あたりの以下の部分を削除する。
  <?meta http-equiv="X-UA-Compatible" content="IE=8" ?>

■拡大、縮小時のエラー対応
 上記の対応をすれば、IEでもほぼほぼ正常動作が可能になるが
 Ctrl+マウスホイールを使った拡大縮小時にエラーが発生してしまう。
 
 これに対応するには、以下のソースの修正が必要である。
 zk.jarの中にある「MouseCommand.java」に以下の修正をおこなう。
 processメソッドの中で、NumberForamtExceptionが発生しないように対処をおこなっている。
 
        // SMBA_CHG IE対応(NumberFormatExceptionの回避)
        MouseEvent event = null;

        if( data == null || data.length == 0 )
        {
            event = new MouseEvent(getId(), comp);            //no area, no coord
        }
        else if( data.length == 1 )
        {
            event = new MouseEvent(getId(), comp, data[0]);    //by area
        }
        else
        {
            BigDecimal bdVal1 = new BigDecimal(data[0]);
            BigDecimal bdVal2 = new BigDecimal(data[1]);

            event = new MouseEvent(getId(), comp,            //by coord
                bdVal1.intValue(), bdVal2.intValue(),
                data.length < 3 ? 0: Commands.parseKeys(data[2]));
        }

//        final MouseEvent event =
//        data == null || data.length == 0 ?
//            new MouseEvent(getId(), comp):            //no area, no coord
//        data.length == 1 ?
//            new MouseEvent(getId(), comp, data[0]):    //by area
//            new MouseEvent(getId(), comp,            //by coord
//                Integer.parseInt(data[0]), Integer.parseInt(data[1]),
//                data.length < 3 ? 0: Commands.parseKeys(data[2]));
        // CHG_END
 

  ちなみに、zk.jarは、通常はjarとして参照していることが多いが、ZK関連のjarもEclipseプロジェクト化しておくと、デバッグができるので便利である。



■メッセージの非表示
 最後に、ログインページで表示されるメッセージを非表示にする。
 IEでログインページにアクセスすると、「一部機能が正常に動作しない可能性がある」という旨のメッセージが表示されてしまう。

 これを表示しないようにしておくには、webuiプロジェクトのWLogin.javaを修正する。
 81行目あたりでメッセージを表示している部分を削除しておく。
 
        /*
        if (!AEnv.isBrowserSupported())
        {
//            String msg = "You might experience slow performance and user interface anomalies using your current browser to access the application. We recommend the use of Firefox, Google Chrome or Apple Safari.";
            String msg = "You might experience slow performance and user interface anomalies using your current browser to access the application. We recommend the use of Firefox, Google Chrome or Apple Safari."
                + "<BR>現在のブラウザはADempiereの一部機能が正常に動作しない可能性があります。ADempiereのすべての機能を使用するにはFirefoxやGoogle ChromeまたはSafariなどのブラウザをお勧めします。";
            browserWarningWindow = new Window();
            Div div = new Div();
            div.setStyle("background-color:#FFFFCC; font-size: " + (AEnv.getFontSize() - 1) + "pt");
            div.appendChild(new Text(msg));
            browserWarningWindow.appendChild(div);
            browserWarningWindow.setPosition("top,right");
            browserWarningWindow.setWidth("550px");
            browserWarningWindow.setPage(page);
            browserWarningWindow.doOverlapped();
        }
        */

これらの対応をしておけば、ADempiereにInternet Explorerでアクセスしても、正常に動作させることが可能である。


2017年7月25日火曜日

ADempiereのトランザクションとコネクション

ADempiereでは、基本的には、プロセスクラスがひとつのトランザクションクラスを保持し、
トランザクションクラスがひとつのコネクションを保持するという関係になっている。
プロセスクラスでひとつだけのトランザクションを使用する場合においては
プロセスクラスがトランザクションとコネクションを管理してくれるようになっている。

例外的には、スレッドなどを使うために、プロセスクラスで複数のトランザクションを使用する場合がある。この場合は、プロセスクラスに依存せず、トランザクションやコネクションの管理を適切におこなう必要がある。

プロセスクラスでひとつのトランザクションを使用する場合においては
プロセスクラスがトランザクションとコネクションを管理してくれるので
(プロセス開始時にトランザクション開始して、プロセス終了時にトランザクションを終了)
複数のトランザクションを使用しない限り、コミット漏れなどを気にする必要はないように見える。

注意が必要なのは、以下に説明するModelクラスを使用する場合とDB.javaのstaticメソッドを使用する場合である。


■Modelクラスとトランザクション
Modelクラスでのトランザクション管理は、少し複雑であるが
大きく分類すると
Modelクラスでトランザクションを作成してクローズするのか、
Modelクラスにトランザクションを外から渡し、トランザクション管理は呼び出し側でおこなうという2通りがある。

<Modelクラスにトランザクションを外から渡す場合>

まず、Modelクラスにトランザクションを外から渡す場合である。
ADempiereでは、通常このパターンでModelクラスを運用することが多いと思われる。
これは、Modelクラスをインスタンス化するときに、以下のようにトランザクション名を渡すことで、
Modelクラスに外からトランザクションを渡せるようになる。
    MPPOrder ppOrder = new MPPOrder(getCtx(), 0, get_TrxName());

通常は、Processクラスのget_TrxName()メソッドを使用して、プロセスクラスが保持しているトランザクションをそのまま渡すことで、ProcessクラスとModelクラスとのトランザクションを統一している。

この場合、Modelクラスのsave()メソッドを呼び出しても、コミットやトランザクションのクローズはされず、これらは呼出元に任されることになる。
つまり、呼出元で必ずコミットやクローズをするようにしないとトランザクションが残ってしまう。
☆呼出元でトランザクション管理をすることが必須となるので注意が必要。(プロセスクラスからの呼び出しであればプロセスクラスがやってくれる。)

また、この場合、エラーがおこったとき、ロールバックはされないが、SavePointを使用して、このModelクラスでおこなった更新処理だけを元に戻すようになっている。

<Modelクラスにトランザクションを外から渡さない場合>

Modelクラスにトランザクションを渡さない場合は、Modelクラス内部で新しいトランザクションが作成される。また、トランザクションは、Modelクラス内でコミットまたはロールバックがおこなわれ、クローズまでされる。Modelクラスないだけで利用されるいわゆるローカルトランザクションとなる。


■DB.javaのStaticメソッドとトランザクション
DB.javaでは、SQLを単発的に実行できるStaticメソッドがたくさん定義されている。
これらのメソッドでも、トランザクション名を外から渡せるようになっており
Modelクラスと同様に、トランザクションを外から渡すか、内部で作成するかを選ぶことができる。

ただ、コミュニティ版のDB.javaでは、コネクションのクローズ漏れがあり
これらのメソッドに、トランザクション名をNullで渡すと、コネクションが残ってしまう。
SMBアソシエイツ社では、これらのDB.javaのStaticメソッドのコネクション漏れを修正している。

また、さらに、SMBアソシエイツ社では、複数トランザクションの実装やコネクションプールを使わない選択ができるようにプロセスクラスを改造している。

ADempiereのヒープメモリの設定

ADempiereのヒープメモリの設定について、どのくらいが適切なのか

もちろん、潤沢なメモリ環境であれば、できるだけ多く設定しておいたほうが間違いないが
弊社で把握しているヒープメモリの設定状況について記述したい。

まず、ヒープ全体について、特にMRPなどの負荷の高い処理を行う場合は、最低4GB、できれば6GB以上あったほうがベターである。
Javaの起動オプションは以下で指定する。
 -Xms6144M -Xmx6144M

次にヒープメモリの内訳について、

■Permanent領域
まず指定しやすいのは、Permanentメモリサイズである。
これは、クラス定義のような一度メモリ登録すれば書き換わることのないであろうデータを登録する領域である。
ADempiereの場合、クラスが多いので通常のアプリケーションよりもこのサイズは、大きいほうがベターであるが、それでも256MBもあれば十分である。これでも、使用率が30%を超えることはないと考えられる。
Javaの起動オプションは以下で指定する。
 -XX:PermSize=256M -XX:MaxPermSize=256M


■New領域
New領域とは、クラスをNewしたときに格納される領域である。クラスがNewされると、まずはEden領域というところに格納され、YoungGCまたはFullGCによって、Eden領域からSurvivor領域に移動されるか解放される。

このNew領域が小さければ、YoungGCが頻繁に走ってしまい、処理落ちを起こす原因となってしまう。
また、ADempiereの場合、Newされるオブジェクトが多いが、すぐに解放されることが多いので
New領域を多めにとって、Old領域を少なめにしても、よほど下手なプログラムを記述しない限り、FullGCが頻発することは考えにくい。

よって、7割から8割をNew領域に割り当てて問題ないといえる。
この設定でも、ある案件で実運用と同様のオーダー数のMRPを回した後でも、Old領域は1割程度の使用率である。

Javaの起動オプションは以下で指定する。
 -XX:NewSize=4096M -XX:MaxNewSize=4096M


<Survivor領域>
Survivor領域とは、Eden領域のうち、GCによって解放されなかったオブジェクトが格納される領域である。
このSurvivor領域とEden領域の割合を起動オプションで設定できるが、これを変えても大きな変化は見られなかった。
理由の推測としては、この割合を変えても、New領域全体が変わるわけではないので、YGCの頻度も変わらないから当然と思われる。
よって、これは、デフォルトでいいと考えられる。

もし、設定する場合、Javaの起動オプションは以下で指定する。
 -XX:SurvivorRatio=2


■Old領域
Old領域は、Survivor領域で、何度も解放されなかったオブジェクトおよび、Survivor領域がいっぱいになったときのオブジェクトが移動される領域である。
この領域がいっぱいになると、FullGCが発生し、何度もいっぱいになると、FullGCが頻発することになるのが、上記でも説明したとおり、ADempiereでは、Old領域に大きなサイズが必要ということにはなりにくいため、ヒープ全体の2割から3割でいいと考えられる。

Javaの起動オプションは特に指定しない。(NewRatioで設定もできるが、全体から、パーマネントとNew領域をひいたサイズとなるため。)


■ヒープ設定例(全体を6GBとしたとき)
 -Xms6144M -Xmx6144M -XX:PermSize=256M -XX:MaxPermSize=256M -XX:NewSize=4096M -XX:MaxNewSize=4096M

 ※もう少し、New領域を増やしてもよいが、ちまたのベストプラクティス(New:Old=1:2)と乖離しすぎるので・・・。
 ※32ビットOSでは、2GBまでなので注意。
 ※起動オプションのgcInterval指定について不要という人もいるが、個人的にはあったほうがいいと思っている。1時間に1回ぐらいは、FullGCされてもいいかなと思うし、逆にこれをせずにメモリオーバーフローになるほうが怖いと思う。(gcInterval=3600000)

Postgresで現在の接続数を確認する

■現在、接続されているConnectionを確認するには
「pg_stat_activity」というテーブルを確認するとよい。

  SELECT * FROM pg_stat_activity


ここに登録されているレコード1件ずつが現在接続されているConnectionである。
レコードが100件あったら、現在接続されているConnection数は、100ということになる。


■実行中のトランザクションを確認するには
上記「pg_stat_activity」テーブルの「xact_start」列が入っている場合は、そのコネクションでトランザクションが実行されている状態。



もし、実行プログラムが終了しているのに、この列に値が入っているような場合は、コミット漏れのトランザクションがある可能性があるので注意が必要。
特に、コミット漏れが原因で、他のトランザクションのレスポンスが著しく遅くなる場合があるのでコミット漏れがないかは、確実にチェックしておいたほうがいいと思われる。
 ちなみに、上記のQuery列に「SHOW TRANSACTION ISOLATION LEVEL」で表示されるのは、コネクションプールを使って接続した場合のコネクションであることが多い。


■強制的に、コネクションを切断するには
 以下のコマンドを実行すればよい。

SELECT pg_terminate_backend(PID)

※PIDは、上記「pg_stat_activity」テーブルの「pid」列値を使うとよい。
※ただし、コネクションプールなどで保持されているコネクションを強制的に切断した場合は
 再度、コネクションプールのコネクションでプログラムが実行された場合、当然のことながらエラーになるので注意が必要。 (このコマンドの使用は開発中の作業に限定されると思われる)


2017年7月21日金曜日

ヒープの状態とGCの実行回数を表示する(備忘録)

ヒープメモリの状態とGCの実行回数を表示するコマンド

jstat -gcutil -h3 <ProcessID> <表示頻度(ms)>


ex.
jstat -gcutil -h3 4716 5000


  S0     S1     E      O      P     YGC     YGCT    FGC    FGCT     GCT
 92.74   0.00  76.22   9.29   7.27    188   23.448     0    0.000   23.448
 92.74   0.00  98.90   9.29   7.27    188   23.448     0    0.000   23.448
  0.00  92.31  23.16   9.29   7.27    189   23.630     0    0.000   23.630

<各列の説明>
S0:S0領域の使用率(Young領域)
S1:S1領域の使用率(Young領域)
E :Eden領域の使用率(Young領域)
O :Old領域の使用率(Old領域)
P :Permanent領域の使用率(永続領域)

YGC:YoungGCの発生回数
YGCT:YoungGCの処理時間(秒)
FGC:FullGCの発生回数
FGCT:FullGCの処理時間(秒)
GCT:YoungGCとFullGCの処理時間(秒)


<ヒープの世代>
■Young領域
S0,S1,EdenがYoung領域(Newされてからまだ若いヒープ)
Eden: Newがおこなわれるとまずここにたまる。
S0: Edenから少し年をとったヒープ
S1: Edenから少し年をとったヒープ

■Old領域
 Young領域にある間に、解放されることがなかったヒープ。
 Young領域から、Old領域に移動するタイミングは、YoungGCの回数やS0,S1領域が満杯になったときなどGCアルゴリズムによる。

■Permanent領域
 基本的には永続的に保持するメモリ。主にクラス定義など


<ヒープの容量を表示する>
上記のオプションの「-gcutil」を「-gc」に変えるだけでよい。

jstat -gc -h3 <ProcessID> <表示頻度(ms)>


ex.
jstat -gc -h3 4716 5000


S0C     Survivor 領域 0 の現在の容量 (KB)
S1C     Survivor 領域 1 の現在の容量 (KB)
S0U     Survivor 領域 0 の使用量 (KB)
S1U     Survivor 領域 1 の使用量 (KB)
EC     Eden 領域の現在の容量 (KB)
EU     Eden 領域の使用量 (KB)
OC     Old 領域の現在の容量 (KB)
OU     Old 領域の使用量 (KB)
PC     Permanent 領域の現在の容量 (KB)
PU     Permanent 領域の使用量 (KB)
YGC     若い世代の GC イベント数
YGCT     若い世代のガベージコレクション時間
FGC     フル GC イベント数
FGCT     フルガベージコレクション時間
GCT     ガベージコレクション総時間

※S0CやS0U、ECやEUの「C」はCapacity、「U」はUsageと思われる。

http://docs.oracle.com/javase/jp/7/technotes/tools/share/jstat.html

2017年7月20日木曜日

ADempiereのMRPの処理速度改善メモ

MRPの処理速度改善のために、いろいろ実装したが、スレッド化がかなり効果的だった。
1スレッドごとに16%程度の処理改善がみられたようだ。
ただし、コア数以上のスレッドでは改善率が極端に下がる。

試験環境は、6コアで、1.16の6乗=2.4363
実際には、6スレッド以上を起動しているので、2.4倍以上の速さになることを確認。

そこそこの部品構成と需要件数でも、30分で処理が終わるMRPになった。

技術的には、スレッドごとにレコードロックが競合しないように設計すること、こまめにコミットしてレコードロックを長時間保持しないこと、Wait、Notifyを巧みに使用すること、トランザクションのコミット漏れや長時間トランザクション、コネクション数のケア、Postgresのデッドタプル、オートバキューム実行ログのチェックなどがポイントだったように思う。