1 of 33

Java Agent経由で簡単に混沌を注入しよ

2024-10-27 JJUG CCC 2024 Fall

Mitsunori Komatsu

2 of 33

Who am I

  • Mitsunori Komatsu @Scalar, Inc.
  • GitHub: komamitsu
    • msgpack-java
      • jackson-dataformat-msgpack
    • Fluency (Data ingestion logger to Fluentd)
    • Chaos Dukey

3 of 33

人類には早すぎるものたち

  • マルチスレッド
  • 分散システム
  • 複数DB
  • マイクロサービス� :

テストされなかった処理順序の

組み合わせが本番環境で

突然発生し障害につながる…

4 of 33

例: Lease-based job queue

Producer 1

Producer 2

Producer 3

Job A

Job C

Job B

Job A

Job B

Job C

Consumer 1

Consumer 2

Dequeue

Producer 1

Producer 2

Producer 3

Job A

Job C

Job B

Job A

Job B

Job C

Consumer 1

Consumer 2

Dequeue

Job A

Job AはConsumer 1に

渡されたので一定時間

他のConsumerには視えない

Queue

Queue

Consumer 1が失敗しても

Lease失効後、別の

ConsumerがDequeueできる

5 of 33

例: Lease-based job queue (正常系)

Worker 1

Distributed

Queue

Dequeue a job

Account DB

Job X

- Incr Account A by $50

Worker 2

Account A: $200

00:01:30

No Jobs

Dequeue a job

Account A: $250

Job X が一度のみ

実行されたので

期待通り $250

Job X

- Account A に $50 追加

- Owner は Worker 1

- Leaseは 00:01:30 に失効

Job X

- Account A に $50 追加

- Leaseは 00:01:30 に失効

Increment Account A by $50

Finish Job X

No available job

Account A: $250

6 of 33

例: Lease-based job queue (正常系)

Worker 1

Distributed

Queue

Dequeue a job

Account DB

Job X

- Incr Account A by $50

Worker 2

Account A: $200

00:01:30

No Jobs

Dequeue a job

Account A: $250

Job X が一度のみ

実行されたので

期待通り $250

Job X

- Account A に $50 追加

- Owner は Worker 1

- Leaseは 00:01:30 に失効

Job X

- Account A に $50 追加

- Leaseは 00:01:30 に失効

Increment Account A by $50

Finish Job X

No available job

Account A: $250

7 of 33

例: Lease-based job queue (正常系)

Worker 1

Distributed

Queue

Dequeue a job

Account DB

Job X

- Incr Account A by $50

Worker 2

Account A: $200

00:01:30

No Jobs

Dequeue a job

Account A: $250

Job X が一度のみ

実行されたので

期待通り $250

Job X

- Account A に $50 追加

- Owner は Worker 1

- Leaseは 00:01:30 に失効

Job X

- Account A に $50 追加

- Leaseは 00:01:30 に失効

Increment Account A by $50

Finish Job X

No available job

Account A: $250

8 of 33

例: Lease-based job queue (正常系)

Worker 1

Distributed

Queue

Dequeue a job

Account DB

Job X

- Incr Account A by $50

Worker 2

Account A: $200

00:01:30

No Jobs

Dequeue a job

Account A: $250

Job X が一度のみ

実行されたので

期待通り $250

Job X

- Account A に $50 追加

- Owner は Worker 1

- Leaseは 00:01:30 に失効

Job X

- Account A に $50 追加

- Leaseは 00:01:30 に失効

Increment Account A by $50

Finish Job X

No available job

Account A: $250

9 of 33

例: Lease-based job queue (stuck & lease失効)

Worker 1

Distributed

Queue

Dequeue a job

Account DB

Job X

- Incr Account A by $50

Worker 2

Increment Account A by $50

Account A: $200

00:01:30

Job Xのleaseが失効

Job X

- Incr Account A by $50

Dequeue a job

Increment Account A by $50

Account A: $250

Account A: $300

00:02:00

Job X

- Account A に $50 追加

- Owner は Worker 1

