[テストJPA]負荷テストとN+1問題
現在のプロジェクトの進行中に、ある程度のコードが作成され、実際の導入を実行するために負荷テストが行われた場合、次のような状況が発生しました.
(テスト-砲兵.io)
1秒あたり5回のリクエスト
試験時間:60秒
最小:21 ms、最大:739 ms
http.200回答率:100%
グループ全体をクエリーするリクエストは、1秒に少なくとも5回実行されます.
しかし、私たちが現在展開しているサービスは少なくとも1000人が使用する必要があると考えています.そのため、私たちは毎秒1000回のリクエストを送信することにしました.
1秒あたり1000回のリクエスト
試験時間:20秒(10秒後に停止)
最小:1ミリ秒、最大:9505
http.200回答率:約24%
サーバが突然混乱し、平均応答時間は4秒になりました.簡単なお願いだったので、変なところを見つけて、どこが問題なのか探し始めました.
まず疑うのはAWSサーバーです.
フリータイヤのEC 2を使用しているので、2 GBのRAMでは計算できないと推測されます.
(監視指標)
検証の結果、CPUが使用されていないのは7%で、ネットワークの問題ではありません.これは、サーバ上のコードに異常が発生し、Debugを開始して検索することを意味します.
複数の人が要求すると、GET関数が急に無理になり、作業効率が低下する可能性があります.
しかし、私が使っている方法はJPAのRepositoryの基本関数で、唯一疑わしいのはN+1です.
1つの要求では、約7〜10回のクエリがあり、一般的なN+1の問題が発生した.
N+1は、JPAがEntity情報をマッピングする際に発生する問題である.
1:N関連関係の情報からN個の情報を取得するために、再びselectクエリが発行され、N+1と呼ばれる.
JPAが関連関係の情報を取得する際にN+1の問題が発生した原因は以下の通りである.
group(1)-people(N)関係で1つのグループに複数の関連関係がある場合、group情報をインポートする際にpeopleに関する情報も必要です.
このとき、JPAがグループに関する情報をインポートすると、関連関係にあるユーザがプロキシオブジェクトとしてインポートされます.
つまり、次の情報が得られます.
JPAはまずグループに関する情報を提供する.
selectクエリーが表示され、proxy(任意のエージェントオブジェクト)を使用してgroupオブジェクトが作成され、Entity Managerによってグループ内の情報とユーザのリストが保存されます.
個人情報を取得するには、関連する個人を選択してN個のクエリーを追加します.
それがJPAがEntityの管理方法を知っている理由です
JPAが関連関係から情報を取得する方法は2つあります. FetchType.Lazy FetchType.Eager 2つのFetch typeの違いは、関連関係をクエリーするタイミングによって異なります.
Lazyは,Nのオブジェクト情報を1:N関係でエージェントを用いて置き換え,実際にデータが必要なときにデータベースに要求してデータを取得する.逆に、関連関係の情報を1つの時点でインポートします.
つまり、Fetch TypeがEagerであれば、Entityのクエリ時にすべての情報が1つの時点で取得されます.ここでは一時的に概念を混同し、
1つの時点でインポートがクエリーにインポートされると勘違いしています.
Eagerの場合、1つの時点でインポートするのは正しいですが、1:Nの場合、結果は複数のselectで結合されます.
そこで,N+1に関する問題はFetchタイプがEagerであるかLazyであるかに関係なく,解決策を再検討することにした.
では、JPAのN+1問題をどう防ぐのか.
JPAは、ORM技術の説明に従ってオブジェクト向けのコードを要求し、独自のSQL形式でデータをマッピングする.これは、JPAが要求したオブジェクト情報をチェックし、JDBCを介してSQLに情報を要求することを意味します.
例)
(出典:JPAという組織ブログ)
JPAは、私たちが要求したオブジェクト情報に基づいてJDBCに要求を発行するので、別の方法でJDBCに要求を発行することで解決することができる.つまり、SQL文のjoinを使用することで、fetch joinというクエリからオブジェクトのすべての情報を取得できます.
*注意:fetchjoinではなく通常のjoinを使用する場合、JPAはJDBCに複数のselectを要求し、eneityManager内部joinによって結果を返します.つまり,N+1問題は解決できない.
Lazy、EagerでN+1問題を解決できると思ったのは私の錯覚です.すでに導入が完了しており、実際のサービスでこのような過負荷が発生した場合は、最悪です.
また、EagerとLazyに関する記事もいくつか閲覧しました.
~ToOneの場合、デフォルト値はEagerです.
~ToManyのデフォルト値はLazyです.
以上の結果から,~ToManyは関連関係の所有者ではない可能性が高いため,デフォルト値はLazyであることが分かる.
SQLで、関連関係の所有者でない場合は、selectを使用して関連関係のエンティティを一度にインポートできないため、上記の設定が設定されている可能性があります.
+追加する内容や不足点があれば、メッセージを残してください!:)
(テスト-砲兵.io)
1秒あたり5回のリクエスト
試験時間:60秒
最小:21 ms、最大:739 ms
http.200回答率:100%
グループ全体をクエリーするリクエストは、1秒に少なくとも5回実行されます.
しかし、私たちが現在展開しているサービスは少なくとも1000人が使用する必要があると考えています.そのため、私たちは毎秒1000回のリクエストを送信することにしました.
1秒あたり1000回のリクエスト
試験時間:20秒(10秒後に停止)
最小:1ミリ秒、最大:9505
http.200回答率:約24%
サーバが突然混乱し、平均応答時間は4秒になりました.簡単なお願いだったので、変なところを見つけて、どこが問題なのか探し始めました.
🤔 サーバの問題
まず疑うのはAWSサーバーです.
フリータイヤのEC 2を使用しているので、2 GBのRAMでは計算できないと推測されます.
(監視指標)
検証の結果、CPUが使用されていないのは7%で、ネットワークの問題ではありません.これは、サーバ上のコードに異常が発生し、Debugを開始して検索することを意味します.
😢 N+1の問題が発生しました。
複数の人が要求すると、GET関数が急に無理になり、作業効率が低下する可能性があります.
しかし、私が使っている方法はJPAのRepositoryの基本関数で、唯一疑わしいのはN+1です.
1つの要求では、約7〜10回のクエリがあり、一般的なN+1の問題が発生した.
🔥 N+1問題は
N+1は、JPAがEntity情報をマッピングする際に発生する問題である.
1:N関連関係の情報からN個の情報を取得するために、再びselectクエリが発行され、N+1と呼ばれる.
JPAが関連関係の情報を取得する際にN+1の問題が発生した原因は以下の通りである.
group(1)-people(N)関係で1つのグループに複数の関連関係がある場合、group情報をインポートする際にpeopleに関する情報も必要です.
このとき、JPAがグループに関する情報をインポートすると、関連関係にあるユーザがプロキシオブジェクトとしてインポートされます.
つまり、次の情報が得られます.
JPAはまずグループに関する情報を提供する.
selectクエリーが表示され、proxy(任意のエージェントオブジェクト)を使用してgroupオブジェクトが作成され、Entity Managerによってグループ内の情報とユーザのリストが保存されます.
個人情報を取得するには、関連する個人を選択してN個のクエリーを追加します.
🤔 どうしてN+1が現れるのですか?
それがJPAがEntityの管理方法を知っている理由です
JPAが関連関係から情報を取得する方法は2つあります.
Lazyは,Nのオブジェクト情報を1:N関係でエージェントを用いて置き換え,実際にデータが必要なときにデータベースに要求してデータを取得する.逆に、関連関係の情報を1つの時点でインポートします.
つまり、Fetch TypeがEagerであれば、Entityのクエリ時にすべての情報が1つの時点で取得されます.ここでは一時的に概念を混同し、
1つの時点でインポートがクエリーにインポートされると勘違いしています.
Eagerの場合、1つの時点でインポートするのは正しいですが、1:Nの場合、結果は複数のselectで結合されます.
そこで,N+1に関する問題はFetchタイプがEagerであるかLazyであるかに関係なく,解決策を再検討することにした.
🤗 N+1トラブルシューティング方法。
では、JPAのN+1問題をどう防ぐのか.
JPAは、ORM技術の説明に従ってオブジェクト向けのコードを要求し、独自のSQL形式でデータをマッピングする.これは、JPAが要求したオブジェクト情報をチェックし、JDBCを介してSQLに情報を要求することを意味します.
例)
(出典:JPAという組織ブログ)
JPAは、私たちが要求したオブジェクト情報に基づいてJDBCに要求を発行するので、別の方法でJDBCに要求を発行することで解決することができる.つまり、SQL文のjoinを使用することで、fetch joinというクエリからオブジェクトのすべての情報を取得できます.
@Repository
public interface ChallengeRepository extends JpaRepository<Challenge, Long> {
@Query("select distinct c from challenge c join fetch c.userChallengeList")
Optional<Challenge> findById(Long id);
...
}
fetch joinメソッドでは、@Query宣言によりSQLを直接作成し、クエリーをJDBCに転送できます.その後、挑戦中のすべてのユーザーをjoinに入れ、クエリーから情報を取得できます.*注意:fetchjoinではなく通常のjoinを使用する場合、JPAはJDBCに複数のselectを要求し、eneityManager内部joinによって結果を返します.つまり,N+1問題は解決できない.
Lazy、EagerでN+1問題を解決できると思ったのは私の錯覚です.すでに導入が完了しており、実際のサービスでこのような過負荷が発生した場合は、最悪です.
また、EagerとLazyに関する記事もいくつか閲覧しました.
~ToOneの場合、デフォルト値はEagerです.
~ToManyのデフォルト値はLazyです.
以上の結果から,~ToManyは関連関係の所有者ではない可能性が高いため,デフォルト値はLazyであることが分かる.
SQLで、関連関係の所有者でない場合は、selectを使用して関連関係のエンティティを一度にインポートできないため、上記の設定が設定されている可能性があります.
+追加する内容や不足点があれば、メッセージを残してください!:)
Reference
この問題について([テストJPA]負荷テストとN+1問題), 我々は、より多くの情報をここで見つけました https://velog.io/@qf9ar8nv/JPA-N1-문제-그리고テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol