Project

General

Profile

개선(improvement) #2627

Updated by Dante Le 3 months ago

**Parent Issue**: #2310 [BE] Optimize Memory, CPU API search list flight 

 ## Summary 

 Multiple `deepCopy()` operations throughout the service creating 100-500 KB garbage per request, triggering excessive GC pressure under concurrent load. 

 ## Location 

 **File**: `web-api/src/main/java/com/ohmy/api/service/flight/FlightListItineraryService.java` 

 **All deepCopy() Locations**: 

 | Line | Context | Impact per Call | 
 |------|---------|-----------------| 
 | 585 | `alliance.deepCopy()` in triple nested loop | 5-10 KB × loop iterations | 
 | 1162 | `fareJsonNode.deepCopy()` in `onewayFaresToFlightsFare()` | 2-5 KB per fare | 
 | 1242 | `x.deepCopy()` in stream map | 2-5 KB per item | 
 | 1543, 1548, 1597, 1620 | Multiple deepCopy in `getSelectedJsonNodeCombinedFares()` | 10-20 KB per call | 
 | 1683, 1760, 1793 | Multiple deepCopy in `getSelectedJsonNodeOnewayFares()` | 10-20 KB per call | 
 | 1999 | `flightJsonNode.deepCopy()` in `makeFareRequestBody()` | 5-10 KB | 
 | 2792 | `requestBody.get("condition").deepCopy()` in `getListItinerary()` | 1-2 KB | 

 ## Problem Code Examples 

 ```java 
 <pre><code class="java"> 
 // Line 585: Deep copy in triple nested loop 
 alliance.deepCopy() 

 // Line 1162: Deep copy in fare transformation 
 JsonNode copiedFareJsonNode = fareJsonNode.deepCopy(); 

 // Lines 1543-1548: Multiple deep copies 
 flightFareJsonNode = flightJsonNode.deepCopy(); 
 // ... more processing ... 
 flightFareJsonNode = flightFareJsonNode.deepCopy(); 

 // Line 2792: Deep copy in request processing 
 ObjectNode conditionJsonNode = requestBody.get("condition").deepCopy(); 
 </code></pre> ``` 

 ## Performance Impact 

 **Memory Allocation**: 
 - **Total per request**: 100-500 KB garbage objects 
 - **Under 50 concurrent requests**: 5-25 MB additional garbage/second 
 - Each deepCopy() recursively copies entire JSON tree structure 

 **GC Pressure**: 
 - Triggers Young GC every 2-3 seconds under load 
 - Contributes to Full GC cycles when Old Gen fills 
 - Part of the GC death spiral causing OutOfMemoryError 

 **CPU Impact**: 
 - Object allocation overhead 
 - Recursive tree traversal for each copy 
 - GC CPU time increases proportionally 

 ## Required Fix 

 ### 1. Use Immutable Patterns 
 Instead of defensive copying, design methods to not modify input: 
 <pre><code class="java"> ```java 
 // BEFORE: defensive copy 
 JsonNode modified = original.deepCopy(); 
 ((ObjectNode) modified).put("field", value); 

 // AFTER: create new node with specific changes 
 ObjectNode modified = JsonNodeFactory.instance.objectNode(); 
 original.fields().forEachRemaining(entry -> { 
     if (!entry.getKey().equals("field")) { 
         modified.set(entry.getKey(), entry.getValue()); 
     } 
 }); 
 modified.put("field", value); 
 </code></pre> 
 ``` 

 ### 2. Copy Only Modified Fields 
 Build new nodes with only changed fields instead of full tree copy: 
 ```java 
 // BEFORE: full deep copy 
 JsonNode copy = fareJsonNode.deepCopy(); 
 ((ObjectNode) copy).put("itemCode", newValue); 

 // AFTER: selective copy 
 ObjectNode fare = JsonNodeFactory.instance.objectNode(); 
 fare.setAll((ObjectNode) fareJsonNode); 
 fare.put("itemCode", newValue); 
 ``` 

 ### 3. In-Place Mutation Where Safe 
 Use ObjectNode mutation when the object is not shared: 
 <pre><code class="java"> ```java 
 // If fareJsonNode is owned by this method only 
 ((ObjectNode) fareJsonNode).put("field", value);    // No copy needed 
 </code></pre> 


 ``` 

 ### 4. Analyze Copy Necessity 
 Audit each deepCopy() to determine: 
 - Is this protecting against mutation that actually occurs? 
 - Can we refactor to avoid mutation entirely? 
 - Is this node shared across threads or requests? 

 ## Acceptance Criteria 

 ✅ **MUST maintain 100% identical API response** (no logic changes) 
 ✅ Reduce garbage allocation from 100-500 KB to < 50 KB per request 
 ✅ Eliminate deepCopy() in hot paths (triple nested loops) 
 ✅ Keep only deepCopy() calls that are truly necessary for thread safety 
 ✅ Verify GC reduction with profiling tools (VisualVM, JFR) 
 ✅ Measure GC pause time improvement under 50 concurrent requests 

 ## Constraints 

 ⚠️ **NO LOGIC CHANGES ALLOWED** - This is a performance optimization only 
 - Do not modify calculation results or data transformations 
 - Do not alter API response structure or values 
 - Maintain thread safety where required 

 ## Related Issues 

 - #2610 - CRITICAL-1: Excessive JsonNode Traversal (Fixed ✅) 
 - #2611 - AtomicReference Memory Retention (Fixed ✅) 
 - #2626 - CRITICAL-2: O(n³) Nested Loop Anti-Pattern 
 - Part of Phase 3: Major Refactoring in optimization roadmap 

 ## Reference 

 See `docs/flight-list-itinerary/VERIFIED_ISSUES.md` - CRITICAL-3 section for complete analysis. 

 ## Testing Requirements 

 1. Before/after memory profiling with identical request load 
 2. JFR recording comparing GC activity 
 3. Response validation: byte-for-byte comparison of API output 
 4. Load test: 50 concurrent requests, measure GC pause time

Back

Add picture from clipboard (Maximum size: 50 MB)