Java EE
パフォーマンスTips
2015/09/16
帰ってきた GlassFish Users Group Japan 勉強会
上妻 宜人 (あげつま のりと)
上妻 宜人 - あげつま のりと
• SIer勤務
• 技術サポート部隊に所属
• Java / Java EE トラブルシューティング
• はてな 見習いプログラミング日記
本日のテーマ
Java EEレイヤ毎の性能Tips
Presentation
Business
Logic
Repository
• JSF
• JAX-RS
• ....
• EJB
• CDI
• JPA/JDBC
• 外部API呼出
• ファイル I/O
Java EEパフォーマンスTips
• プレゼンテーション層 (JSF)
• #1 古いmojarraを使わない (JAVASERVERFACES-2494対策)
• ビジネス層 (EJB/CDI)
• #2 DIはEJBではなくCDIを使う
• #3 @Asynchronous利用時はスレッドプールに注意
• データアクセス層 (JPA)
• #4 JPAのexecuteBatch設定を忘れない
#1 古いmojarraを使わない
(JAVASERVERFACES-2494対策)
#1 古いmojarraを使わない
• JSF参照実装mojarraには、かつて性能バグがあった
• JAVASERVERFACES-2494
(https://java.net/jira/browse/JAVASERVERFACES-2494)
• JSFタグ (UIComponent) が増えると遅くなる問題
• 2.1.22, 2.2.1 で修正
• GlassFish4.0 が該当。4.1でFix。
GlassFish4.0 と Payara4.1 で実測
• 大量のJSFタグがポイント
• 1000, 3000, 5000 タグで GlassFish4.0 と Payara4.1 比較
<h:body>
<h1>JSF Many Component 1000</h1>
<h:form>
<h:outputText value="#{testBean.name}"/>
<h:outputText value="#{testBean.name}"/>
<h:outputText value="#{testBean.name}"/>
<h:outputText value="#{testBean.name}"/>
<h:outputText value="#{testBean.name}"/>
... 以降大量のoutputTextを繰り返す
タグ数が多い場合は注意
• GlassFish4.0: レスポンスタイムが徐々に低下
• Payara4.1.153 : 5000タグで150ミリ秒
0
1000
2000
3000
4000
5000
1000 2000 3000 4000 5000
レスポンスタイム(ミリ秒)
JSFタグ数
GlassFish 4.0
(mojarra2.2.0)
Payara 4.1.153
(mojarra2.2.11)
マシン情報:
MacBook Air corei5 1.7GHz
JDK1.8.0_60
mojarraは色々な所で使われている
• GlassFish
• 4.0は影響あり。 4.1以降で修正。
• Payaraは4.1ベースなので影響なし。
• WildFly
• 8.0.0と8.1.0は影響あり。 8.2以降で修正。
Java EEパフォーマンスTips
• プレゼンテーション層 (JSF)
• #1 古いmojarraを使わない (JAVASERVERFACES-2494対策)
• ビジネス層 (EJB/CDI)
• #2 DIはEJBではなくCDIを使う
• #3 @Asynchronous利用時はスレッドプールに注意
• データアクセス層 (JPA)
• #4 JPAのexecuteBatch設定を忘れない
#2 DIはEJBではなくCDIを使う
#2 DIはEJBではなくCDIを使う
• Java EE6 CDI が導入。
• Java EE7 より beans.xml なしでデフォルト有効化。
• Java EE5 まではEJB間でのみDIが利用可。
@Inject
private StockService service;
@Dependent
public class StockService {
// ...
}
EJBのループ呼び出しによるオーバヘッド
• EJBのループ呼び出しは性能上悩みの種だった
• 万単位以上のループのみオーバヘッド顕在化
• 見つかった時には試験期間の後半
実際に測っている
• Payara4.1.152でBeanをループ呼び出し
• EJB と CDI Bean を呼び出して比較
// EJB
@Stateless
public class EJBBean {
public String echo(String s) {
return s;
}
}
// CDI
@ApplicationScoped
public class CDIBean {...}
// Client
@Inject
EJBBean ejb;
for (int i = 0; i < loop; i++) {
ejb.echo(String.valueOf(i));
}
CDIはほとんどオーバヘッドがない
271
907
2953
1 8 15
10000 50000 100000
EJB @Stateless CDI @ApplicationScoped
ループ呼び出し回数
単位: ミリ秒
マシン情報:
MacBook Air corei5 1.7GHz
JDK1.8.0_60, Payara4.1.153
@Transactionalもループで呼び出してみた
• @Transactinal 宣言的トランザクション (Java EE7 〜)
• ついうっかり、ループで呼び出される状況を想定
@ApplicationScoped
@Transactional
public class StockService {
public void put(Stock s) {
// 更新系処理 ...
}
public void search(Criteria c) {
// 参照系処理 ...
}
}
@Transactionalはループで呼ばないこと
• @Transactional の実体はインターセプタ。
• ループ呼出しコストは高い。
271 907 2953
1 8 151909
8822
32761
10000 50000 100000
EJB @Stateless
CDI @ApplicationScoped
CDI @ApplicationScoped & @Transactional
ループ呼び出し回数
マシン情報:
MacBook Air corei5 1.7GHz
JDK1.8.0_60, Payara4.1.153
単位: ミリ秒
#3 @Asynchronous利用時は
EJBスレッドプールに注意
#3 @Asynchronous利用時はEJBスレッドプールに注意
• @Asynchronousの振り返り
• EJB3.1 (Java EE6) で導入
• シンプルに非同期タスク処理が実装できる
@Stateless
public class AsyncBean {
@Asynchronous
public Future<String> async() {
return new AsyncResult<>("done”);
}
}
GlassFishデフォルトは多重度が増えない
• 以下の非同期EJBの呼び出しは、16多重で止まる
• GlassFish4 EJBスレッドプールのデフォルト上限が16
• Performance Tuning Guideにも言及がなく、ハマる
@Inject
private AsyncBean async;
public void callAsyncEjb() {
IntStream.range(0, 100)
.forEach(i -> async.async());
}
“EJB Container” のプロパティを追加
• 固有の入力欄はなく、プロパティとして設定
• thread-core-pool-size : 最小プールサイズ
• thread-max-pool-size : 最大プールサイズ
Java EEパフォーマンスTips
• プレゼンテーション層 (JSF)
• #1 古いmojarraを使わない (JAVASERVERFACES-2494対策)
• ビジネス層 (EJB/CDI)
• #2 DIはEJBではなくCDIを使う
• #3 @Asynchronous利用時はスレッドプールに注意
• データアクセス層 (JPA)
• #4 JPAのexecuteBatch設定を忘れない
#4. JPAのexecuteBatch設定を忘れない
• JDBC利用時はexecuteBatchを皆意識する
• JPAを使い始めると、何故か忘れやすい
• JPA内部で自動的に最適化されている淡い期待
• しかし、EclipseLinkもHibernateもバッチ更新デフォルト無効
EclipseLink のバッチ書き込み設定
• persistence.xml に設定
• eclipselink.jdbc.batch-writing = jdbc (default: none)
• eclipselink.jdbc.batch-writing.size = ... (default: 100)
<persistence-unit name="PostgresPU">
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<jta-data-source>jdbc/postgresDS</jta-data-source>
<properties>
<property name="eclipselink.jdbc.batch-writing" value="jdbc"/>
<property name="eclipselink.jdbc.batch-writing.size" value="100"/>
...
Payara4.1で実際に測ってみる
• PostgreSQLに100, 1000, 1万レコードのINSERT
• JDBCを加えてO/Rマッパー遅い疑惑も検証
• #1. JDBC executeBatch
• #2. JPA batch-writing=jdbc (バッチ有効化)
• #3. JPA batch-writing=none (バッチ無効化)
測定結果
• INSERT 100レコード でも十分な効果がある
• JDBCと比較して、JPA(EclipseLink) は1〜2割遅い
• 1万行であれば、ORMコストはそれほど大きくない
40 128 122375 169 14891243
9023
58247
100レコード 1000レコード 10000レコード
JDBC executeBatch EclipseLink (batch-writing=jdbc) EclipseLink (batch-writing=none)
単位: ミリ秒
マシン情報:
MacBook Air corei5 1.7GHz
JDK1.8.0_60, Payara4.1.153, PostgreSQL9.3
まとめ
#1 古いmojarraを使わない
GlassFish4.1以上 or Payaraを使う
(mojarra2.1.22, 2.2.1 以上)
#2 DIはEJBではなくCDIを使う
過ループ時のEJBのオーバヘッドは大きい
@Transactionalループ呼び出しに注意
#3 @Asynchronous利用時は
EJBスレッドプールに注意
EJBコンテナの以下プロパティを忘れずに設定
thread-core-pool-size, thread-max-pool-size
#4 JPAのexecuteBatch設定を忘れない
EclipseLink/Hibernateデフォルトはバッチ無効
persistence.xml
eclipselink.jdbc.batch-writing=jdbc

Java EE パフォーマンスTips #glassfish_jp