大規模な負荷でもドキドキしない為のjava ee

45
JJUG CCC Spring 2015 #jjug_ccc #ccc_ab3 大規模な負荷でも ドキドキしない為の Java EE

Upload: taiichilow-nagase

Post on 17-Jul-2015

5.559 views

Category:

Technology


0 download

TRANSCRIPT

JJUG CCC Spring 2015 #jjug_ccc #ccc_ab3

大規模な負荷でも ドキドキしない為の

Java EE

@nagaseyasuhito

java-ja

グリー株式会社

the CRAZY ANGEL COMPANY

Agenda

その1 負荷テストするぞ その2 JPAのスケールアウト戦略

負荷テストするぞ

http://www.flickr.com/photos/mattt_org/2831690932 "Electrocardiogram" by mattt.org is licensed under CC BY 2.0 / Added some texts to original

Stress Test Anti

Pattern

アンチパターン その1 「シングルスレッドで実行」

Stress Test Anti

Pattern

アンチパターン その2 「ユースケースとかけ離れたシナリオ」

Stress Test Anti

Pattern

アンチパターン その3 「複数のサーバーでコマンドを叩いて手動実行」

そもそも負荷テストの目的は?

https://www.flickr.com/photos/jakecaptive/3205277810 Thinking RFID by Jacob Bøtter is licensed under CC BY 2.0 / Added some texts to original

What Is

Stress Test For?

負荷テストの目的 その1 システムの限界性能を知る

What Is

Stress Test For?

負荷テストの目的 その2 高負荷時の不具合を発見する

https://www.flickr.com/photos/hermanolobo/8605855035 Road to Hell by Jose Padin is licensed under CC BY 2.0 / Added some texts to original

Stress Test meets

Continuous Integration

Apache JMeter 負荷テストツールのデファクトスタンダード GUIでシナリオ作成 Pure Java 分散実行に対応

Apache®, Apache JMeter™ and Apache JMeter logo are either registered trademarks or trademarks of the Apache Software Foundation in the United States and/or other countries.

HTTP(S) Test Script Recorder HTTP(S)のプロキシサーバーとして振る舞いHTTPリクエストをトレースする。

Selenium Web Driver Sampler WebアプリケーションのテストツールSeleniumのシナリオを使って負荷テストを行える。 Selenium IDEは実際のユーザーのアクティビティをそのままテストシナリオにできるので、より本番に近い負荷をかけることができる。

JUnit Request $JMETER_HOME/lib/junitにあるJUnitテストを実行できる。 • 独自プロトコルの負荷テスト • 既存のプラグインでは表現できない複雑な負荷テストシナリオ などをJUnitテストとして書いて負荷テストができる。

Make Scenario

@Test(timeout = 1500)@SneakyThrowspublic void createAndShow() {

String mailAddress = UUID.randomUUID() + "@example.com";

WebTarget target;target = this.client.target(this.url.toString());target = target.path("api/user");target = target.queryParam("mailAddress", mailAddress);

User user = target.request().method("POST", User.class);assertThat(user.getMailAddress(), is(mailAddress));

Thread.sleep(1000L);target.path(user.getId().toString()).request().get(User.class);

}

JUnit負荷テストシナリオの例 上記の例はJAX-RSのクライアントを使いREST APIのリクエストを発行し、レスポンスの値を使いふたたびリクエストを発行するサンプル。 独自のプロトコルや複数のコネクションなども柔軟に扱える。 アサートの条件なども独自に定義できるので複雑な負荷テストシナリオを作りやすい。

Running On

Command Line

$ jmeter -n -t stress-test.jmx -l stress-test.jtlCreating summariser <summary>Created the tree successfully using stress-test.jmxStarting the test @ Fri Apr 03 18:16:27 JST 2015 (1428052587427)Waiting for possible shutdown message on port 4445summary + 6 in 3s = 2.0/s Avg: 57 Min: 9 Max: 294 Err: 0 (0.00%) Active: 1 Started: 6 Finished: 5summary + 50 in 29.4s = 1.7/s Avg: 7 Min: 4 Max: 11 Err: 0 (0.00%) Active: 1 Started: 56 Finished: 55summary = 56 in 33s = 1.7/s Avg: 12 Min: 4 Max: 294 Err: 0 (0.00%)summary + 44 in 26s = 1.7/s Avg: 5 Min: 5 Max: 7 Err: 0 (0.00%) Active: 0 Started: 100 Finished: 100summary = 100 in 59.4s = 1.7/s Avg: 9 Min: 4 Max: 294 Err: 0 (0.00%)Tidying up ... @ Fri Apr 03 18:17:26 JST 2015 (1428052646994)... end of run