- Leaseは 00:01:30 に失効

Job X

- Account A に $50 追加

- Owner は Worker 2

- Lease は 00:02:00 に失効

Job X

- Account A に $50 追加

- Leaseは 00:01:30 に失効

Job X

- Account A に $50 追加

- Lease は 00:02:00 に失効

何らかの理由

(e.g., GC)により

処理が停止・遅延

Finish Job X

Finish Job X

二重追加が

本番環境で発生...!

10 of 33

例: Lease-based job queue (stuck & lease失効)

Worker 1

Distributed

Queue

Dequeue a job

Account DB

Job X

- Incr Account A by $50

Worker 2

Increment Account A by $50

Account A: $200

00:01:30

Job Xのleaseが失効

Job X

- Incr Account A by $50

Dequeue a job

Increment Account A by $50

Account A: $250

Account A: $300

00:02:00

Job X

- Account A に $50 追加

- Owner は Worker 1

- Leaseは 00:01:30 に失効

Job X

- Account A に $50 追加

- Owner は Worker 2

- Lease は 00:02:00 に失効

Job X

- Account A に $50 追加

- Leaseは 00:01:30 に失効

Job X

- Account A に $50 追加

- Lease は 00:02:00 に失効

何らかの理由

(e.g., GC)により

処理が停止・遅延

Finish Job X

Finish Job X

二重追加が

本番環境で発生...!

11 of 33

例: Lease-based job queue (stuck & lease失効)

Worker 1

Distributed

Queue

Dequeue a job

Account DB

Job X

- Incr Account A by $50

Worker 2

Increment Account A by $50

Account A: $200

00:01:30

Job Xのleaseが失効

Job X

- Incr Account A by $50

Dequeue a job

Increment Account A by $50

Account A: $250

Account A: $300

00:02:00

Job X

- Account A に $50 追加

- Owner は Worker 1

- Leaseは 00:01:30 に失効

Job X

- Account A に $50 追加

- Owner は Worker 2

- Lease は 00:02:00 に失効

Job X

- Account A に $50 追加

- Leaseは 00:01:30 に失効

Job X

- Account A に $50 追加

- Lease は 00:02:00 に失効

何らかの理由

(e.g., GC)により

処理が停止・遅延

Finish Job X

Finish Job X

二重追加が

本番環境で発生...!

12 of 33

例: Lease-based job queue (stuck & lease失効)

Worker 1

Distributed

Queue

Dequeue a job

Account DB

Job X

- Incr Account A by $50

Worker 2

Increment Account A by $50

Account A: $200

00:01:30

Job Xのleaseが失効

Job X

- Incr Account A by $50

Dequeue a job

Increment Account A by $50

Account A: $250

Account A: $300

00:02:00

Job X

- Account A に $50 追加

- Owner は Worker 1

- Leaseは 00:01:30 に失効

Job X

- Account A に $50 追加

- Owner は Worker 2

- Lease は 00:02:00 に失効

Job X

- Account A に $50 追加

- Leaseは 00:01:30 に失効

Job X

- Account A に $50 追加

- Lease は 00:02:00 に失効

何らかの理由

(e.g., GC)により

処理が停止・遅延

Finish Job X

Finish Job X

二重追加が

本番環境で発生...!

13 of 33

例: Lease-based job queue (stuck & lease失効)

Worker 1

Distributed

Queue

Dequeue a job

Account DB

Job X

- Incr Account A by $50

Worker 2

Increment Account A by $50

Account A: $200

00:01:30

Job Xのleaseが失効

Job X

- Incr Account A by $50

Dequeue a job

Increment Account A by $50

Account A: $250

Account A: $300

00:02:00

Job X

- Account A に $50 追加

- Owner は Worker 1

- Leaseは 00:01:30 に失効

Job X

- Account A に $50 追加

- Owner は Worker 2

- Lease は 00:02:00 に失効

Job X

- Account A に $50 追加

- Leaseは 00:01:30 に失効

Job X

- Account A に $50 追加

- Lease は 00:02:00 に失効

