404 Not Found

404 Not Found


nginx

Performance Optimization

Performance optimization is a systems engineering effort—it involves three dimensions: the JVM, the database, and the connection pool. Only by accurately pinpointing bottlenecks can we address the root causes.

1. What You'll Learn


2. A True Story of a Performance Engineer

(1) Pain Point: The API is as slow as a snail

Charlie reported that the P99 latency for the OrderFlow order list API was 500 ms, resulting in a very poor user experience. Alice's analysis revealed the following: 1) Frequent Full GC pauses in the JVM lasting 200 ms; 2) An N+1 problem occurring when JPA queries the order list (100 orders = 101 SQL queries); 3) The HikariCP connection pool size is insufficient, causing requests to time out while waiting for a connection.

(2) Solution Methods for System Optimization

Optimize Layer by Layer to Precisely Address Bottlenecks:

Level Bottleneck Optimization Solution
JVM Full GC Pause G1GC + Heap Size Tuning
Connection Pool Not Enough Connections HikariCP Parameter Optimization
ORM N+1 Queries JOIN FETCH / EntityGraph
Database Full Table Scan Add Index

(3) Revenue

After Alice optimized each component one by one, the P99 latency dropped from 500 ms to 50 ms, and throughput increased tenfold.


3. JVM Parameter Tuning

(1) Comparison of GC Algorithms

GC Algorithm Maximum Pause Time Suitable Scenarios JVM Parameters
G1GC 10-200 ms General (JDK 17 default) -XX:+UseG1GC
ZGC < 1 ms Low Latency (JDK 17+) -XX:+UseZGC
SerialGC Long Small Heap (< 200MB) -XX:+UseSerialGC
ParallelGC Medium Throughput-First -XX:+UseParallelGC
100%
graph LR
    A["JVM Tuning"] --> B["Heap Size<br/>-Xms = -Xmx"]
    A --> C["GC Algorithm<br/>G1 / ZGC"]
    A --> D["GC Logging<br/>-Xlog:gc*"]
    B --> B1["Container: MaxRAMPercentage"]
    C --> C1["Default: G1GC"]
    C --> C2["Low latency: ZGC"]

(1) ▶ Example: JVM Configuration in a Docker Environment

DOCKERFILE
ENTRYPOINT ["java", \
  "-XX:+UseG1GC", \
  "-XX:MaxRAMPercentage=75.0", \
  "-XX:InitialRAMPercentage=50.0", \
  "-XX:+UseStringDeduplication", \
  "-Xlog:gc*:file=/app/logs/gc.log:time,uptime,level,tags", \
  "-jar", "app.jar"]

Output:

TEXT
// Execution Successful
Parameter Meaning Recommended Value
MaxRAMPercentage Maximum heap as a percentage of container memory 75.0
InitialRAMPercentage Percentage of container memory occupied by the initial heap 50.0
+UseG1GC Use the G1 garbage collector Enabled by default
+UseStringDeduplication Removing Duplicates from Strings Saves Memory Recommended
MaxGCPauseMillis GC Target Pause Time 200 (G1) / None (ZGC)

(2) ▶ Example: ZGC Low-Latency Configuration

BASH
java -XX:+UseZGC \
     -XX:MaxRAMPercentage=75.0 \
     -XX:+ZGenerational \
     -Xlog:gc*:file=gc.log \
     -jar app.jar

Output:

TEXT
# Command executed successfully
📌 Key Point: ZGC is an experimental feature in JDK 17 (officially available in JDK 21). With a pause time of less than 1 ms, it is suitable for scenarios that are extremely latency-sensitive.


4. HikariCP Connection Pool Optimization

(1) Connection Pool Parameters

Parameter Default Value Description Recommended Value
maximumPoolSize 10 Maximum number of connections Number of CPU cores x 2 + number of disks
minimumIdle = maxPool Minimum idle connections = maxPoolSize
connectionTimeout 30,000 ms Connection timeout 3,000 ms
idleTimeout 600,000 ms Idle connection timeout 600,000 ms
maxLifetime 1,800,000 ms Maximum connection lifetime 1,800,000 ms
leakDetectionThreshold 0 (Disabled) Connection Leak Detection 60,000 ms

(1) ▶ Example: HikariCP Production Configuration

