Java Agent経由で簡単に混沌を注入しよう
2024-10-27 JJUG CCC 2024 Fall
Mitsunori Komatsu
Who am I
人類には早すぎるものたち
テストされなかった処理順序の
組み合わせが本番環境で
突然発生し障害につながる…
例: 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できる
例: 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
例: 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
例: 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
例: 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
例: 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
二重追加が
本番環境で発生...!
例: 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
二重追加が
本番環境で発生...!
例: 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
二重追加が
本番環境で発生...!
例: 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
二重追加が
本番環境で発生...!
例: 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
二重追加が
本番環境で発生...!
例: 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
二重追加が
本番環境で発生...!
例: 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
テストは書いたけど…
Job QueueからJobを
Dequeue して、実行&終了
(Queueから除去)するConsumer を 4 Thread 起動
Job Queue の初期化等。
Job Lease は4秒経つと失効。
Account A に $50 足す Job を
1000個投入
全ての処理が
終了するまで待機
テストは書いたけど…
Job QueueからJobを
Dequeue して、実行&終了
(Queueから除去)するConsumer を 4 Thread 起動
Job Queue の初期化等。
Job Lease は4秒経つと失効。
Account A に $50 足す Job を
1000個投入
全ての処理が
終了するまで待機
テストは書いたけど…
Job QueueからJobを
Dequeue して、実行&終了
(Queueから除去)するConsumer を 4 Thread 起動
Job Queue の初期化等。
Job Lease は4秒経つと失効。
Account A に $50 足す Job を
1000個投入
全ての処理が
終了するまで待機
テストは書いたけど…
$50追加するJobが1000 Job投入されていたので
$50000 == $50 x 1000 で期待通り
本番環境で発生したバグが再現できていない!!
今回のように問題が発生するケースが特定できていればmock等で確実に再現するテストを作成すべきだね
とはいえ、全てのコーナーケースを事前に把握するのは非常に難しいので、処理を走らせた状態で、コードに手を加えずに外部からランダムな遅延を注入して「何も起こらないか / 何か起こるか」を確認したい、ね
「コードに手を加えずに遅延を注入して
テストしたい...」
番外編
Chaos engineering?
ちょっと大袈裟すぎる...
Jepsen?
Clojureでテスト書くの辛い...
モデル検査?
モデル検査仕様書くの辛い...
実装との乖離も…
Java Agent
Javaアプリケーションの実行時に、バイトコードを変更する実装を追加できる機構.
主な用途は:
$ java -javaagent:<jarpath>[=<options>] … で指定可能
APM!!!
Byte Buddyで書くJava Agent例
main() ではなく
premain() を定義
Java bytecode manipulation library
Java bytecodeを動的に変更するためのライブラリ。�Java Agent作成にも用いられることが多い。
主なものとしては
最近、よく使われている
(例:Mockito)
Byte BuddyでJava Agent作れば「コードに手を加えずに遅延を注入してテスト」できる?
技術的にはYes. ただ、現実的にはByte BuddyでJava Agentを作って目的を達成するのは結構大変
もっと簡単に解決したい…
具体的に言うと極力Java
を書かずにすませたい
極力Javaのコードを書かずに遅延を
注入するには
Byteman rule scriptの例
変更対象のクラス名とメソッド名
下記の処理を発動するタイミング(メソッド開始時)
変更対象のクラス名とメソッド名
下記の処理を発動するタイミング(メソッド終了時)
logging等も簡単に注入できる
Java Agent経由でBytemanを有効化し、
rule scriptを注入
例:$ java -javaagent:/path/to/byteman.jar=script:/path/to/script.btm …
BMUnitを使えばUnit test内のJava annotationでscript等を指定することも可能
Chaos Dukey properties fileの例
遅延時間はランダム
クラス名、メソッド名は正規表現で指定可能
遅延発動確率は 1%
最大遅延時間は 1500ms
発動時に投げられる例外
遅延に関する設定
例外発生に関する設定
Java Agent経由でChaos Dukeyを有効化
例:
$ java -javaagent:/path/to/chaos-dukey-x.x.x-all.jar=� configFile=/path/to/chaos-dukey.properties …
現状、BMUnitのようにUnit test上できめ細かく設定する方法はない
開発予定…
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
↑
これを遅延させれば..!
二重追加が
本番環境で発生...!
Chaos Dukeyを有効化して
先程のテストを実行してみる
最大8秒間のランダムな遅延を 1% の確率で発生させる
Chaos Dukeyを有効化して再現
対応策の例:
Account DBの処理を冪等化
二重追加が2件発生...!
まとめ