何らかの理由

(e.g., GC)により

処理が停止・遅延

Finish Job X

Finish Job X

二重追加が

本番環境で発生...!

14 of 33

例: Lease-based job queue (stuck & lease失効)

Worker 1

Distributed

Queue

Dequeue a job

Account DB

Job X

- Incr Account A by $50

Worker 2

Increment Account A by $50

Account A: $200

00:01:30

Job Xのleaseが失効

Job X

- Incr Account A by $50

Dequeue a job

Increment Account A by $50

Account A: $250

Account A: $300

00:02:00

Job X

- Account A に $50 追加

- Owner は Worker 1

- Leaseは 00:01:30 に失効

Job X

- Account A に $50 追加

- Owner は Worker 2

- Lease は 00:02:00 に失効

Job X

- Account A に $50 追加

- Leaseは 00:01:30 に失効

Job X

- Account A に $50 追加

- Lease は 00:02:00 に失効

何らかの理由

(e.g., GC)により

処理が停止・遅延

Finish Job X

Finish Job X

二重追加が

本番環境で発生...!

15 of 33

例: Lease-based job queue (stuck & lease失効)

Worker 1

Distributed

Queue

Dequeue a job

Account DB

Job X

- Incr Account A by $50

Worker 2

Increment Account A by $50

Account A: $200

00:01:30

Job Xのleaseが失効

Job X

- Incr Account A by $50

Dequeue a job

Increment Account A by $50

Account A: $250

Account A: $300

00:02:00

二重追加が

本番環境で発生...!

Job X

- Account A に $50 追加

- Owner は Worker 1

- Leaseは 00:01:30 に失効

Job X

- Account A に $50 追加

- Owner は Worker 2

- Lease は 00:02:00 に失効

Job X

- Account A に $50 追加

- Leaseは 00:01:30 に失効

Job X

- Account A に $50 追加

- Lease は 00:02:00 に失効

何らかの理由

(e.g., GC)により

処理が停止・遅延

Finish Job X

Finish Job X

16 of 33

テストは書いたけど…

Job QueueからJobを

Dequeue して、実行&終了

(Queueから除去)するConsumer を 4 Thread 起動

Job Queue の初期化等。

Job Lease は4秒経つと失効。

Account A に $50 足す Job を

1000個投入

全ての処理が

終了するまで待機

17 of 33

テストは書いたけど…

Job QueueからJobを

Dequeue して、実行&終了

(Queueから除去)するConsumer を 4 Thread 起動

Job Queue の初期化等。

Job Lease は4秒経つと失効。

Account A に $50 足す Job を

1000個投入

全ての処理が

終了するまで待機

18 of 33

テストは書いたけど…

Job QueueからJobを

Dequeue して、実行&終了

(Queueから除去)するConsumer を 4 Thread 起動

Job Queue の初期化等。

Job Lease は4秒経つと失効。

Account A に $50 足す Job を

1000個投入

全ての処理が

終了するまで待機

19 of 33

テストは書いたけど…

$50追加するJobが1000 Job投入されていたので

$50000 == $50 x 1000 で期待通り

本番環境で発生したバグが再現できていない!!

今回のように問題が発生するケースが特定できていればmock等で確実に再現するテストを作成すべきだね

とはいえ、全てのコーナーケースを事前に把握するのは非常に難しいので、処理を走らせた状態で、コードに手を加えずに外部からランダムな遅延を注入して「何も起こらないか / 何か起こるか」を確認したい、ね

20 of 33

コードに手を加えずに遅延を注入して

 テストしたい...

番外編

Chaos engineering?

ちょっと大袈裟すぎる...

Jepsen?

Clojureでテスト書くの辛い...

モデル検査?

モデル検査仕様書くの辛い...

実装との乖離も…

21 of 33

Java Agent

Javaアプリケーションの実行時に、バイトコードを変更する実装を追加できる機構.

主な用途は:

  • Monitoring
  • Tracing
  • Profiling
  • Logging

$ java -javaagent:<jarpath>[=<options>] … で指定可能

