今年(2013年)最後の研究日誌は、オープンソースのERP iDempiereのパフォーマンス改善の関する取り組みについてお伝えして、締めくくりたいと思います。
ここで紹介するパフォーマンス改善のトピックは、主に下記のiDempiere Wikiに記載されている内容です。
StringBufferの代わりにStringBuilderを使用する
Javaの1.5よりStringBuilderクラスが導入されました(※参考:Java6 StringBuilder)。私が知っている限りCompiere3.0では、StringBufferクラスが多用されていましたが、iDempiereではスレッドと関係がなく同期をとる必要がない所ではStringBufferをStringBuilderクラスに書きかえています。
1ヶ所や2か所変更しても、パフォーマンスにはあまり影響しないかもしれませんが、システム全体で書きなおせば、塵もつもれば山となるで、馬鹿に出来ないくらいの改善になっているのではないかと思います。
参考サイト
DB接続のクローズ処理を適切に行う
Compiere DistributionはDB接続に対して、PreparedStatementを使用したある程度定型化されたお作法的な書き方がありましたが、私が知っているCompiereの3.0では定型化されているといっても、そのクローズ処理については統一されているとは言えず、メモリーリークなどの原因にもなっていたと思われます。
iDempiereでは、ざっとソースコードを読む限りでは、DB接続の一連の処理をクローズ処理も含めて完全に定型化しています。DB接続を記載しているソースコードを洗い出して、書き直しています。
オープンソースのERP iDempiere(アイデンピエレ)のリポジトリーのメンテナーであるCarlos Ruizさんが、JIRAチケット568で、JDBCを使用する際の適切なクローズ処理について次のように記述しています。
Best practice is to close resultsets and statements on a finally block
(一番良い方法は、ResultSetとPreparedStatementをtry~catch構文のfinally句でクローズ処理する事です。)
そして、次のような定型文でDBにアクセスするようにソースコードを書き直しています。
string sql = "SQL文を記述"; ※クエリ実行条件の値は"?"としておく
PreparedStatement pstmt = null;
ResultSet rs = null;
try
{
pstmt = DB.prepareStatement (sql, get_TrxName());
pstmt.set~ ※クエリの実行条件("?")に代入される値を設定
rs = pstmt.executeQuery();
※rsから値を取得する処理
}
catch(Exception e)
{
※例外処理の記述
}
finally
{
DB.close(rs, pstmt);
rs = null;
pstmt = null;
}
地味な改善ですが、このような地味な改善がシステムの安定稼働には欠かせないのだと思います。Compiere Distributionの開発者の方々は、DBにアクセスする際には上記の定型文を常に頭においてプラグラムするようにして頂きたいなと思います。
例えば、取引先マスタのモデルクラス(MBPartner.java)にある取引先マスタの口座情報を取得するgetBankAccountsメソッドは次のように記述されています。
/** * Get Bank Accounts
* @param requery requery
* @return Bank Accounts
*/
public MBPBankAccount[] getBankAccounts (boolean requery)
{
if (m_accounts != null && m_accounts.length >= 0 && !requery) // re-load
return m_accounts;
//
ArrayList<MBPBankAccount> list = new ArrayList<MBPBankAccount>();
String sql = "SELECT * FROM C_BP_BankAccount WHERE C_BPartner_ID=? AND IsActive='Y'";
PreparedStatement pstmt = null;
ResultSet rs = null;
try
{
pstmt = DB.prepareStatement(sql, get_TrxName());
pstmt.setInt(1, getC_BPartner_ID());
rs = pstmt.executeQuery();
while (rs.next())
list.add(new MBPBankAccount (getCtx(), rs, get_TrxName()));
}
catch (Exception e)
{
log.log(Level.SEVERE, sql, e);
}
finally
{
DB.close(rs, pstmt);
rs = null;
pstmt = null;
}
m_accounts = new MBPBankAccount[list.size()];
list.toArray(m_accounts); return m_accounts;
} // getBankAccounts
参考サイト
ロギング時のロギングレベルの判定処理
ロギング処理の際に余計なオーバーヘッドがかからないように、fineなどのメッセージ処理の際には、ロギングのレベルを判定してから処理しています。