Memory and CPU Leaks
Learn how to detect and diagnose memory and CPU leaks in your Front-Commerce application using various tools and techniques.
Understanding Memory and CPU Leaks
Memory Leaks
Memory leaks occur when your application allocates memory but fails to release it when it's no longer needed. In Front-Commerce applications, this can lead to:
- Gradually increasing memory usage
- Performance degradation over time
- Application crashes in severe cases
- Higher server costs due to increased resource usage
CPU Leaks
CPU leaks happen when your application consumes excessive CPU resources due to inefficient code patterns. Common causes include:
- Infinite loops or recursive functions
- Excessive event handling
- Unoptimized rendering cycles
- Blocking operations in the main thread
- Memory leaks that trigger frequent garbage collection
Common Causes in Front-Commerce
Memory Leak Causes
1. Event Listeners Not Cleaned Up
Event listeners that aren't properly removed can accumulate over time:
// ❌ Bad: Event listener not cleaned up
useEffect(() => {
const handleResize = () => {
// Handle resize logic
};
window.addEventListener("resize", handleResize);
// Missing cleanup
}, []);
// ✅ Good: Proper cleanup
useEffect(() => {
const handleResize = () => {
// Handle resize logic
};
window.addEventListener("resize", handleResize);
return () => {
window.removeEventListener("resize", handleResize);
};
}, []);
2. Timer Intervals Not Cleared
setInterval calls that aren't cleared can continue running:
// ❌ Bad: Interval not cleared
useEffect(() => {
const interval = setInterval(() => {
// Polling logic
}, 5000);
// Missing cleanup
}, []);
// ✅ Good: Proper cleanup
useEffect(() => {
const interval = setInterval(() => {
// Polling logic
}, 5000);
return () => {
clearInterval(interval);
};
}, []);
3. Large Objects in Memory Cache
Caching large objects without size limits or TTL:
// ❌ Bad: Unbounded cache growth
class ProductCache {
constructor() {
this.cache = new Map(); // No size limit
}
set(key, value) {
this.cache.set(key, value); // Can grow indefinitely
}
}
// ✅ Good: Bounded cache with TTL
class ProductCache {
constructor(maxSize = 1000, ttl = 300000) {
// 5 minutes TTL
this.cache = new Map();
this.maxSize = maxSize;
this.ttl = ttl;
}
set(key, value) {
if (this.cache.size >= this.maxSize) {
// Remove oldest entry
const firstKey = this.cache.keys().next().value;
this.cache.delete(firstKey);
}
this.cache.set(key, {
value,
timestamp: Date.now(),
});
}
get(key) {
const item = this.cache.get(key);
if (!item) return null;
if (Date.now() - item.timestamp > this.ttl) {
this.cache.delete(key);
return null;
}
return item.value;
}
}
CPU Leak Causes
1. Infinite Loops and Recursive Functions
Functions that run indefinitely or have unbounded recursion:
// ❌ Bad: Potential infinite loop
const processData = (data) => {
// Process data
if (data.length > 0) {
processData(data.slice(1)); // Unbounded recursion
}
};
processData(largeDataSet);
// ✅ Good: Bounded recursion with base case
const processData = (data, depth = 0) => {
if (depth > 1000 || data.length === 0) return; // Base case
// Process data
processData(data.slice(1), depth + 1);
};
processData(largeDataSet);
2. Excessive Re-renders
Components that re-render unnecessarily:
// ❌ Bad: Component re-renders on every state change
function ProductList({ products }) {
const [filteredProducts, setFilteredProducts] = useState([]);
// This runs on every render
const expensiveFilter = products.filter((product) => {
// Expensive filtering logic
return complexFilterCondition(product);
});
useEffect(() => {
setFilteredProducts(expensiveFilter);
}, [expensiveFilter]); // This creates an infinite loop
}
// ✅ Good: Memoized expensive operations
function ProductList({ products }) {
const filteredProducts = useMemo(() => {
return products.filter((product) => {
return complexFilterCondition(product);
});
}, [products]); // Only recalculates when products change
}
3. Blocking Operations in Main Thread
Synchronous operations that block the event loop:
// ❌ Bad: Blocking operation
function processLargeData(data) {
const result = [];
for (let i = 0; i < data.length; i++) {
// Expensive synchronous operation
result.push(expensiveCalculation(data[i]));
}
return result;
}
// ✅ Good: Non-blocking with chunks
function processLargeData(data) {
return new Promise((resolve) => {
const result = [];
let index = 0;
function processChunk() {
const chunkSize = 1000;
const end = Math.min(index + chunkSize, data.length);
for (let i = index; i < end; i++) {
result.push(expensiveCalculation(data[i]));
}
index = end;
if (index < data.length) {
// Process next chunk in next tick
setImmediate(processChunk);
} else {
resolve(result);
}
}
processChunk();
});
}
4. Unbounded Promise Chains
Promise chains that can grow indefinitely:
// ❌ Bad: Unbounded promise chain
async function processDataStream(dataStream) {
let result = [];
for (const chunk of dataStream) {
// Each iteration creates a new promise that waits for the previous
result = await processChunk(result, chunk);
}
return result;
}
// ✅ Good: Process in batches with limits
async function processDataStream(dataStream) {
const batchSize = 100;
const maxConcurrency = 5;
let result = [];
let batch = [];
for (const chunk of dataStream) {
batch.push(chunk);
if (batch.length >= batchSize) {
// Process batch with concurrency limit
const processedBatch = await Promise.all(
batch.map((chunk) => processChunk(result, chunk))
);
result = processedBatch.reduce((acc, item) => acc.concat(item), result);
batch = [];
// Add small delay to prevent overwhelming the system
await new Promise((resolve) => setTimeout(resolve, 10));
}
}
// Process remaining items
if (batch.length > 0) {
const processedBatch = await Promise.all(
batch.map((chunk) => processChunk(result, chunk))
);
result = processedBatch.reduce((acc, item) => acc.concat(item), result);
}
return result;
}
Detection Tools
1. Built-in Node.js Tools
Memory Usage Monitoring
Monitor memory usage in your application:
// Add to your server.mjs or entry point
setInterval(() => {
const memUsage = process.memoryUsage();
console.log("Memory Usage:", {
rss: `${Math.round(memUsage.rss / 1024 / 1024)} MB`,
heapTotal: `${Math.round(memUsage.heapTotal / 1024 / 1024)} MB`,
heapUsed: `${Math.round(memUsage.heapUsed / 1024 / 1024)} MB`,
external: `${Math.round(memUsage.external / 1024 / 1024)} MB`,
});
}, 30000); // Log every 30 seconds
Be cautious when adding memory monitoring to production environments. Frequent logging can create noise in your logs.
Heap Snapshots
Take heap snapshots to analyze memory allocation:
# Buidl and start your application with heap profiling
pnpm build && pnpm start:debug
Then use Chrome DevTools to take heap snapshots:
- Open Chrome and navigate to
chrome://inspect
- Click "Open dedicated DevTools for Node"
- In the DevTools, go to the Memory tab
- Click "Take heap snapshot" to capture memory state
- Perform actions that might cause leaks
- Take another snapshot and compare them
To debug easily in production mode, you can enable source map in your vite config.
CPU Profiling
Monitor CPU usage and identify hot paths:
- Since 3.14
- Before 3.14
# Start with CPU profiling enabled
pnpm start:profile
# Run your application for a while, then stop it (Ctrl+C)
# This will generate isolate-*.log files in your current directory
# Generate CPU profile report
pnpm start:profile:process
# Start with CPU profiling enabled
cross-env NODE_ENV=production node --prof --import tsx/esm ./server.mjs
# Run your application for a while, then stop it (Ctrl+C)
# This will generate isolate-\*.log files in your current directory
# Generate CPU profile report
cross-env NODE_ENV=production node --prof-process isolate-\*.log >
cpu-profile.txt
2. Clinic.js Tools
Clinic.js is a powerful suite of tools for diagnosing Node.js performance issues including memory and CPU leaks.
Installation
Install clinic and autocannon
pnpm install -g clinic autocannon
Basic Usage
Profile your Front-Commerce application for memory and CPU issues:
# Profile with Doctor (general analysis)
pnpm build && NODE_ENV=production clinic doctor --on-port 'autocannon -c 2 -d 5 localhost:4000' -- node --import tsx/esm ./server.mjs
# Profile with Heap Profiler (memory-specific)
pnpm build && NODE_ENV=production clinic heapprofiler --on-port 'autocannon -c 2 -d 5 localhost:4000' -- node --import tsx/esm ./server.mjs
# Profile with Flame (CPU-specific)
pnpm build && NODE_ENV=production clinic flame --on-port 'autocannon -c 2 -d 5 localhost:4000' -- node --import tsx/esm ./server.mjs
Understanding the Results
Doctor provides a general overview and identifies:
- Memory usage patterns
- CPU utilization
- Event loop delays
- I/O bottlenecks
Heap Profiler specifically analyzes:
- Memory allocation patterns
- Object retention
- Garbage collection behavior
- Memory leak indicators
Flame focuses on:
- CPU usage by function
- Hot code paths
- Performance bottlenecks
- Call stack analysis
Advanced Usage
For more detailed analysis, you can customize the load testing:
# Longer test with more concurrent connections
NODE_ENV=production clinic doctor --on-port 'autocannon -c 10 -d 30 localhost:4000' -- node --import tsx/esm ./server.mjs
# Test specific endpoints
NODE_ENV=production clinic doctor --on-port 'autocannon -c 5 -d 10 http://localhost:4000/products' -- node --import tsx/esm ./server.mjs
Cleanup
After profiling, clean up generated files:
clinic clean
3. Browser Developer Tools
Chrome DevTools Memory Tab
- Open Chrome DevTools
- Go to the Memory tab
- Take heap snapshots:
- Click "Take heap snapshot"
- Perform actions that might cause leaks
- Take another snapshot
- Compare snapshots to identify retained objects
React DevTools Profiler
- Install React DevTools browser extension
- Use the Profiler tab to:
- Record component renders
- Identify components that re-render unnecessarily
- Check for memory leaks in component lifecycle
When to Seek Help
Contact our support team if you:
- Experience memory usage growing continuously over time
- See application crashes due to out-of-memory errors
- Notice high CPU usage that doesn't decrease over time
- Experience slow response times despite adequate resources
- Have tried the above techniques without success
- Need help interpreting performance profiling results
Please provide:
- Performance profiling reports (Clinic.js, heap snapshots, CPU profiles)
- Application logs
- Steps to reproduce the issue
- Environment details (Node.js version, memory limits, CPU cores, etc.)
- Performance metrics (memory usage, CPU usage, response times)