APM!!!

22 of 33

Byte Buddyで書くJava Agent例

main() ではなく

premain() を定義

23 of 33

Java bytecode manipulation library

Java bytecodeを動的に変更するためのライブラリ。�Java Agent作成にも用いられることが多い。

主なものとしては

  • ASM
  • Cglib
  • Javasist
  • Byte Buddy (内部でASM利用)

最近、よく使われている

(例:Mockito)

24 of 33

Byte BuddyでJava Agent作れば「コードに手を加えずに遅延を注入してテスト」できる?

技術的にはYes. ただ、現実的にはByte BuddyでJava Agentを作って目的を達成するのは結構大変

もっと簡単に解決したい…

具体的に言うと極力Java

を書かずにすませたい

25 of 33

極力Javaのコードを書かずに遅延を

注入するには

  • Byteman
    • https://byteman.jboss.org/
    • 強力。Byteman rule scriptを書くことで何でもできる
    • 内部的にASMを使用

  • Chaos-Dukey
    • https://github.com/komamitsu/chaos-dukey
    • 簡単。遅延/故障(==例外を投げる)注入に特化
    • 内部的にByte Buddyを使用

26 of 33

Byteman rule scriptの例

変更対象のクラス名とメソッド名

下記の処理を発動するタイミング(メソッド開始時)

変更対象のクラス名とメソッド名

下記の処理を発動するタイミング(メソッド終了時)

logging等も簡単に注入できる

27 of 33

Java Agent経由でBytemanを有効化し、

rule scriptを注入

例:$ java -javaagent:/path/to/byteman.jar=script:/path/to/script.btm …

BMUnitを使えばUnit test内のJava annotationでscript等を指定することも可能

28 of 33

Chaos Dukey properties fileの例

遅延時間はランダム

クラス名、メソッド名は正規表現で指定可能

遅延発動確率は 1%

最大遅延時間は 1500ms

発動時に投げられる例外

遅延に関する設定

例外発生に関する設定

29 of 33

Java Agent経由でChaos Dukeyを有効化

例:

$ java -javaagent:/path/to/chaos-dukey-x.x.x-all.jar=� configFile=/path/to/chaos-dukey.properties

現状、BMUnitのようにUnit test上できめ細かく設定する方法はない

開発予定…

30 of 33

Chaos Dukeyを有効化して

先程のテストを実行してみる

Worker 1

Distributed

Queue

Dequeue a job

Account DB

Job X

- Incr Account A by $50

Worker 2

Increment Account A by $50

Account A: $200

00:01:30

Job Xのleaseが失効

Job X

- Incr Account A by $50

Dequeue a job

Increment Account A by $50

Account A: $250

Account A: $300

00:02:00

Job X

- Account A に $50 追加

- Owner は Worker 1

- Leaseは 00:01:30 に失効

Job X

- Account A に $50 追加

- Owner は Worker 2

- Lease は 00:02:00 に失効

Job X

- Account A に $50 追加

- Leaseは 00:01:30 に失効

Job X

- Account A に $50 追加

- Lease は 00:02:00 に失効

何らかの理由(e.g., GC)により

処理が停止・遅延

Finish Job X

Finish Job X

これを遅延させれば..!

二重追加が

本番環境で発生...!

31 of 33

Chaos Dukeyを有効化して

先程のテストを実行してみる

最大8秒間のランダムな遅延を 1% の確率で発生させる

32 of 33

Chaos Dukeyを有効化して再現

対応策の例:

Account DBの処理を冪等化

二重追加が2件発生...!

33 of 33

まとめ

  • 処理順序の組み合わせが膨大である場合、全てのケースを把握してmock等を使ってテストするのは難しい
  • Java Agentを使うと、コードを変更せずに混沌(遅延や故障)を注入できるので便利
  • 複雑なことをやりたいならByteman (https://byteman.jboss.org/)、簡単にランダムな遅延や故障を注入したいならChaos Dukey (https://github.com/komamitsu/chaos-dukey) がおすすめ