0

Cách debug memory leak trong Node.js

App Node.js chạy vài ngày thì restart? Memory tăng đều? Có thể bạn đang bị memory leak. Đây là cách mình debug.

Dấu hiệu memory leak

# Theo dõi memory
node --max-old-space-size=512 app.js

# Process memory tăng đều theo thời gian
# RSS (Resident Set Size) không giảm sau GC

Bước 1: Xác nhận leak

// Thêm endpoint này vào app
app.get('/health/memory', (req, res) => {
    const mem = process.memoryUsage();
        res.json({
                rss: (mem.rss / 1024 / 1024).toFixed(2) + ' MB',
                        heapUsed: (mem.heapUsed / 1024 / 1024).toFixed(2) + ' MB',
                                heapTotal: (mem.heapTotal / 1024 / 1024).toFixed(2) + ' MB',
                                        external: (mem.external / 1024 / 1024).toFixed(2) + ' MB',
                                            });
                                            });
                                            ```
                                            
                                            Gọi endpoint này mỗi phút. Nếu `heapUsed` tăng đều → leak.
                                            
                                            ## Bước 2: Tìm nguồn leak
                                            
                                            ### Nguyên nhân phổ biến
                                            
                                            **1. Event listeners không remove**
                                            
                                            ```javascript
                                            // Leak: mỗi request thêm 1 listener, không bao giờ remove
                                            app.get('/stream', (req, res) => {
                                                emitter.on('data', (data) => res.write(data));
                                                });
                                                
                                                // Fix: remove khi connection đóng
                                                app.get('/stream', (req, res) => {
                                                    const handler = (data) => res.write(data);
                                                        emitter.on('data', handler);
                                                            req.on('close', () => emitter.off('data', handler));
                                                            });
                                                            ```
                                                            
                                                            **2. Cache không giới hạn**
                                                            
                                                            ```javascript
                                                            // Leak: cache grow mãi mãi
                                                            const cache = {};
                                                            function getData(key) {
                                                                if (!cache[key]) cache[key] = fetchFromDB(key);
                                                                    return cache[key];
                                                                    }
                                                                    
                                                                    // Fix: dùng LRU cache
                                                                    const LRU = require('lru-cache');
                                                                    const cache = new LRU({ max: 500 });
                                                                    ```
                                                                    
                                                                    **3. Closures giữ reference**
                                                                    
                                                                    ```javascript
                                                                    // Leak: closure giữ reference đến toàn bộ `data`
                                                                    function processData() {
                                                                        const data = loadHugeFile();  // 100MB
                                                                            return function getSize() {
                                                                                    return data.length;  // Giữ `data` trong memory mãi
                                                                                        };
                                                                                        }
                                                                                        ```
                                                                                        
                                                                                        **4. Global variables**
                                                                                        
                                                                                        ```javascript
                                                                                        // Leak: mỗi request push vào array global
                                                                                        const logs = [];
                                                                                        app.use((req, res, next) => {
                                                                                            logs.push({ url: req.url, time: Date.now() });
                                                                                                next();
                                                                                                });
                                                                                                ```
                                                                                                
                                                                                                ## Bước 3: Heap Snapshot
                                                                                                
                                                                                                ```javascript
                                                                                                const v8 = require('v8');
                                                                                                const fs = require('fs');
                                                                                                
                                                                                                app.get('/debug/heapdump', (req, res) => {
                                                                                                    const snapshotStream = v8.writeHeapSnapshot();
                                                                                                        res.json({ file: snapshotStream });
                                                                                                        });
                                                                                                        ```
                                                                                                        
                                                                                                        Mở file `.heapsnapshot` trong Chrome DevTools:
                                                                                                        1. Chrome → F12 → Memory tab
                                                                                                        2. Load snapshot
                                                                                                        3. Sort by "Retained Size"
                                                                                                        4. Tìm object chiếm nhiều memory nhất
                                                                                                        
                                                                                                        ## Bước 4: Theo dõi trong production
                                                                                                        
                                                                                                        ```javascript
                                                                                                        // Log memory mỗi 30 giây
                                                                                                        setInterval(() => {
                                                                                                            const mem = process.memoryUsage();
                                                                                                                const heapMB = (mem.heapUsed / 1024 / 1024).toFixed(1);
                                                                                                                    if (mem.heapUsed > 400 * 1024 * 1024) {
                                                                                                                            console.warn(`HIGH MEMORY: ${heapMB}MB`);
                                                                                                                                }
                                                                                                                                }, 30000);
                                                                                                                                ```
                                                                                                                                
                                                                                                                                ## Quick Checklist
                                                                                                                                
                                                                                                                                - [ ] Event listeners có `removeListener` / `off` khi không cần?
                                                                                                                                - [ ] Cache có giới hạn size (LRU)?
                                                                                                                                - [ ] Closures có giữ reference không cần thiết?
                                                                                                                                - [ ] Global arrays/objects có clear định kỳ?
                                                                                                                                - [ ] Streams có properly close/destroy?
                                                                                                                                - [ ] setTimeout/setInterval có clearTimeout/clearInterval?
                                                                                                                                
                                                                                                                                ---
                                                                                                                                
                                                                                                                                Bạn đã gặp memory leak chưa? Nguyên nhân là gì?

All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí