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