コマンドラインで実行 jmeterコマンドを-nオプションで起動するとCLIモードになる。-tオプションで負荷テストシナリオを指定して実行する。 -lオプションで指定したファイルに負荷テストの結果が保存される。

Running On

Command Line

$ jmeter-server &$ jmeter -n -t stress-test.jmx -R localhost -l stress-test.jtlCreating summariser <summary>Created the tree successfully using stress-test.jmxConfiguring remote engine for localhostUsing remote object: UnicastRef [liveRef: [endpoint:[10.48.138.59:63762](remote),objID:[76e350db:14c7e9b35bc:-7fff, -2457264288549687940]]]Starting remote enginesStarting the test @ Fri Apr 03 18:27:34 JST 2015 (1428053254765)Remote engines have been startedWaiting for possible shutdown message on port 4445summary + 50 in 29.4s = 1.7/s Avg: 7 Min: 4 Max: 11 Err: 0 (0.00%) Active: 1 Started: 56 Finished: 55summary = 56 in 33s = 1.7/s Avg: 12 Min: 4 Max: 294 Err: 0 (0.00%)summary + 44 in 26s = 1.7/s Avg: 5 Min: 5 Max: 7 Err: 0 (0.00%) Active: 0 Started: 100 Finished: 100summary = 100 in 59.4s = 1.7/s Avg: 9 Min: 4 Max: 294 Err: 0 (0.00%)Tidying up ... @ Fri Apr 03 18:17:26 JST 2015 (1428052646994)... end of run

コマンドラインで実行(分散) -Rオプションでjmeter-serverが起動したホストを指定すると分散実行される。 負荷テストの結果もマージして保存される。

Running On

Command Line

負荷テスト結果の閲覧 CUIで出力された負荷テストの結果(*.jtl)をGUIのリスナーで閲覧できる。 負荷テストの傾向などをパッと見たいときに。

JMeter Maven Plugin

<plugin> <groupId>com.lazerycode.jmeter</groupId> <artifactId>jmeter-maven-plugin</artifactId> <version>1.10.1</version> <executions> <execution> <phase>verify</phase> <goals> <goal>jmeter</goal> </goals> </execution> </executions> <configuration> <testFilesDirectory>${project.build.testOutputDirectory}</testFilesDirectory> <ignoreResultFailures>true</ignoreResultFailures> <suppressJMeterOutput>false</suppressJMeterOutput> <remoteConfig> <startServersBeforeTests>true</startServersBeforeTests> <serverList>${jmeter.servers}</serverList> </remoteConfig> </configuration></plugin>

JMeter Maven Plugin

<profile> <id>stress-test</id>

<build> <plugins> <plugin> <groupId>com.lazerycode.jmeter</groupId> <artifactId>jmeter-maven-plugin</artifactId> <version>1.10.1</version> </plugin> ... </plugins> </build></profile>

負荷テスト用プロファイル 通常のビルドプロセスに組み込まないようにjmeter-maven-pluginはプロファイルに分離すると使いやすい。 mvn clean verify -Pstress-test

JMeter Maven Plugin

<properties> <jmeter.numberOfThreads>1</jmeter.numberOfThreads> <jmeter.loopCount>1</jmeter.loopCount> <jmeter.rampUpPeriod>60</jmeter.rampUpPeriod>

<jmeter.servers>localhost</jmeter.servers></properties>

負荷テスト環境用プロファイル Maven実行時に値を調整できるようにプロパティ化すると便利。 $ mvn clean verify -Djmeter.numberOfThreads=100 -Djmeter.servers=10.0.0.41,10.0.0.42

JMeter Maven Plugin

