確認
念の為確認というか前提と論点整理なのですが
- アプリケーションサーバ 1 ノード、DBサーバ 1 ノード構成
(※アプリ&DB同居構成でも同じ)
- いわゆる連番ID
- アプリケーションサーバ側でのID採番
という認識でよいでしょうか?
複数動くと主キーが被りエラーになります
とありますが、現状実際にエラーになっているわけではなく「エラーにならない(ような実装が出来ている)ことを確認したい」でいいですよね?
余計な話
正直にいうと特別な理由がない限りはこの構成にしない方がよいことが多いので
Java側のsynchronizedではなくDB側のロック/トランザクションで解決した方がよいのでは?とか
ただのオートインクリメントならばDB側での採番が妥当では?とか
多ノードDBまで見るならばそもそもID体系を見直してどこで採番しても問題ないようにする(UUID/ULID、その他独自実装etc)とか
余裕があれば色々ぐぐったりして調べてみると役に立つかもしれません
本題
マルチスレッド/排他制御のテストは本質的に難しく、「あらゆるシーンで完璧な過不足のないテスト」などは自分も用意できないですが
例えば今回のような場合はこのような検証を行うと思います。
- 意図的に競合が起きるように手を加えながら同時実行し、
クリティカル箇所の処理順序に問題ないかを確認する構造的な検証
- 同時アクセスorメソッド呼び出しを多数回実施するやや実践的な検証
マルチプロセス/マルチスレッド
質問文の試したことで『登録メソッドを呼ぶプログラムをマルチプロセスで動かす』とありますが
おそらく質問者さんも気づかれているとおり、これは意味がありません。
こういう場合は「テストドライバとして、『複数スレッドを自前で立ち上げて同時実行させる』ようなコードを書く」が一番手っ取り早いです。
アプリケーションサーバが1ノード前提なので、アプリケーションのプロセスを複数立ち上げてはいけません。
tomcat内部では複数スレッドでそれぞれリクエストを受け付けて同時に当該メソッドを実行することになるので
それを模すために自前で複数スレッド立ち上げるのが必要となります。
そうではなくtomcatを立ち上げたうえで、リクエストを送る方をマルチプロセスで動かすのももちろん効果があります。
質問文の試したことで問題とされている『登録テストに同時にたどり着いてもその先でスレッドに分割され優先順位がつけられるので厳密に同時ではない』というのがちょっとよく分からないのですが、
これは「(最終的なクリティカルセクション到達が)同時かもしれないし同時ではないかもしれないがそれを確認はできないので、ちゃんと検証になっているかどうかも分からない」という意図でしょうか?
それであってるのならば、基本的には「多数回実施して検証とする」が一般的なアプローチです。
「多数回しても十分かどうか分からんやろ!」と言われそうですが、例えば
「わざとsynchronizedを外したうえで同条件で実施してみて、エラーが再現するのに十分かチェックする」などで適切な実施条件を手探りで検討もできます。
try/catchで同時実行エラーかを標準出力しつつ最終的に成功回数/発生した原因ごとエラー回数を見るとかで、どのくらいの時間(回数分)実施したら十分かが分かるでしょう。
①意図的に競合を起こす
まずは、そもそも処理順序や構造として正しいかの検証をします。
例として
- 当該メソッドのソースコード書き換え
- クリティカルセクションに入った直後にsleep処理を入れる
- いろんなタイミングで「スレッド番号+処理内容」をコンソール出力
- テストドライバのコード
- 当該メソッドを呼び出すようなスレッドを複数生成
- 同時にスレッド開始
こんな感じで実行すると、おそらく以下のような出力になるでしょう
- ほぼ同時に、クリティカルセクションに入る前の表示がコンソールに出力
- 先に入った方のスレッドでsleep開始の表示がコンソールに出力
- 先に入った方でsleep終了の表示
- 先に入った方で本命の処理が進み、クリティカルセクションを抜けるまでのすべての表示が続く
- この間、待っている方は何も処理が進まない
- 後に入った方のスレッドでsleep開始の表示がコンソールに出力
- (以下略)
ここでクリティカルセクション内の出力が複数スレッドで入り乱れて表示されるようであれば、なんらかの実装ミスや前提条件の勘違いなどがあることになります。
メソッド自体を書き換えて余計な処理を追加するため、これ単体では検証として十分ではないのですが
これが通らないのはそもそもおかしいので、必要条件になります。
※IDEのデバッガを使っても同じことはできると思いますが、出力がないとぱっと見わかりにくいとか予備知識ないと正確な手順が難しいとかで、コードを弄らなくて良い分を差し引いても一長一短ではないかと思っています
②多数回実施
これは前述のスレッドを自前で立ち上げてメソッド呼び出しする方でも、tomcatを立ち上げてリクエストを複数プロセスから投げてservlet経由で実行する方でも、どちらでもよいのですが
開発当初であればメソッド呼び出し、最終的にはservlet経由で検証することになると思います。
こちらは勿論前述のような本体コードへ手を加える必要はありません。
実施条件については「マルチプロセス/マルチスレッド」の所で示した通りですが
基本的には何パターンかでやることになると思います。
servlet経由の方は、どちらかというと非機能要件として性能テストの一貫で実施されるかもしれません。
負荷テストツールなんかを使ってもいいでしょう。