Spring JPAを使用したアプリケーション開発において、パフォーマンス診断と実行SQLの可視化は、本番環境で発生するボトルネック特定に不可欠なスキルです。本記事では、spring.jpa.show-sqlやformat_sqlによる基本的なSQLログ出力から、Hibernate統計情報、DataSource-Proxy、P6Spyを活用した高度なクエリ分析、N+1問題の検出方法、スロークエリの特定とEXPLAIN ANALYZEの使い方、そしてインデックス設計の基本まで、Spring JPAパフォーマンス診断の実践的なテクニックを体系的に解説します。
実行環境と前提条件#
本記事の内容を実践するにあたり、以下の環境を前提としています。
| 項目 |
バージョン・要件 |
| Java |
17以上 |
| Spring Boot |
3.4.x |
| Spring Data JPA |
3.4.x(Spring Boot Starterに含まれる) |
| Hibernate |
6.6.x(Spring Data JPAに含まれる) |
| データベース |
PostgreSQL 16 / MySQL 8.x / H2 Database |
| ビルドツール |
Maven または Gradle |
| IDE |
VS Code または IntelliJ IDEA |
事前に以下の準備を完了してください。
- JDK 17以上のインストール
- Spring Boot + Spring Data JPAプロジェクトの基本構成
- Maven または Gradle によるプロジェクトビルド環境
最も基本的なSQLの可視化方法は、Spring BootとHibernateの標準設定を利用することです。
基本設定(application.yml)#
1
2
3
4
5
6
|
spring:
jpa:
show-sql: true
properties:
hibernate:
format_sql: true
|
この設定により、実行されるSQLがコンソールに整形されて出力されます。
出力例#
1
2
3
4
5
6
7
8
9
10
|
Hibernate:
select
u1_0.id,
u1_0.email,
u1_0.name,
u1_0.created_at
from
users u1_0
where
u1_0.id=?
|
バインドパラメータの表示#
SQLの?プレースホルダに実際にバインドされる値を確認するには、追加のログ設定が必要です。
1
2
3
4
|
logging:
level:
org.hibernate.SQL: DEBUG
org.hibernate.orm.jdbc.bind: TRACE
|
この設定により、以下のようにバインドパラメータが出力されます。
DEBUG org.hibernate.SQL - select u1_0.id,u1_0.email,u1_0.name from users u1_0 where u1_0.id=?
TRACE org.hibernate.orm.jdbc.bind - binding parameter (1:BIGINT) <- [1]
SQLコメントの追加#
どのメソッドからSQLが発行されたかを追跡するには、SQLコメントを有効化します。
1
2
3
4
5
|
spring:
jpa:
properties:
hibernate:
use_sql_comments: true
|
これにより、生成されたSQLにJPQLやメソッド名がコメントとして追加されます。
1
2
3
4
5
6
7
8
|
/* select u from User u where u.id = :id */ select
u1_0.id,
u1_0.email,
u1_0.name
from
users u1_0
where
u1_0.id=?
|
設定の優先順位と注意点#
| 設定 |
説明 |
本番環境 |
spring.jpa.show-sql |
標準出力へのSQL出力 |
無効推奨 |
hibernate.format_sql |
SQLの整形 |
無効推奨 |
org.hibernate.SQL=DEBUG |
ログフレームワーク経由の出力 |
条件付きで有効 |
hibernate.use_sql_comments |
SQLへのコメント追加 |
無効推奨 |
本番環境ではshow-sqlは無効にし、必要に応じてログレベルで制御することを推奨します。
Hibernate統計情報(hibernate.generate_statistics)の活用#
Hibernateの統計情報機能を有効にすると、セッション単位でのクエリ実行回数、キャッシュヒット率、エンティティのロード数など、詳細なパフォーマンスメトリクスを取得できます。
統計情報の有効化#
1
2
3
4
5
6
7
8
9
10
|
spring:
jpa:
properties:
hibernate:
generate_statistics: true
logging:
level:
org.hibernate.stat: DEBUG
org.hibernate.engine.internal.StatisticalLoggingSessionEventListener: DEBUG
|
統計情報の出力例#
Session Metrics {
1500000 nanoseconds spent acquiring 1 JDBC connections;
0 nanoseconds spent releasing 0 JDBC connections;
2500000 nanoseconds spent preparing 3 JDBC statements;
15000000 nanoseconds spent executing 3 JDBC statements;
0 nanoseconds spent executing 0 JDBC batches;
0 nanoseconds spent performing 0 L2C puts;
0 nanoseconds spent performing 0 L2C hits;
0 nanoseconds spent performing 0 L2C misses;
5000000 nanoseconds spent executing 1 flushes (flushing a total of 10 entities and 5 collections);
0 nanoseconds spent executing 0 partial-flushes (flushing a total of 0 entities and 0 collections)
}
プログラムからの統計情報取得#
統計情報はプログラムからも取得可能です。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
package com.example.demo.service;
import org.hibernate.SessionFactory;
import org.hibernate.stat.Statistics;
import org.springframework.stereotype.Service;
import jakarta.persistence.EntityManagerFactory;
@Service
public class StatisticsService {
private final Statistics statistics;
public StatisticsService(EntityManagerFactory entityManagerFactory) {
SessionFactory sessionFactory = entityManagerFactory.unwrap(SessionFactory.class);
this.statistics = sessionFactory.getStatistics();
this.statistics.setStatisticsEnabled(true);
}
public void printStatistics() {
System.out.println("=== Hibernate Statistics ===");
System.out.println("Query execution count: " + statistics.getQueryExecutionCount());
System.out.println("Query execution max time: " + statistics.getQueryExecutionMaxTime() + "ms");
System.out.println("Slowest query: " + statistics.getQueryExecutionMaxTimeQueryString());
System.out.println("Entity load count: " + statistics.getEntityLoadCount());
System.out.println("Entity fetch count: " + statistics.getEntityFetchCount());
System.out.println("Collection load count: " + statistics.getCollectionLoadCount());
System.out.println("Collection fetch count: " + statistics.getCollectionFetchCount());
System.out.println("Second level cache hit count: " + statistics.getSecondLevelCacheHitCount());
System.out.println("Second level cache miss count: " + statistics.getSecondLevelCacheMissCount());
}
public void clearStatistics() {
statistics.clear();
}
}
|
統計情報で確認すべき重要なメトリクス#
| メトリクス |
説明 |
注意すべき値 |
getQueryExecutionCount() |
実行されたクエリ総数 |
想定より多い場合はN+1の可能性 |
getQueryExecutionMaxTime() |
最も遅いクエリの実行時間 |
100ms以上は要調査 |
getEntityFetchCount() |
フェッチされたエンティティ数 |
ロード数との乖離が大きい場合は注意 |
getSecondLevelCacheHitCount() |
2次キャッシュヒット数 |
低い場合はキャッシュ設定見直し |
DataSource-ProxyによるSQL/実行時間ログの取得#
DataSource-Proxyは、JDBCレベルでのSQL実行を詳細にログ出力できるライブラリです。実行時間、バインドパラメータ、バッチ処理の詳細を確認できます。
依存関係の追加#
1
2
3
4
5
6
|
<!-- Maven -->
<dependency>
<groupId>net.ttddyy</groupId>
<artifactId>datasource-proxy</artifactId>
<version>1.11.0</version>
</dependency>
|
1
2
|
// Gradle
implementation 'net.ttddyy:datasource-proxy:1.11.0'
|
DataSourceのプロキシ設定#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
package com.example.demo.config;
import net.ttddyy.dsproxy.listener.logging.SLF4JLogLevel;
import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import javax.sql.DataSource;
import java.util.concurrent.TimeUnit;
@Configuration
public class DataSourceProxyConfig {
@Autowired
private DataSourceProperties dataSourceProperties;
@Bean
@Primary
public DataSource dataSource() {
DataSource originalDataSource = dataSourceProperties
.initializeDataSourceBuilder()
.build();
return ProxyDataSourceBuilder.create(originalDataSource)
.name("QueryLoggingProxy")
.logQueryBySlf4j(SLF4JLogLevel.INFO)
.logSlowQueryBySlf4j(100, TimeUnit.MILLISECONDS, SLF4JLogLevel.WARN)
.countQuery()
.multiline()
.build();
}
}
|
出力例#
Name:QueryLoggingProxy, Connection:1, Time:15, Success:True
Type:Prepared, Batch:False, QuerySize:1, BatchSize:0
Query:["select u1_0.id,u1_0.email,u1_0.name from users u1_0 where u1_0.id=?"]
Params:[(1)]
スロークエリの自動検出#
上記の設定では、100ミリ秒を超えるクエリが自動的にWARNレベルでログ出力されます。
WARN - Name:QueryLoggingProxy, Connection:3, Time:250, Success:True
Type:Prepared, Batch:False, QuerySize:1, BatchSize:0
Query:["select * from large_table where status = ? order by created_at desc"]
Params:[('ACTIVE')]
クエリ実行回数のカウント#
テストコードでN+1問題を検出するために、クエリ実行回数をアサートできます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
package com.example.demo.test;
import net.ttddyy.dsproxy.QueryCountHolder;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest
class UserServiceIntegrationTest {
@Autowired
private UserService userService;
@BeforeEach
void setUp() {
QueryCountHolder.clear();
}
@AfterEach
void tearDown() {
QueryCountHolder.clear();
}
@Test
void findAllUsersWithOrders_shouldExecuteOptimalQueries() {
// Given: 10人のユーザーと各ユーザーに5件の注文がある状態
// When
userService.findAllUsersWithOrders();
// Then: N+1問題がなければ2クエリ以下で取得できるはず
assertThat(QueryCountHolder.getGrandTotal().getSelect())
.as("SELECT文の実行回数が最適化されていること")
.isLessThanOrEqualTo(2);
}
}
|
P6Spyによるクエリ分析と可視化#
P6Spyは、JDBCドライバレベルでSQLをインターセプトし、実行時間やパラメータを詳細にログ出力するツールです。設定がシンプルで、既存のプロジェクトへの導入が容易です。
依存関係の追加#
1
2
3
4
5
6
|
<!-- Maven -->
<dependency>
<groupId>p6spy</groupId>
<artifactId>p6spy</artifactId>
<version>3.9.1</version>
</dependency>
|
1
2
|
// Gradle
implementation 'p6spy:p6spy:3.9.1'
|
接続URL・ドライバの変更#
1
2
3
4
5
6
|
spring:
datasource:
# 元のURLの前に「jdbc:p6spy:」を追加
url: jdbc:p6spy:postgresql://localhost:5432/mydb
# P6SpyDriverに変更
driver-class-name: com.p6spy.engine.spy.P6SpyDriver
|
spy.propertiesの設定#
src/main/resources/spy.propertiesを作成します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
# 実際のJDBCドライバを指定
driverlist=org.postgresql.Driver
# ログ出力形式をカスタマイズ
logMessageFormat=com.p6spy.engine.spy.appender.CustomLineFormat
customLogMessageFormat=%(currentTime)|%(executionTime)ms|%(category)|%(connectionId)|%(sqlSingleLine)
# ログ出力先
appender=com.p6spy.engine.spy.appender.Slf4JLogger
# スロークエリの閾値(ミリ秒)
executionThreshold=100
# バインドパラメータを展開して表示
excludecategories=info,debug,result,resultset
# 日付フォーマット
dateformat=yyyy-MM-dd HH:mm:ss.SSS
|
出力例#
2026-01-13 17:30:45.123|15ms|statement|1|select u1_0.id,u1_0.email,u1_0.name from users u1_0 where u1_0.id=1
2026-01-13 17:30:45.150|250ms|statement|1|select o1_0.id,o1_0.user_id,o1_0.total from orders o1_0 where o1_0.user_id in (1,2,3,4,5,6,7,8,9,10)
P6Spyの利点#
| 機能 |
説明 |
| 実行時間の計測 |
クエリごとの実行時間をミリ秒単位で記録 |
| パラメータの展開 |
?プレースホルダを実際の値に展開して表示 |
| スロークエリ検出 |
閾値を超えたクエリを自動的にハイライト |
| 導入の容易さ |
URL変更と設定ファイル追加のみで導入可能 |
N+1問題の検出方法#
N+1問題は、JPAアプリケーションで最も頻繁に発生するパフォーマンス問題です。効率的に検出する方法を解説します。
N+1問題とは#
親エンティティN件を取得した後、各親に紐づく子エンティティを個別に取得するためにN回の追加クエリが発生する現象です。
sequenceDiagram
participant App as アプリケーション
participant DB as データベース
App->>DB: 1回目: SELECT * FROM users
DB-->>App: ユーザー100件を返却
loop 各ユーザーに対して
App->>DB: SELECT * FROM orders WHERE user_id = ?
end
Note over App,DB: 合計101回のクエリ(1 + 100 = N+1)検出方法1: Hibernate統計情報#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
@Test
void detectN1Problem() {
Statistics stats = entityManagerFactory.unwrap(SessionFactory.class).getStatistics();
stats.clear();
// 処理を実行
List<User> users = userRepository.findAll();
users.forEach(user -> user.getOrders().size()); // Lazy Load発生
// N+1問題が発生している場合、クエリ数がユーザー数+1になる
long queryCount = stats.getQueryExecutionCount();
System.out.println("実行されたクエリ数: " + queryCount);
// 期待値は2以下(ユーザー取得 + 注文一括取得)
assertThat(queryCount).isLessThanOrEqualTo(2);
}
|
検出方法2: DataSource-Proxyのカウント機能#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
@Test
void detectN1ProblemWithDataSourceProxy() {
QueryCountHolder.clear();
// 処理を実行
userService.findAllUsersWithOrderDetails();
// SELECTクエリの実行回数を検証
long selectCount = QueryCountHolder.getGrandTotal().getSelect();
// N+1問題がない場合は2クエリ以下
assertThat(selectCount)
.as("N+1問題が発生していないこと")
.isLessThanOrEqualTo(2);
}
|
検出方法3: ログ分析#
SQLログを有効にし、同じテーブルへのSELECTが連続して発生していないか確認します。
1
2
3
4
5
6
|
-- N+1問題のパターン
select * from users;
select * from orders where user_id = 1;
select * from orders where user_id = 2;
select * from orders where user_id = 3;
-- ... 繰り返し
|
N+1問題の解決策#
1
2
3
4
5
6
7
8
9
10
11
12
|
// 解決策1: JOIN FETCH
@Query("SELECT u FROM User u LEFT JOIN FETCH u.orders")
List<User> findAllWithOrders();
// 解決策2: @EntityGraph
@EntityGraph(attributePaths = {"orders"})
List<User> findAll();
// 解決策3: @BatchSize(エンティティクラスに設定)
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
@BatchSize(size = 100)
private List<Order> orders;
|
スロークエリの特定とEXPLAIN ANALYZEの使い方#
実行時間の長いクエリを特定し、実行計画を分析することでパフォーマンス改善の糸口を見つけられます。
Hibernateによるスロークエリログ#
1
2
3
4
5
6
7
8
9
|
spring:
jpa:
properties:
hibernate:
# 100ミリ秒以上のクエリをログ出力
session:
events:
log:
LOG_QUERIES_SLOWER_THAN_MS: 100
|
また、Hibernate 6.6では以下の設定も利用可能です。
1
2
3
4
5
|
spring:
jpa:
properties:
hibernate:
log_slow_query: 100
|
PostgreSQLでのEXPLAIN ANALYZE#
1
2
3
4
5
6
7
|
EXPLAIN ANALYZE
SELECT u.*, o.*
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.status = 'ACTIVE'
ORDER BY u.created_at DESC
LIMIT 100;
|
実行計画の読み方#
Limit (cost=1234.56..1234.78 rows=100 width=256) (actual time=15.234..15.456 rows=100 loops=1)
-> Sort (cost=1234.56..1245.67 rows=5000 width=256) (actual time=15.230..15.345 rows=100 loops=1)
Sort Key: u.created_at DESC
Sort Method: top-N heapsort Memory: 50kB
-> Hash Left Join (cost=100.00..1000.00 rows=5000 width=256) (actual time=5.123..12.456 rows=5000 loops=1)
Hash Cond: (u.id = o.user_id)
-> Seq Scan on users u (cost=0.00..500.00 rows=5000 width=128) (actual time=0.015..3.456 rows=5000 loops=1)
Filter: (status = 'ACTIVE')
-> Hash (cost=80.00..80.00 rows=2000 width=128) (actual time=2.345..2.345 rows=2000 loops=1)
Buckets: 2048 Batches: 1 Memory Usage: 200kB
-> Seq Scan on orders o (cost=0.00..80.00 rows=2000 width=128) (actual time=0.010..1.234 rows=2000 loops=1)
Planning Time: 0.456 ms
Execution Time: 15.789 ms
実行計画で注目すべきポイント#
| 項目 |
説明 |
注意点 |
| Seq Scan |
テーブル全件スキャン |
大きなテーブルでは要インデックス |
| actual time |
実際の実行時間 |
costより重要 |
| rows |
処理行数 |
予測と実際の乖離に注意 |
| loops |
繰り返し回数 |
高い値はネストループの可能性 |
MySQLでのEXPLAIN ANALYZE#
1
2
3
4
5
6
7
|
EXPLAIN ANALYZE
SELECT u.*, o.*
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.status = 'ACTIVE'
ORDER BY u.created_at DESC
LIMIT 100;
|
JPA経由でのEXPLAINの実行#
1
2
3
4
5
6
7
8
9
10
11
12
|
@Repository
public class QueryAnalysisRepository {
@PersistenceContext
private EntityManager entityManager;
public List<Object[]> explainQuery(String sql) {
// PostgreSQLの場合
String explainSql = "EXPLAIN ANALYZE " + sql;
return entityManager.createNativeQuery(explainSql).getResultList();
}
}
|
インデックス設計の基本#
適切なインデックス設計は、クエリパフォーマンスに決定的な影響を与えます。
JPAエンティティでのインデックス定義#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
@Entity
@Table(
name = "users",
indexes = {
@Index(name = "idx_users_email", columnList = "email", unique = true),
@Index(name = "idx_users_status_created", columnList = "status, created_at"),
@Index(name = "idx_users_name", columnList = "name")
}
)
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String email;
@Column(nullable = false)
private String name;
@Column(nullable = false)
private String status;
@Column(name = "created_at", nullable = false)
private LocalDateTime createdAt;
}
|
インデックス設計の原則#
| 原則 |
説明 |
| WHERE句の条件列 |
検索条件に頻繁に使用される列 |
| JOIN条件の列 |
外部キーや結合条件に使用される列 |
| ORDER BY句の列 |
ソートに使用される列 |
| カーディナリティ |
値の種類が多い列ほど効果的 |
| 複合インデックス |
よく一緒に使用される列はまとめる |
複合インデックスの順序#
複合インデックスでは、列の順序が重要です。
1
2
3
4
5
|
// 良い例: statusで絞り込み → created_atでソート
@Index(name = "idx_status_created", columnList = "status, created_at")
// クエリ例
SELECT * FROM users WHERE status = 'ACTIVE' ORDER BY created_at DESC;
|
インデックスが効かないケース#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
-- 1. 関数を使用した場合
SELECT * FROM users WHERE LOWER(email) = 'test@example.com';
-- 解決策: 関数インデックスを作成するか、データを正規化
-- 2. LIKE検索で前方一致以外
SELECT * FROM users WHERE name LIKE '%田中%';
-- 解決策: 全文検索の導入を検討
-- 3. NULL比較(一部のDB)
SELECT * FROM users WHERE deleted_at IS NULL;
-- 解決策: 部分インデックスの作成
-- 4. 型の不一致
SELECT * FROM users WHERE id = '123'; -- idがBIGINTの場合
-- 解決策: 適切な型でバインド
|
インデックスの確認クエリ#
1
2
3
4
5
6
7
8
9
10
11
|
-- PostgreSQL
SELECT
tablename,
indexname,
indexdef
FROM pg_indexes
WHERE schemaname = 'public'
ORDER BY tablename, indexname;
-- MySQL
SHOW INDEX FROM users;
|
よくある誤解やアンチパターン#
アンチパターン1: 本番環境でのshow-sql有効化#
1
2
3
4
|
# 本番環境では絶対に避けるべき設定
spring:
jpa:
show-sql: true # パフォーマンス低下の原因
|
標準出力への書き込みはI/Oコストが高く、高負荷環境でボトルネックになります。
アンチパターン2: すべての関連をEAGERフェッチ#
1
2
3
4
5
6
7
|
// アンチパターン
@ManyToOne(fetch = FetchType.EAGER) // 常にJOINが発生
private Department department;
// 推奨
@ManyToOne(fetch = FetchType.LAZY)
private Department department;
|
アンチパターン3: インデックスの過剰作成#
1
2
3
4
5
6
7
|
// アンチパターン: すべての列にインデックス
@Table(indexes = {
@Index(columnList = "col1"),
@Index(columnList = "col2"),
@Index(columnList = "col3"),
// ...
})
|
インデックスはINSERT/UPDATE/DELETE時にオーバーヘッドが発生します。実際のクエリパターンに基づいて必要なものだけを作成してください。
アンチパターン4: 統計情報を本番で常時有効#
1
2
3
4
5
6
|
# 本番環境では負荷が高い
spring:
jpa:
properties:
hibernate:
generate_statistics: true
|
本番環境では必要な時だけ動的に有効化する仕組みを検討してください。
アンチパターン5: EXPLAIN ANALYZEの結果を無視#
実行計画を確認しても、以下の点を見落とすケースがあります。
- 予測行数と実際の行数の乖離
- Seq Scanの見落とし
- actual timeではなくcostだけを見ている
まとめと実践Tips#
本記事では、Spring JPAパフォーマンス診断のための様々な手法を解説しました。
診断ツールの使い分け#
| 用途 |
推奨ツール |
| 開発時のクイック確認 |
show-sql + format_sql |
| 詳細なパラメータ確認 |
P6Spy |
| テストでのN+1検出 |
DataSource-Proxy |
| 統計的な分析 |
Hibernate Statistics |
| 本番環境のモニタリング |
APMツール(Micrometer等) |
実践Tips#
- 開発初期からSQLログを確認する習慣をつける - 問題は早期発見が重要
- テストコードでクエリ数をアサートする - リグレッションを防止
- スロークエリの閾値を設定する - 問題を自動検出
- 定期的にEXPLAIN ANALYZEを実行する - 実行計画の変化を監視
- インデックスは実際のクエリパターンに基づいて設計する - 不要なインデックスは害
- 本番環境と開発環境でデータ量を近づける - 本番でしか発生しない問題を防止
パフォーマンス改善のチェックリスト#
- N+1問題が発生していないか確認
- 適切なフェッチ戦略(LAZY/EAGER)を選択しているか
- 必要なインデックスが作成されているか
- 不要なカラムを取得していないか(Projectionの活用)
- バッチ処理が適切に設定されているか
- 2次キャッシュを活用できないか検討
これらの手法を組み合わせることで、Spring JPAアプリケーションのパフォーマンスを継続的に監視・改善できます。
参考リンク#