$ mvn clean verify...[INFO] --- jmeter-maven-plugin:1.10.0:jmeter (default) @ sample-jmeter ---[INFO][INFO] -------------------------------------------------------[INFO] P E R F O R M A N C E T E S T S[INFO] -------------------------------------------------------[info][info] Executing test: com.github.nagaseyasuhito.sample.jmeter.EchoEndpointST.jmx[info] Creating summariser <summary>[info] Created the tree successfully using /Users/nagaseyasuhito/Documents/workspace/sample-jmeter/target/test-classes/com.github.nagaseyasuhito.sample.jmeter.EchoEndpointST.jmx[info] Configuring remote engine for localhost[info] Using remote object: UnicastRef [liveRef: [endpoint:[10.48.138.59:62076](remote),objID:[7104f3ba:14c7e91302b:-7fff, -7569131339288171743]]][info] Starting remote engines[info] Starting the test @ Fri Apr 03 18:16:30 JST 2015 (1428052590810)[info] Remote engines have been started[info] Waiting for possible shutdown message on port 4446[info] summary + 98 in 58.2s = 1.7/s Avg: 6 Min: 4 Max: 11 Err: 0 (0.00%) Active: 0 Started: 100 Finished: 100[info] summary = 100 in 59.4s = 1.7/s Avg: 7 Min: 4 Max: 146 Err: 0 (0.00%)[info] Tidying up remote @ Fri Apr 03 18:17:30 JST 2015 (1428052650888)[info] Exitting remote servers[info] ... end of run[info] Completed Test: com.github.nagaseyasuhito.sample.jmeter.EchoEndpointST.jmx[INFO][INFO] Test Results:[INFO][INFO] Tests Run: 1, Failures: 0

Running On

JenkinsPerformance Plugin JMeterが出力する負荷テストの結果をJenkinsでプロットするプラグイン。Report filesに **/*.jtlのようにプロットする結果ファイルのパスを記述する。

Running On

JenkinsPerformance Plugin JMeterが出力する負荷テストの結果をJenkinsでプロットするプラグイン。Report filesに **/*.jtlのようにプロットする結果ファイルのパスを記述する。

Bug And

Bottleneck

https://www.flickr.com/photos/mjhagen/2973212926 Scream by Mingo Hagen is licensed under CC BY 2.0 / Added some texts to original

他ユーザーのレスポンスが返ってくる レスポンスが返ってこない リソースは余っているのにレスポンスが遅い リクエストが遅い レスポンスが遅い

https://www.flickr.com/photos/dailym/6790546237 bottleneck by ferrie=differentieel & Jöran Maaswinkel DailyM.net is licensed under CC BY 2.0 / Added some texts to original

ボトルネックを探すぞ

Ganglia

Resource Monitoring ボトルネックやスケールアウトの計画を立てるためにリソースのモニタリングは重要。 ロードアベレージ・CPU・メモリ・ディスクアクセス・スワップ・ネットワーク転送量・コネクション数・GC頻度・ヒープ使用率・スレッド数など気になるところは可視化しておく。 Zabbix、Munin、MRTG、Sensuなど。下記はGangliaの例。

Ganglia

jmxsh plugin JXMの情報をGangliaで取得するためのプラグイン。下記のオプションを有効にする。 -Dcom.sun.management.jmxremote-Dcom.sun.management.jmxremote.port=8887-Dcom.sun.management.jmxremote.ssl=false-Dcom.sun.management.jmxremote.authenticate=false

Ganglia

jmxsh plugin プラグインの設定ファイルに値を取得するホストの情報と、プロットする値を設定する。 metricの値の末尾に##diffを追加すると差分、##deltaを追加すると増分をプロットする。 Total Thread Countなどの積算値は増分をプロットすると見やすい。

modules { module { name = 'jmxsh' language = 'python' param host { value = 'localhost' } param port { value = '8887' } param name { value = 'jvm' } param metric_group { value = 'jvm' } param heap { value = 'java.lang:type=Memory HeapMemoryUsage' } param total_started_thread_count { value = 'java.lang:type=Threading TotalStartedThreadCount' } }}collection_group { collect_every = 15 time_threshold = 45 metric { name = 'jmx_jvm_heap' } metric { name = ‘total_started_thread_count##delta’ }}

Flight Recorder

Mission Control Flight Recorderで取得したJVMの統計情報を可視化するツール。 -XX:+UnlockCommercialFeatures-XX:+FlightRecorderというオプションを付けてアプリケーションサーバーを起動し、jcmdコマンドで統計情報を取得する。

# プロファイル開始jcmd [プロセスID] JFR.start

# データのダンプjcmd [プロセスID] JFR.dump filename="[出力ファイル名]" recording=[レコードID]

https://www.flickr.com/photos/chidorian/106706292 Shogi by Ishikawa Ken is licensed under CC BY 2.0 / Added some texts to original

JPAの スケールアウト

戦略

READが 頭打ち

https://www.flickr.com/photos/mjhagen/2973212926 Scream by Mingo Hagen is licensed under CC BY 2.0 / Added some texts to original

Master Slave

Replication

Master-Slave Replication 更新系クエリ(INSERT / UPDATE / DELETE)はマスターへ発行し、検索系クエリ(SELECT)はスレーブに発行することで負荷を分散させる。 スレーブは負荷に応じて複数台用意できる。

ReplicationINSERTUPDATEDELETE

Master Database Slave DatabaseApplication

SELECT

MySQL Replication

Driver

MySQL ReplicationDriver com.mysql.jdbc.ReplicationDriverというJDBCドライバを使う。 jdbc:mysql:replication://master,slave1,slave2…/databasejava.sql.Connection#setReadOnly(true)した場合はスレーブのホストに発行される。 JPAの場合はentityManagerのunwrapメソッドでjava.sql.Connectionを取得する。

// for EclipseLinkentityManager.unwrap(Connection.class).setReadOnly(true);

// for HibernateentityManager.unwrap(SessionImplementor.class).connection().setReadOnly(true);

User user = entityManager.find(User.class, 1L);

WRITEも 頭打ち

https://www.flickr.com/photos/mjhagen/2973212926 Scream by Mingo Hagen is licensed under CC BY 2.0 / Added some texts to original

Partitioning

Partitioning 一定のルールに従ってクエリを発行するデータベースを分ける。IDの剰余などをキーにして振り分けるのが一般的。 ジョインやソートができないのでアプリケーションの設計にも影響あり。

Master Database

Application

Master Database

ID:1,3

,5,7..

.

ID:2,4,6,8...

EclipseLink Partitioning

EclipseLink Partitioning EclipseLinkにはパーティショニング(シャーディング)の機能があり、複数のデータベースにクエリを振り分けられる。 @HashPartitioningの他に@ValuePartitioningや@RangePartitioningなど用途によって複数のパーティショニング戦略が用意されている。

@Entity@HashPartitioning(

name = "hashPartitioningById",partitionColumn = @Column(name = "id"),connectionPools = { "pool0", "pool1" },unionUnpartitionableQueries = true)

@Partitioned("hashPartitioningById")public class User {

@Id@GeneratedValue(strategy = GenerationType.TABLE)private Integer id;

@Column(nullable = false, unique = true)private String mailAddress;

}

EclipseLink Partitioning

EclipseLink Partitioning EclipseLinkにはパーティショニング(シャーディング)の機能があり、複数のデータベースにクエリを振り分けられる。 HibernateはHibernate Shardsというサブプロジェクトがあるが最近はメンテナンスが止まっている。

<persistence-unit name="freesia" transaction-type="JTA"><jta-data-source>jdbc/freesia</jta-data-source><properties>

<property name="eclipselink.connection-pool.pool0.jtaDataSource"value="jdbc/freesia0" />

<property name="eclipselink.connection-pool.pool1.jtaDataSource"value="jdbc/freesia1" />

</properties></persistence-unit>

EclipseLink Partitioning

EclipseLink Partitioning where句にpartitioningColumnで指定したカラムがある場合。 FROM User u WHERE u.id = 1

Master Database

Application

Master Database

partitioningColumnをキーにして特定のデータベースにクエリを発行する。

ID:1,3

,5,7..

.

ID:2,4,6,8...

EclipseLink Partitioning

EclipseLink Partitioning where句にpartitioningColumnで指定したカラムがない場合。 FROM User u WHERE u.name = 'nagaseyasuhito'

Master Database

Application

Master Database

ID:1,3

,5,7..

.

ID:2,4,6,8...

どのデータベースに対してもクエリが発行されない。

EclipseLink Partitioning

EclipseLink Partitioning unionUnpartitionableQueriesにtrueを設定し、where句にpartitioningColumnで指定したカラムがない場合。 FROM User u WHERE u.name = 'nagaseyasuhito'

Master Database

Application

Master Database

ID:1,3

,5,7..

.

ID:2,4,6,8...

すべてのデータベースにクエリが発行され結果は結合される。

Master Slave

Replication Partitioning

Master-Slave Replication & Partitioning もちろんこれらの合わせ技も可能。

Master Database

Application

Master Database

Replication

Slave Database

Replication

Slave Database

ID:1,3

,5,7..

.

ID:2,4,6,8...

Conclusion

https://www.flickr.com/photos/ensh/3440275790 Heart of Light by Emmanuel Huybrechts is licensed under CC BY 2.0 / Added some texts to original

Conclusion

負荷テストの目的は • システムの限界性能を知る • 高負荷時の不具合を発見する

JPAのスケールアウト戦略は • マスター/スレーブのレプリケーション • パーティショニング

https://www.flickr.com/photos/ensh/3440275790 Heart of Light by Emmanuel Huybrechts is licensed under CC BY 2.0 / Added some texts to original