YAML
spring:
  datasource:
    hikari:
      maximum-pool-size: 20
      minimum-idle: 20
      connection-timeout: 3000
      idle-timeout: 600000
      max-lifetime: 1800000
      leak-detection-threshold: 60000
      pool-name: OrderFlowHikariCP

Output:

TEXT
The configuration has taken effect.

(2) ▶ Example: Connection Leak Detection

YAML
# Enable leak detection (log warning if connection held > 60s)
spring:
  datasource:
    hikari:
      leak-detection-threshold: 60000
💻 Output (when leaking):

TEXT
WARN  - Connection leak detection triggered for {conn-12345}
  Stack trace of the call site that acquired the connection:
    at com.orderflow.service.OrderService.getOrder(OrderService.java:45)
    ...

5. JPA Query Optimization

(1) The N+1 Problem

(1) ▶ Example: Demonstration of the N+1 Problem

JAVA
// BAD: N+1 problem
// 1 SQL to fetch 100 orders + 100 SQLs to fetch items for each order = 101 SQLs!
List<Order> orders = orderRepository.findAll();
for (Order order : orders) {
    order.getItems().size();  // Triggers lazy load for each order
}

Output:

TEXT
// Execution Successful

(2) ▶ Example: Using JOIN FETCH to Solve the N+1 Problem

JAVA
// GOOD: Single query with JOIN FETCH
@Query("SELECT DISTINCT o FROM Order o JOIN FETCH o.items i JOIN FETCH i.product WHERE o.status = :status")
List<Order> findByStatusWithItems(@Param("status") String status);

Output:

TEXT
// Execution Successful

(3) ▶ Example: Declarative Loading with @EntityGraph

JAVA
@Entity
@NamedEntityGraph(
    name = "Order.withItemsAndProduct",
    attributeNodes = {
        @NamedAttributeNode("items"),
        @NamedAttributeNode(value = "items", subgraph = "item-product")
    },
    subgraphs = {
        @NamedSubgraph(name = "item-product", attributeNodes = @NamedAttributeNode("product"))
    }
)
public class Order { /* ... */ }

// Usage in Repository
@EntityGraph(value = "Order.withItemsAndProduct", type = EntityGraphType.LOAD)
List<Order> findByStatus(String status);

Output:

TEXT
// Execution Successful
Solution Number of SQL Statements Applicable Scenarios Maintainability
Default Lazy Loading N+1 Single-Object Query High
JOIN FETCH 1 Specific Query Medium
@EntityGraph 1 Reusable load plan High
@BatchSize N/batchSize Batch Loading High

6. Database Index Optimization

(1) Indexing Strategy

(1) ▶ Example: Adding a Database Index

SQL
-- Index for order status queries
CREATE INDEX idx_order_status ON orders(status);

-- Composite index for common query pattern
CREATE INDEX idx_order_status_created ON orders(status, created_at DESC);

-- Index for product search
CREATE INDEX idx_product_name ON products(name);

-- Index for order item product lookup
CREATE INDEX idx_order_item_product ON order_items(product_id);

Output:

TEXT
CREATE TABLE
Index Type Use Case Example
Single-column index Single-condition query WHERE status = 'PENDING'
Composite Index Multi-Condition Query WHERE status = ? AND created_at > ?
Unique Index Unique Constraint UNIQUE(sku)
Covering Index Index contains all query columns SELECT status FROM orders WHERE id = ?

(2) ▶ Example: Slow Query Analysis

SQL
-- MySQL slow query log configuration
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 1;  -- Log queries > 1 second
SET GLOBAL log_queries_not_using_indexes = ON;

-- Analyze query execution plan
EXPLAIN SELECT o.*, i.*
FROM orders o
JOIN order_items i ON o.id = i.order_id
WHERE o.status = 'PENDING'
  AND o.created_at > '2024-01-01'
ORDER BY o.created_at DESC
LIMIT 20;

Output:

TEXT
CREATE TABLE
EXPLAIN Field Meaning Key Points
type Access Type ALL (Full Table Scan) Needs Optimization
key Index used NULL indicates that no index was used
rows Number of Scanned Lines The lower, the better
Extra Additional Information Using filesort requires optimization

7. Comprehensive Example: OrderFlow P99 Reduced from 500 ms to 50 ms

YAML
# application-prod.yml (optimized)
spring:
  datasource:
    hikari:
      maximum-pool-size: 20
      minimum-idle: 20
      connection-timeout: 3000
      leak-detection-threshold: 60000
  jpa:
    hibernate:
      ddl-auto: validate
    show-sql: false
    properties:
      hibernate:
        default_batch_fetch_size: 100
        jdbc:
          batch_size: 50
        order_inserts: true
        order_updates: true
BASH
# JVM parameters (Docker ENTRYPOINT)
java -XX:+UseG1GC \
     -XX:MaxRAMPercentage=75.0 \
     -XX:InitialRAMPercentage=50.0 \
     -XX:+UseStringDeduplication \
     -XX:MaxGCPauseMillis=100 \
     -Xlog:gc*:file=/app/logs/gc.log:time,uptime,level,tags \
     -jar app.jar
JAVA
// Optimized OrderRepository with EntityGraph and batch fetching
public interface OrderRepository extends JpaRepository<Order, Long> {

    @EntityGraph(value = "Order.withItemsAndProduct", type = EntityGraphType.LOAD)
    @Query("SELECT o FROM Order o WHERE o.status = :status ORDER BY o.createdAt DESC")
    Page<Order> findByStatusPaged(@Param("status") String status, Pageable pageable);

    @EntityGraph(value = "Order.withItemsAndProduct", type = EntityGraphType.LOAD)
    Optional<Order> findById(Long id);
}

// Hibernate batch size configuration eliminates N+1 for collections
// application.yml: hibernate.default_batch_fetch_size=100
Before Optimization After Optimization Improvement Factor
P99: 500 ms P99: 50 ms 10x
SQL/Request: 101 SQL/Request: 1 100x
GC pause: 200 ms GC pause: 50 ms 4x
Connection wait: 100 ms Connection wait: 2 ms 50x

❓ FAQ

Q Which should I choose, G1GC or ZGC?
A G1GC is the default in JDK 17 and is suitable for the vast majority of scenarios, with pause times of 10-200 ms. ZGC has a pause time of < 1 ms and is suitable for low-latency scenarios such as financial transactions, but its throughput is slightly lower. Start with G1GC, and switch to ZGC only if you encounter latency issues.
Q Is a larger connection pool always better?
A No. A connection pool that is too large results in: 1) increased database load; 2) context-switching overhead; 3) increased memory usage. Recommended formula: connections = (CPU cores x 2) + effective_spindle_count.
Q What is the difference between @EntityGraph and JOIN FETCH?
A @EntityGraph is declarative and can be defined on an Entity or Repository for reuse; JOIN FETCH is imperative and is written within each @Query. EntityGraph is easier to maintain in complex scenarios.
Q What does the Hibernate batch_size do?
A When hibernate.jdbc.batch_size=50 is set, INSERT/UPDATE statements are executed in batches (50 records per batch), reducing the number of round trips to the database. This works even better when used in conjunction with order_inserts=true.
Q How do I identify performance bottlenecks?
A 1) Use Actuator /metrics to check HTTP request durations; 2) Analyze GC logs to identify GC pauses; 3) HikariCP metrics to check the connection pool status; 4) MySQL slow query log to identify slow queries; 5) APM tools (SkyWalking/Zipkin) for end-to-end tracing.
Q How do you perform performance testing?
A Use JMeter, Gatling, or k6. Gradually increase the load (ramp-up) and observe the inflection points in response time and error rate. Test scenarios should cover: single API endpoints, mixed scenarios, and peak scenarios.

📖 Summary


📝 Exercises

  1. Basic Exercise (Difficulty: ⭐): Configure the JVM G1GC parameters and HikariCP connection pool parameters for OrderFlow, and enable GC logging and connection leak detection.

  2. Advanced Problem (Difficulty ⭐⭐): Use @EntityGraph and JOIN FETCH to solve the N+1 problem in the order list query, and compare the number of SQL statements and response times before and after optimization.

  3. Challenge (Difficulty: ⭐⭐⭐): Use JMeter to perform load testing on the OrderFlow order list API, establish a performance baseline, and gradually optimize it (JVM + connection pool + N+1 + indexes). Record the changes in P50 and P99 for each optimization step, and ultimately optimize P99 to below 50 ms.

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