New Relic APM入門 第5.3章 - コードレベルパフォーマンス分析

📖 ナビゲーション

メイン: 第5章 New Relic APM(高度化)
前セクション: 第5.2章 分散トレーシング
次セクション: 第6章 New Relic Log Management


💡 この章で学べること

従来のAPM監視は「どのサービスが遅いか」を教えてくれますが、「なぜ遅いのか」「どのコードが原因か」までは分からません。New RelicのCode-Level Metricsプロファイリング機能を使えば、関数レベルまで詳細に分析し、パフォーマンス問題の真の原因を特定できます。

学習目標

  • [ ] Code-Level Metrics:関数・メソッドレベルでの詳細パフォーマンス監視
  • [ ] プロファイリング:CPU使用率とメモリ使用量の詳細分析
  • [ ] メモリリーク検出:メモリ使用パターンの異常発見と対処法
  • [ ] ホットスポット特定:パフォーマンス問題の根本原因特定
  • [ ] 最適化戦略:コードレベルでの具体的改善手法
  • [ ] 継続的監視:パフォーマンス劣化の予防的検出システム

5.3.1 Code-Level Metricsの基本概念

コードレベル監視とは

Code-Level Metricsは、アプリケーション内の個別の関数やメソッドの実行時間、呼び出し回数、CPU使用率を追跡する機能です。これにより、「重いAPI」だけでなく、**「その中のどの処理が重いのか」**まで正確に特定できます。

従来監視 vs コードレベル監視

yaml
# 監視粒度の比較
Traditional_APM_Monitoring:
  granularity: "サービス・エンドポイント単位"
  example: 
    - "POST /checkout が遅い (3.2秒)"
    - "在庫サービスに問題あり"
  limitation: "どの処理が重いかは不明"

Code_Level_Monitoring:
  granularity: "関数・メソッド単位"
  example:
    - "POST /checkout が遅い (3.2秒)"
    - "└ inventory.checkStock() が遅い (2.8秒)"
    - "  └ database.executeQuery() が遅い (2.5秒)"
    - "    └ 'SELECT * FROM inventory WHERE...' が遅い"
  advantage: "ピンポイントで改善箇所を特定"

Code-Level Metricsが監視する内容

javascript
// New Relic Code-Level Metricsの監視対象例
const codeMetricsExample = {
    
    // 関数レベルメトリクス
    function_metrics: {
        function_name: "calculateShippingCost",
        file_path: "/src/services/shipping.js",
        line_number: 142,
        metrics: {
            call_count: 1247,               // 呼び出し回数
            total_time: "45.2s",            // 累計実行時間
            average_time: "36.2ms",         // 平均実行時間
            max_time: "340ms",              // 最大実行時間
            cpu_time: "42.1s",              // CPU使用時間
            wall_clock_time: "45.2s"        // 実時間
        },
        performance_score: 0.73             // パフォーマンススコア
    },
    
    // メソッドチェーン分析
    method_chain: [
        {
            method: "OrderService.processOrder",
            duration: "1.8s",
            self_time: "50ms",              // 自身の処理時間
            children_time: "1.75s"          // 子メソッドの処理時間
        },
        {
            method: "├─ PaymentService.processPayment", 
            duration: "800ms",
            self_time: "20ms",
            children_time: "780ms"
        },
        {
            method: "│  └─ StripeAPI.createCharge",
            duration: "780ms",
            self_time: "780ms",             // 外部API呼び出し
            children_time: "0ms"
        },
        {
            method: "├─ InventoryService.reserveItems",
            duration: "950ms", 
            self_time: "100ms",
            children_time: "850ms"
        },
        {
            method: "│  └─ DatabaseService.updateInventory",
            duration: "850ms",              // ← ボトルネック特定!
            self_time: "850ms",
            query: "UPDATE inventory SET quantity = quantity - ? WHERE..."
        }
    ]
};

New Relic でのCode-Level Metrics設定

Node.js実装

javascript
// newrelic.js 設定(Code-Level Metrics有効化)
'use strict';

exports.config = {
  app_name: ['ECommerce-API'],
  license_key: process.env.NEW_RELIC_LICENSE_KEY,
  
  // Code-Level Metrics 有効化
  code_level_metrics: {
    enabled: true
  },
  
  // 詳細トランザクション トレーシング
  transaction_tracer: {
    enabled: true,
    transaction_threshold: 0.1,     // 100ms以上の関数を詳細記録
    record_sql: 'raw',
    explain_enabled: true,
    explain_threshold: 0.5,
    stack_trace_threshold: 0.5      // 500ms以上でスタックトレース記録
  },
  
  // 関数レベル監視設定
  attributes: {
    enabled: true,
    include: [
      'request.*',
      'response.*',
      'custom.*'
    ]
  }
};

// app.js - 詳細な関数監視
require('newrelic');
const express = require('express');
const newrelic = require('newrelic');
const app = express();

// 在庫管理サービス(詳細監視対象)
class InventoryService {
    
    // 自動的にCode-Level Metricsで監視される
    async checkStock(items) {
        // 個別関数の詳細監視
        const validationResult = await this.validateItems(items);
        const availability = await this.checkAvailability(validationResult);
        const reservationResult = await this.createReservation(availability);
        
        return reservationResult;
    }
    
    // @newrelic.agent.function_trace() デコレータで手動監視
    @newrelic.agent.function_trace()
    async validateItems(items) {
        // バリデーション処理
        const startTime = Date.now();
        
        try {
            const validItems = [];
            
            for (const item of items) {
                // 各アイテムの詳細検証(監視対象)
                const validation = await this.validateSingleItem(item);
                
                if (validation.valid) {
                    validItems.push(validation.item);
                } else {
                    // 検証失敗の詳細記録
                    newrelic.addCustomAttribute(`validation.failed.${item.id}`, validation.reason);
                }
            }
            
            // 成功メトリクス
            const duration = Date.now() - startTime;
            newrelic.recordMetric('Custom/Inventory/Validation/Duration', duration);
            newrelic.recordMetric('Custom/Inventory/Validation/ItemCount', items.length);
            
            return validItems;
            
        } catch (error) {
            // エラー詳細(関数レベル)
            newrelic.noticeError(error, {
                'function': 'validateItems',
                'items_count': items.length,
                'processing_time': Date.now() - startTime
            });
            throw error;
        }
    }
    
    // CPU集約的処理の監視例
    async checkAvailability(validItems) {
        return await newrelic.startBackgroundTransaction('availability_check', async () => {
            
            const availabilityChecks = [];
            
            // 並列処理での詳細監視
            for (const item of validItems) {
                const checkPromise = this.checkSingleItemAvailability(item);
                availabilityChecks.push(checkPromise);
            }
            
            // Promise.all のパフォーマンス監視
            const startTime = Date.now();
            const results = await Promise.all(availabilityChecks);
            const parallelProcessingTime = Date.now() - startTime;
            
            // 並列処理効率メトリクス
            newrelic.recordMetric('Custom/Inventory/Parallel/Duration', parallelProcessingTime);
            newrelic.recordMetric('Custom/Inventory/Parallel/Efficiency', 
                (validItems.length * 100) / parallelProcessingTime); // items/second * 100
            
            return results;
        });
    }
    
    // データベース集約処理の詳細監視
    async checkSingleItemAvailability(item) {
        // データベースクエリの詳細分析
        const queryStartTime = Date.now();
        
        try {
            // 複雑な在庫チェッククエリ(自動監視)
            const query = `
                SELECT 
                    i.quantity,
                    i.reserved,
                    w.available_quantity,
                    p.reorder_point
                FROM inventory i
                JOIN warehouses w ON i.warehouse_id = w.id
                JOIN products p ON i.product_id = p.id
                WHERE i.product_id = ? 
                AND i.warehouse_id IN (
                    SELECT id FROM warehouses 
                    WHERE region = ? 
                    AND status = 'active'
                )
            `;
            
            const result = await database.query(query, [item.product_id, item.region]);
            
            // クエリパフォーマンス記録
            const queryDuration = Date.now() - queryStartTime;
            newrelic.recordMetric('Custom/Database/InventoryCheck/Duration', queryDuration);
            
            // 遅いクエリのアラート
            if (queryDuration > 200) {  // 200ms以上
                newrelic.recordMetric('Custom/Database/InventoryCheck/SlowQuery', 1);
                newrelic.addCustomAttribute('slow_query.product_id', item.product_id);
                newrelic.addCustomAttribute('slow_query.duration', queryDuration);
            }
            
            // ビジネスロジックの処理時間監視
            const businessLogicStart = Date.now();
            const availability = this.calculateAvailability(result[0]);
            const businessLogicDuration = Date.now() - businessLogicStart;
            
            newrelic.recordMetric('Custom/BusinessLogic/AvailabilityCalc/Duration', businessLogicDuration);
            
            return availability;
            
        } catch (error) {
            const queryDuration = Date.now() - queryStartTime;
            
            newrelic.noticeError(error, {
                'function': 'checkSingleItemAvailability',
                'product_id': item.product_id,
                'query_duration': queryDuration,
                'database_error': error.code
            });
            
            throw error;
        }
    }
    
    // メモリ使用量監視
    calculateAvailability(inventoryData) {
        const memUsageBefore = process.memoryUsage();
        
        try {
            // 複雑な在庫計算ロジック
            const calculation = {
                available: inventoryData.quantity - inventoryData.reserved,
                buffer: inventoryData.reorder_point,
                warehouse_stock: inventoryData.available_quantity
            };
            
            // 在庫状況の詳細判定
            let status = 'available';
            if (calculation.available <= 0) {
                status = 'out_of_stock';
            } else if (calculation.available <= calculation.buffer) {
                status = 'low_stock';
            }
            
            // メモリ使用量の変化監視
            const memUsageAfter = process.memoryUsage();
            const memoryDelta = {
                heapUsed: memUsageAfter.heapUsed - memUsageBefore.heapUsed,
                heapTotal: memUsageAfter.heapTotal - memUsageBefore.heapTotal,
                external: memUsageAfter.external - memUsageBefore.external
            };
            
            // メモリ使用量メトリクス
            newrelic.recordMetric('Custom/Memory/AvailabilityCalc/HeapUsed', memoryDelta.heapUsed);
            
            // メモリリークの早期警告
            if (memoryDelta.heapUsed > 1024 * 1024) {  // 1MB以上の増加
                newrelic.recordMetric('Custom/Memory/PotentialLeak', 1);
                newrelic.addCustomAttribute('memory.large_allocation', memoryDelta.heapUsed);
            }
            
            return {
                ...calculation,
                status: status,
                calculated_at: new Date().toISOString()
            };
            
        } catch (error) {
            newrelic.noticeError(error, {
                'function': 'calculateAvailability',
                'inventory_data': JSON.stringify(inventoryData)
            });
            throw error;
        }
    }
}

Python実装

python
# Python でのCode-Level Metrics実装
import newrelic.agent
import time
import psutil
import os
from memory_profiler import profile

# newrelic.ini または環境変数で設定
# code_level_metrics.enabled = true

class OrderProcessor:
    
    def __init__(self):
        self.processed_orders = 0
        
    @newrelic.agent.function_trace()  # 関数レベル監視有効化
    def process_order(self, order_data):
        """注文処理メインロジック"""
        
        # ビジネスコンテキスト追加
        newrelic.agent.add_custom_attribute('order.id', order_data.get('id'))
        newrelic.agent.add_custom_attribute('order.items_count', len(order_data.get('items', [])))
        
        start_time = time.time()
        
        try:
            # ステップ1:注文バリデーション(詳細監視)
            validation_result = self.validate_order(order_data)
            
            # ステップ2:在庫引当(詳細監視)
            inventory_result = self.reserve_inventory(order_data['items'])
            
            # ステップ3:価格計算(詳細監視)
            pricing_result = self.calculate_pricing(order_data, inventory_result)
            
            # ステップ4:注文保存(詳細監視)
            order_record = self.save_order(order_data, pricing_result)
            
            # 成功メトリクス
            processing_time = (time.time() - start_time) * 1000  # ミリ秒
            newrelic.agent.record_custom_metric('Custom/Order/ProcessingTime', processing_time)
            newrelic.agent.record_custom_metric('Custom/Order/Success', 1)
            
            self.processed_orders += 1
            
            return order_record
            
        except Exception as e:
            # エラー詳細(関数コンテキスト付き)
            processing_time = (time.time() - start_time) * 1000
            
            newrelic.agent.notice_error(attributes={
                'function': 'process_order',
                'order_id': order_data.get('id'),
                'processing_time': processing_time,
                'step_failed': getattr(e, 'step', 'unknown')
            })
            
            raise e
    
    @newrelic.agent.function_trace('order_validation')
    def validate_order(self, order_data):
        """注文データバリデーション(CPU集約的処理)"""
        
        validation_start = time.time()
        
        # CPUプロファイリング対象処理
        errors = []
        
        # 顧客情報検証
        customer_validation_time = time.time()
        if not self.validate_customer(order_data.get('customer', {})):
            errors.append('Invalid customer data')
        customer_validation_duration = (time.time() - customer_validation_time) * 1000
        
        # 商品情報検証(重い処理)
        items_validation_time = time.time()
        for item in order_data.get('items', []):
            if not self.validate_item(item):
                errors.append(f'Invalid item: {item.get("id")}')
        items_validation_duration = (time.time() - items_validation_time) * 1000
        
        # 配送先検証
        shipping_validation_time = time.time()
        if not self.validate_shipping_address(order_data.get('shipping_address', {})):
            errors.append('Invalid shipping address')
        shipping_validation_duration = (time.time() - shipping_validation_time) * 1000
        
        # バリデーション詳細メトリクス
        newrelic.agent.record_custom_metric('Custom/Validation/Customer/Duration', customer_validation_duration)
        newrelic.agent.record_custom_metric('Custom/Validation/Items/Duration', items_validation_duration)
        newrelic.agent.record_custom_metric('Custom/Validation/Shipping/Duration', shipping_validation_duration)
        
        total_validation_time = (time.time() - validation_start) * 1000
        newrelic.agent.record_custom_metric('Custom/Validation/Total/Duration', total_validation_time)
        
        if errors:
            validation_error = ValueError(f'Validation failed: {", ".join(errors)}')
            validation_error.step = 'validation'
            raise validation_error
        
        return {'valid': True, 'validation_time': total_validation_time}
    
    @newrelic.agent.function_trace('inventory_reservation')
    def reserve_inventory(self, items):
        """在庫引当処理(データベース集約的処理)"""
        
        reservation_start = time.time()
        reservations = []
        
        # メモリ使用量監視開始
        process = psutil.Process(os.getpid())
        memory_before = process.memory_info().rss
        
        try:
            for item in items:
                # データベース処理(自動監視)
                item_reservation_start = time.time()
                
                reservation = self.reserve_single_item(item)
                reservations.append(reservation)
                
                # アイテム別処理時間
                item_duration = (time.time() - item_reservation_start) * 1000
                newrelic.agent.record_custom_metric('Custom/Inventory/SingleItem/Duration', item_duration)
                
                # 遅い処理の詳細記録
                if item_duration > 100:  # 100ms以上
                    newrelic.agent.add_custom_attribute(f'slow_reservation.item_{item.get("id")}', item_duration)
            
            # メモリ使用量変化
            memory_after = process.memory_info().rss
            memory_delta = memory_after - memory_before
            
            newrelic.agent.record_custom_metric('Custom/Memory/InventoryReservation', memory_delta)
            
            # メモリリーク検出
            if memory_delta > 10 * 1024 * 1024:  # 10MB以上増加
                newrelic.agent.record_custom_metric('Custom/Memory/PotentialLeak', 1)
                newrelic.agent.add_custom_attribute('memory.large_increase', memory_delta)
            
            total_reservation_time = (time.time() - reservation_start) * 1000
            newrelic.agent.record_custom_metric('Custom/Inventory/TotalReservation/Duration', total_reservation_time)
            
            return {'reservations': reservations, 'processing_time': total_reservation_time}
            
        except Exception as e:
            newrelic.agent.notice_error(attributes={
                'function': 'reserve_inventory',
                'items_count': len(items),
                'processed_items': len(reservations),
                'memory_delta': memory_after - memory_before if 'memory_after' in locals() else 0
            })
            raise e
    
    @profile  # memory_profiler による詳細メモリ分析
    @newrelic.agent.function_trace('pricing_calculation')
    def calculate_pricing(self, order_data, inventory_result):
        """価格計算(メモリ集約的処理)"""
        
        # 大量のデータ処理によるメモリ使用パターン分析
        pricing_data = {}
        large_calculation_cache = {}  # メモリ使用量監視対象
        
        try:
            # 基本価格計算
            base_prices = []
            for item in order_data['items']:
                # 価格履歴データの大量読み込み(メモリ使用増加要因)
                price_history = self.get_price_history(item['id'])  # 大量データ
                
                current_price = self.calculate_current_price(item, price_history)
                base_prices.append(current_price)
                
                # キャッシュへの蓄積(メモリリーク可能性)
                large_calculation_cache[item['id']] = price_history
            
            # 割引計算(CPU集約的)
            discount_calculation_start = time.time()
            
            discount_result = self.calculate_discounts(order_data, base_prices)
            
            discount_duration = (time.time() - discount_calculation_start) * 1000
            newrelic.agent.record_custom_metric('Custom/Pricing/Discount/Duration', discount_duration)
            
            # 税額計算
            tax_result = self.calculate_taxes(order_data, discount_result)
            
            # 配送料計算
            shipping_cost = self.calculate_shipping_cost(order_data['shipping_address'])
            
            # 最終価格
            final_pricing = {
                'subtotal': sum(base_prices),
                'discount': discount_result['total_discount'],
                'tax': tax_result['total_tax'],
                'shipping': shipping_cost,
                'total': sum(base_prices) - discount_result['total_discount'] + tax_result['total_tax'] + shipping_cost
            }
            
            # キャッシュクリア(メモリリーク防止)
            large_calculation_cache.clear()
            
            return final_pricing
            
        except Exception as e:
            # メモリ状況と共にエラー記録
            newrelic.agent.notice_error(attributes={
                'function': 'calculate_pricing',
                'cache_size': len(large_calculation_cache),
                'memory_info': str(psutil.Process(os.getpid()).memory_info())
            })
            raise e

Java実装

java
// Java でのCode-Level Metrics実装
import com.newrelic.api.agent.Trace;
import com.newrelic.api.agent.NewRelic;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.util.List;

@Component
public class OrderProcessorService {
    
    private final MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
    
    @Trace(dispatcher = true)  // トランザクションエントリーポイント
    public OrderResult processOrder(OrderRequest request) {
        // ビジネスコンテキスト
        NewRelic.addCustomAttribute("order.id", request.getOrderId());
        NewRelic.addCustomAttribute("order.items", request.getItems().size());
        
        long startTime = System.currentTimeMillis();
        
        try {
            // 処理ステップの詳細監視
            ValidationResult validation = validateOrder(request);
            InventoryResult inventory = reserveInventory(request.getItems());
            PricingResult pricing = calculatePricing(request, inventory);
            OrderResult order = saveOrder(request, pricing);
            
            // 成功メトリクス
            long processingTime = System.currentTimeMillis() - startTime;
            NewRelic.recordMetric("Custom/Order/ProcessingTime", processingTime);
            NewRelic.recordMetric("Custom/Order/Success", 1);
            
            return order;
            
        } catch (Exception e) {
            // エラー詳細
            NewRelic.noticeError(e, Map.of(
                "function", "processOrder",
                "orderId", request.getOrderId(),
                "processingTime", System.currentTimeMillis() - startTime
            ));
            throw e;
        }
    }
    
    @Trace(metricName = "Custom/Order/Validation")  // カスタムメトリクス名
    private ValidationResult validateOrder(OrderRequest request) {
        long startTime = System.currentTimeMillis();
        
        // CPU使用率監視
        long cpuStart = getCpuTime();
        
        try {
            // 複雑なバリデーションロジック
            List<ValidationError> errors = new ArrayList<>();
            
            // 顧客検証(詳細監視)
            if (!validateCustomer(request.getCustomer())) {
                errors.add(new ValidationError("Invalid customer"));
            }
            
            // 商品検証(CPU集約的処理)
            for (OrderItem item : request.getItems()) {
                if (!validateItem(item)) {
                    errors.add(new ValidationError("Invalid item: " + item.getId()));
                }
            }
            
            // 配送先検証
            if (!validateShippingAddress(request.getShippingAddress())) {
                errors.add(new ValidationError("Invalid shipping address"));
            }
            
            // バリデーション結果メトリクス
            long validationTime = System.currentTimeMillis() - startTime;
            long cpuUsed = getCpuTime() - cpuStart;
            
            NewRelic.recordMetric("Custom/Validation/Duration", validationTime);
            NewRelic.recordMetric("Custom/Validation/CpuTime", cpuUsed);
            NewRelic.recordMetric("Custom/Validation/ItemCount", request.getItems().size());
            
            if (!errors.isEmpty()) {
                ValidationException e = new ValidationException("Validation failed");
                e.setErrors(errors);
                throw e;
            }
            
            return new ValidationResult(true, validationTime);
            
        } catch (Exception e) {
            NewRelic.noticeError(e, Map.of(
                "function", "validateOrder",
                "itemCount", request.getItems().size(),
                "validationTime", System.currentTimeMillis() - startTime
            ));
            throw e;
        }
    }
    
    @Trace(metricName = "Custom/Inventory/Reservation")
    private InventoryResult reserveInventory(List<OrderItem> items) {
        long startTime = System.currentTimeMillis();
        
        // メモリ使用量監視開始
        long memoryBefore = memoryMXBean.getHeapMemoryUsage().getUsed();
        
        try {
            List<InventoryReservation> reservations = new ArrayList<>();
            
            for (OrderItem item : items) {
                // 個別アイテム処理時間監視
                long itemStartTime = System.currentTimeMillis();
                
                InventoryReservation reservation = reserveSingleItem(item);
                reservations.add(reservation);
                
                long itemDuration = System.currentTimeMillis() - itemStartTime;
                NewRelic.recordMetric("Custom/Inventory/SingleItem", itemDuration);
                
                // 遅い処理の詳細記録
                if (itemDuration > 100) {  // 100ms以上
                    NewRelic.addCustomAttribute("slowReservation.item." + item.getId(), itemDuration);
                }
            }
            
            // メモリ使用量変化監視
            long memoryAfter = memoryMXBean.getHeapMemoryUsage().getUsed();
            long memoryDelta = memoryAfter - memoryBefore;
            
            NewRelic.recordMetric("Custom/Memory/InventoryReservation", memoryDelta);
            
            // メモリリーク検出
            if (memoryDelta > 10 * 1024 * 1024) {  // 10MB以上
                NewRelic.recordMetric("Custom/Memory/PotentialLeak", 1);
                NewRelic.addCustomAttribute("memory.largeIncrease", memoryDelta);
            }
            
            long totalTime = System.currentTimeMillis() - startTime;
            NewRelic.recordMetric("Custom/Inventory/TotalReservation", totalTime);
            
            return new InventoryResult(reservations, totalTime);
            
        } catch (Exception e) {
            long memoryAfter = memoryMXBean.getHeapMemoryUsage().getUsed();
            
            NewRelic.noticeError(e, Map.of(
                "function", "reserveInventory",
                "itemCount", items.size(),
                "memoryDelta", memoryAfter - memoryBefore
            ));
            throw e;
        }
    }
    
    @Trace(metricName = "Custom/Pricing/Calculation")
    private PricingResult calculatePricing(OrderRequest request, InventoryResult inventory) {
        // 複雑な価格計算の詳細監視
        
        long startTime = System.currentTimeMillis();
        
        // メモリ集約的処理
        Map<String, List<PriceHistory>> priceCache = new HashMap<>();
        
        try {
            // 基本価格計算
            BigDecimal subtotal = BigDecimal.ZERO;
            
            for (OrderItem item : request.getItems()) {
                // 価格履歴取得(メモリ使用増加)
                List<PriceHistory> history = getPriceHistory(item.getId());
                priceCache.put(item.getId(), history);  // キャッシュに蓄積
                
                BigDecimal itemPrice = calculateItemPrice(item, history);
                subtotal = subtotal.add(itemPrice);
            }
            
            // 割引計算(CPU集約的)
            long discountStartTime = System.currentTimeMillis();
            DiscountResult discount = calculateDiscounts(request, subtotal);
            long discountDuration = System.currentTimeMillis() - discountStartTime;
            
            NewRelic.recordMetric("Custom/Pricing/Discount/Duration", discountDuration);
            
            // 税計算
            TaxResult tax = calculateTaxes(request, discount);
            
            // 配送料計算  
            BigDecimal shipping = calculateShipping(request.getShippingAddress());
            
            // 最終価格
            BigDecimal total = subtotal
                .subtract(discount.getAmount())
                .add(tax.getAmount())
                .add(shipping);
            
            // キャッシュクリア(メモリリーク防止)
            priceCache.clear();
            
            long totalTime = System.currentTimeMillis() - startTime;
            NewRelic.recordMetric("Custom/Pricing/Total/Duration", totalTime);
            
            return new PricingResult(subtotal, discount.getAmount(), tax.getAmount(), shipping, total);
            
        } catch (Exception e) {
            NewRelic.noticeError(e, Map.of(
                "function", "calculatePricing",
                "cacheSize", priceCache.size(),
                "processingTime", System.currentTimeMillis() - startTime
            ));
            throw e;
        }
    }
    
    // ユーティリティメソッド
    private long getCpuTime() {
        return ManagementFactory.getThreadMXBean().getCurrentThreadCpuTime() / 1_000_000; // ms
    }
}

5.3.2 プロファイリングによる詳細分析

New Relic プロファイリング機能

プロファイリングは、アプリケーション実行中のCPU使用率とメモリ使用パターンを継続的にサンプリングし、パフォーマンスのホットスポットを特定する技術です。

プロファイリングの種類と用途

yaml
# プロファイリング手法の比較
CPU_Profiling:
  purpose: "CPU使用率の高い関数・処理の特定"
  measurement: "CPU時間の消費パターン"
  use_cases:
    - "レスポンス時間が遅い原因特定"
    - "CPU集約的処理の最適化"
    - "アルゴリズム効率性評価"
  example: "画像処理、データ変換、複雑計算処理"

Memory_Profiling:
  purpose: "メモリ使用量とメモリリークの検出"
  measurement: "ヒープメモリの割り当て・解放パターン"
  use_cases:
    - "メモリリーク検出"
    - "ガベージコレクション最適化"
    - "メモリ使用効率改善"
  example: "大量データ処理、キャッシュ管理、セッション管理"

Thread_Profiling:
  purpose: "スレッド利用効率とデッドロック検出"
  measurement: "スレッドの状態とブロック時間"
  use_cases:
    - "並行処理の最適化"
    - "デッドロック問題解決"
    - "スレッドプール効率化"
  example: "並列データ処理、非同期処理、WebSocket通信"

実践的プロファイリング実装

Node.js パフォーマンスプロファイリング

javascript
// Node.js プロファイリング統合実装
const newrelic = require('newrelic');
const v8Profiler = require('v8-profiler-next');
const fs = require('fs').promises;

class PerformanceProfiler {
    
    constructor() {
        this.activeProfiles = new Map();
        this.samplingInterval = 1000; // 1秒間隔
        this.setupContinuousProfiler();
    }
    
    setupContinuousProfiler() {
        """継続的プロファイリングセットアップ"""
        
        // CPU使用率監視
        setInterval(() => {
            this.sampleCpuUsage();
        }, this.samplingInterval);
        
        // メモリ使用量監視
        setInterval(() => {
            this.sampleMemoryUsage();
        }, this.samplingInterval);
        
        // ガベージコレクション監視
        this.setupGCMonitoring();
    }
    
    sampleCpuUsage() {
        """CPU使用率サンプリング"""
        
        const cpuUsage = process.cpuUsage();
        const memUsage = process.memoryUsage();
        
        // New Relic メトリクス記録
        newrelic.recordMetric('Custom/Performance/CPU/User', cpuUsage.user / 1000); // マイクロ秒 → ミリ秒
        newrelic.recordMetric('Custom/Performance/CPU/System', cpuUsage.system / 1000);
        newrelic.recordMetric('Custom/Performance/Memory/HeapUsed', memUsage.heapUsed);
        newrelic.recordMetric('Custom/Performance/Memory/HeapTotal', memUsage.heapTotal);
        newrelic.recordMetric('Custom/Performance/Memory/RSS', memUsage.rss);
        
        // 高CPU使用率アラート
        const totalCpu = (cpuUsage.user + cpuUsage.system) / 1000;
        if (totalCpu > 1000) {  // 1秒以上のCPU時間消費
            newrelic.recordMetric('Custom/Performance/CPU/HighUsage', 1);
            this.triggerCpuProfileCapture();
        }
    }
    
    sampleMemoryUsage() {
        """メモリ使用パターン分析"""
        
        const memUsage = process.memoryUsage();
        
        // メモリリーク検出ロジック
        const currentHeapUsed = memUsage.heapUsed;
        const currentTime = Date.now();
        
        if (!this.memoryBaseline) {
            this.memoryBaseline = { heap: currentHeapUsed, time: currentTime };
            return;
        }
        
        const timeDiff = currentTime - this.memoryBaseline.time;
        const heapDiff = currentHeapUsed - this.memoryBaseline.heap;
        
        // 30分間で100MB以上の継続的増加 = メモリリーク疑い
        if (timeDiff > 30 * 60 * 1000 && heapDiff > 100 * 1024 * 1024) {
            newrelic.recordMetric('Custom/Performance/Memory/PotentialLeak', 1);
            newrelic.addCustomAttribute('memory.leak_rate_mb_per_hour', 
                (heapDiff / 1024 / 1024) / (timeDiff / 60 / 60 / 1000));
                
            this.triggerMemoryProfileCapture();
            
            // ベースライン更新
            this.memoryBaseline = { heap: currentHeapUsed, time: currentTime };
        }
    }
    
    triggerCpuProfileCapture() {
        """CPU プロファイル取得"""
        
        const profileId = `cpu-profile-${Date.now()}`;
        
        // V8 CPU プロファイリング開始
        v8Profiler.startProfiling(profileId, true);
        
        // 10秒後に停止・解析
        setTimeout(async () => {
            try {
                const profile = v8Profiler.stopProfiling(profileId);
                const profileData = JSON.stringify(profile);
                
                // プロファイルデータ解析
                const analysis = this.analyzeCpuProfile(profile);
                
                // New Relic カスタムイベント送信
                newrelic.recordCustomEvent('CPUProfileCaptured', {
                    profileId: profileId,
                    captureTime: new Date().toISOString(),
                    hotFunctions: JSON.stringify(analysis.hotFunctions),
                    totalSamples: analysis.totalSamples,
                    topCpuConsumer: analysis.topFunction
                });
                
                // プロファイルファイル保存(オプション)
                await fs.writeFile(`/tmp/profiles/${profileId}.cpuprofile`, profileData);
                
                console.log(`CPU profile captured: ${profileId}`);
                
            } catch (error) {
                newrelic.noticeError(error, { context: 'cpu_profile_capture' });
            }
        }, 10000);
    }
    
    triggerMemoryProfileCapture() {
        """メモリ プロファイル取得"""
        
        const profileId = `memory-profile-${Date.now()}`;
        
        try {
            // ヒープスナップショット取得
            const snapshot = v8Profiler.takeSnapshot(profileId);
            
            // メモリ使用状況解析
            const analysis = this.analyzeMemorySnapshot(snapshot);
            
            // New Relic カスタムイベント送信
            newrelic.recordCustomEvent('MemoryProfileCaptured', {
                profileId: profileId,
                captureTime: new Date().toISOString(),
                totalObjects: analysis.totalObjects,
                largestObjects: JSON.stringify(analysis.largestObjects),
                suspiciousLeaks: JSON.stringify(analysis.suspiciousLeaks)
            });
            
            // メモリスナップショット解放
            snapshot.delete();
            
            console.log(`Memory profile captured: ${profileId}`);
            
        } catch (error) {
            newrelic.noticeError(error, { context: 'memory_profile_capture' });
        }
    }
    
    analyzeCpuProfile(profile) {
        """CPU プロファイル解析"""
        
        const functionStats = new Map();
        let totalSamples = 0;
        
        // プロファイルノード解析
        function traverseNode(node) {
            if (node.functionName && node.functionName !== '(root)') {
                const key = `${node.functionName}:${node.url}:${node.lineNumber}`;
                
                if (!functionStats.has(key)) {
                    functionStats.set(key, {
                        functionName: node.functionName,
                        url: node.url,
                        lineNumber: node.lineNumber,
                        hitCount: 0,
                        selfTime: 0
                    });
                }
                
                const stats = functionStats.get(key);
                stats.hitCount += node.hitCount;
                stats.selfTime += node.selfTime || 0;
                totalSamples += node.hitCount;
            }
            
            // 子ノード解析
            if (node.children) {
                node.children.forEach(traverseNode);
            }
        }
        
        traverseNode(profile.head);
        
        // CPU使用率上位関数特定
        const sortedFunctions = Array.from(functionStats.values())
            .sort((a, b) => b.hitCount - a.hitCount)
            .slice(0, 10);
        
        return {
            totalSamples: totalSamples,
            hotFunctions: sortedFunctions,
            topFunction: sortedFunctions[0]?.functionName || 'unknown'
        };
    }
    
    analyzeMemorySnapshot(snapshot) {
        """メモリスナップショット解析"""
        
        // 注意:実際の実装では v8-profiler-next の詳細API使用
        // ここでは概念的な解析例を示す
        
        const analysis = {
            totalObjects: 0,
            largestObjects: [],
            suspiciousLeaks: []
        };
        
        // スナップショット内のオブジェクト分析
        // (実装は v8-profiler-next のドキュメント参照)
        
        return analysis;
    }
    
    setupGCMonitoring() {
        """ガベージコレクション監視"""
        
        // GCイベント監視(Node.js v14+)
        if (process.env.NODE_ENV === 'production') {
            const { PerformanceObserver } = require('perf_hooks');
            
            const gcObserver = new PerformanceObserver((list) => {
                const entries = list.getEntries();
                
                entries.forEach(entry => {
                    if (entry.entryType === 'gc') {
                        // GC詳細記録
                        newrelic.recordMetric(`Custom/GC/${entry.detail.kind}/Duration`, entry.duration);
                        newrelic.recordMetric(`Custom/GC/${entry.detail.kind}/Count`, 1);
                        
                        // 長時間GCのアラート
                        if (entry.duration > 100) {  // 100ms以上
                            newrelic.recordMetric('Custom/GC/LongPause', 1);
                            newrelic.addCustomAttribute('gc.long_pause_duration', entry.duration);
                        }
                    }
                });
            });
            
            gcObserver.observe({ entryTypes: ['gc'] });
        }
    }
}

// 使用例:Express アプリケーションでの統合
const app = express();
const profiler = new PerformanceProfiler();

// CPU集約的エンドポイント(プロファイリング対象)
app.post('/api/heavy-computation', async (req, res) => {
    const startTime = Date.now();
    
    try {
        // 重い計算処理(プロファイリング対象)
        const result = await performHeavyComputation(req.body.data);
        
        const processingTime = Date.now() - startTime;
        
        // パフォーマンスメトリクス
        newrelic.recordMetric('Custom/HeavyComputation/Duration', processingTime);
        
        res.json({
            result: result,
            processingTime: processingTime
        });
        
    } catch (error) {
        newrelic.noticeError(error);
        res.status(500).json({ error: error.message });
    }
});

async function performHeavyComputation(data) {
    // CPU集約的処理例
    let result = 0;
    
    for (let i = 0; i < data.iterations; i++) {
        // 複雑な数値計算
        result += Math.sqrt(Math.pow(data.values[i % data.values.length], 2.7));
        
        // メモリ使用量増加処理
        if (i % 10000 === 0) {
            // 中間結果の保存(メモリ使用量監視対象)
            const intermediateResults = new Array(1000).fill(result);
            // ... 処理続行
        }
    }
    
    return result;
}

Python メモリプロファイリング

python
# Python メモリリーク検出・分析
import newrelic.agent
import tracemalloc
import gc
import psutil
import os
import time
from memory_profiler import profile
from pympler import tracker, muppy, summary

class MemoryProfiler:
    
    def __init__(self):
        self.process = psutil.Process(os.getpid())
        self.memory_tracker = tracker.SummaryTracker()
        self.baseline_memory = None
        
        # メモリトレースマロック有効化
        tracemalloc.start(10)  # 10フレームまでスタックトレース保存
        
        self.setup_continuous_monitoring()
    
    def setup_continuous_monitoring(self):
        """継続的メモリ監視セットアップ"""
        
        # 定期的メモリチェック(30秒間隔)
        import threading
        self.monitoring_timer = threading.Timer(30.0, self.periodic_memory_check)
        self.monitoring_timer.daemon = True
        self.monitoring_timer.start()
    
    def periodic_memory_check(self):
        """定期メモリチェック"""
        
        try:
            current_memory = self.process.memory_info().rss
            
            # ベースライン設定
            if self.baseline_memory is None:
                self.baseline_memory = current_memory
                return
            
            # メモリ増加量チェック
            memory_increase = current_memory - self.baseline_memory
            memory_increase_mb = memory_increase / 1024 / 1024
            
            # New Relic メトリクス記録
            newrelic.agent.record_custom_metric('Custom/Memory/RSS/Current', current_memory)
            newrelic.agent.record_custom_metric('Custom/Memory/RSS/Increase', memory_increase)
            
            # メモリリーク疑いの検出
            if memory_increase_mb > 50:  # 50MB以上の増加
                newrelic.agent.record_custom_metric('Custom/Memory/PotentialLeak/Detected', 1)
                
                # 詳細分析実行
                self.analyze_memory_leak()
                
                # ベースライン更新(継続監視のため)
                self.baseline_memory = current_memory
            
        except Exception as e:
            newrelic.agent.notice_error(e, attributes={'context': 'periodic_memory_check'})
        
        finally:
            # 次回チェックのスケジュール
            self.monitoring_timer = threading.Timer(30.0, self.periodic_memory_check)
            self.monitoring_timer.daemon = True
            self.monitoring_timer.start()
    
    @newrelic.agent.function_trace('memory_leak_analysis')
    def analyze_memory_leak(self):
        """メモリリーク詳細分析"""
        
        analysis_start = time.time()
        
        try:
            # 1. tracemalloc による分析
            snapshot = tracemalloc.take_snapshot()
            top_stats = snapshot.statistics('lineno')
            
            leak_suspects = []
            
            # メモリ使用量上位10ファイル・行を分析
            for index, stat in enumerate(top_stats[:10]):
                leak_suspects.append({
                    'rank': index + 1,
                    'filename': stat.traceback.filename,
                    'lineno': stat.traceback.lineno,
                    'size_mb': stat.size / 1024 / 1024,
                    'count': stat.count
                })
            
            # 2. オブジェクト統計分析
            all_objects = muppy.get_objects()
            object_summary = summary.summarize(all_objects)
            
            large_object_types = []
            for row in object_summary[:5]:  # 上位5つのオブジェクトタイプ
                large_object_types.append({
                    'type': str(row[0]),
                    'count': row[1],
                    'total_size': row[2]
                })
            
            # 3. ガベージコレクション統計
            gc_stats = gc.get_stats()
            
            # New Relic カスタムイベント送信
            newrelic.agent.record_custom_event('MemoryLeakAnalysis', {
                'analysis_time': time.time() - analysis_start,
                'top_memory_consumers': str(leak_suspects[:3]),
                'large_object_types': str(large_object_types),
                'gc_generation_0': gc_stats[0]['collections'] if gc_stats else 0,
                'gc_generation_1': gc_stats[1]['collections'] if len(gc_stats) > 1 else 0,
                'gc_generation_2': gc_stats[2]['collections'] if len(gc_stats) > 2 else 0
            })
            
            # メモリダンプ生成(本番環境では慎重に実行)
            if os.environ.get('ENABLE_MEMORY_DUMPS', 'false').lower() == 'true':
                self.generate_memory_dump(leak_suspects, large_object_types)
            
            print(f"Memory leak analysis completed in {time.time() - analysis_start:.2f}s")
            
        except Exception as e:
            newrelic.agent.notice_error(e, attributes={'context': 'memory_leak_analysis'})
    
    def generate_memory_dump(self, leak_suspects, large_object_types):
        """メモリダンプファイル生成"""
        
        timestamp = int(time.time())
        dump_filename = f"/tmp/memory_dump_{timestamp}.txt"
        
        try:
            with open(dump_filename, 'w') as f:
                f.write(f"Memory Dump - {time.strftime('%Y-%m-%d %H:%M:%S')}\n")
                f.write("=" * 50 + "\n\n")
                
                # メモリ使用量上位箇所
                f.write("Top Memory Consumers:\n")
                for suspect in leak_suspects:
                    f.write(f"{suspect['rank']}. {suspect['filename']}:{suspect['lineno']} "
                           f"- {suspect['size_mb']:.2f}MB ({suspect['count']} objects)\n")
                
                f.write("\n" + "-" * 30 + "\n")
                
                # 大きなオブジェクトタイプ
                f.write("Large Object Types:\n")
                for obj_type in large_object_types:
                    f.write(f"- {obj_type['type']}: {obj_type['count']} objects, "
                           f"{obj_type['total_size']} bytes\n")
            
            # ダンプファイル生成成功
            newrelic.agent.record_custom_metric('Custom/Memory/DumpGenerated', 1)
            
        except Exception as e:
            newrelic.agent.notice_error(e, attributes={
                'context': 'memory_dump_generation',
                'dump_filename': dump_filename
            })

# 使用例:メモリ集約的処理での統合
profiler = MemoryProfiler()

@profile  # memory_profiler デコレータ
@newrelic.agent.function_trace('data_processing')
def process_large_dataset(dataset):
    """大量データ処理(メモリリーク監視対象)"""
    
    processing_start = time.time()
    
    # メモリ使用量ベースライン
    initial_memory = psutil.Process().memory_info().rss
    
    try:
        # データ処理バッファ(メモリリーク可能性あり)
        processing_buffer = {}
        results = []
        
        for batch_num, batch in enumerate(dataset.batches()):
            batch_start = time.time()
            
            # バッチ処理(メモリ使用量増加)
            batch_result = process_batch(batch)
            
            # 中間結果をバッファに保存(潜在的メモリリーク源)
            processing_buffer[f'batch_{batch_num}'] = {
                'intermediate_data': batch_result,
                'metadata': generate_metadata(batch),
                'timestamp': time.time()
            }
            
            results.append(batch_result)
            
            batch_duration = (time.time() - batch_start) * 1000
            newrelic.agent.record_custom_metric('Custom/DataProcessing/BatchDuration', batch_duration)
            
            # 定期的なメモリチェック
            if batch_num % 100 == 0:
                current_memory = psutil.Process().memory_info().rss
                memory_increase = current_memory - initial_memory
                
                newrelic.agent.record_custom_metric('Custom/DataProcessing/MemoryIncrease', memory_increase)
                
                # メモリ使用量が異常に増加している場合
                if memory_increase > 100 * 1024 * 1024:  # 100MB以上
                    newrelic.agent.record_custom_metric('Custom/DataProcessing/HighMemoryUsage', 1)
                    
                    # バッファクリア(メモリリーク対策)
                    old_buffer_size = len(processing_buffer)
                    processing_buffer = {k: v for k, v in processing_buffer.items() 
                                       if time.time() - v['timestamp'] < 300}  # 5分以内のデータのみ保持
                    
                    cleared_entries = old_buffer_size - len(processing_buffer)
                    newrelic.agent.record_custom_metric('Custom/DataProcessing/BufferCleared', cleared_entries)
        
        # 最終メモリ使用量
        final_memory = psutil.Process().memory_info().rss
        total_memory_increase = final_memory - initial_memory
        
        processing_duration = (time.time() - processing_start) * 1000
        
        # 処理完了メトリクス
        newrelic.agent.record_custom_metric('Custom/DataProcessing/TotalDuration', processing_duration)
        newrelic.agent.record_custom_metric('Custom/DataProcessing/TotalMemoryIncrease', total_memory_increase)
        newrelic.agent.record_custom_metric('Custom/DataProcessing/BatchCount', len(results))
        
        # メモリリーク疑いの最終チェック
        if total_memory_increase > 200 * 1024 * 1024:  # 200MB以上
            newrelic.agent.record_custom_metric('Custom/DataProcessing/PotentialMemoryLeak', 1)
            profiler.analyze_memory_leak()
        
        return results
        
    except Exception as e:
        # エラー時のメモリ状況記録
        error_memory = psutil.Process().memory_info().rss
        
        newrelic.agent.notice_error(e, attributes={
            'function': 'process_large_dataset',
            'initial_memory': initial_memory,
            'error_memory': error_memory,
            'memory_increase_at_error': error_memory - initial_memory,
            'buffer_size': len(processing_buffer) if 'processing_buffer' in locals() else 0
        })
        
        raise e

5.3.3 メモリリーク検出と対処法

メモリリーク検出の実践手法

メモリリークは、アプリケーションが使用したメモリを適切に解放せず、徐々にメモリ使用量が増加し続ける現象です。New Relicのモニタリングと組み合わせて、早期検出・対処を実現します。

一般的なメモリリークパターン

javascript
// 一般的なメモリリークパターンと検出方法

// パターン1: イベントリスナーの適切な削除忘れ
class ProblematicEventHandler {
    constructor() {
        this.data = new Array(10000).fill('large data');  // 大量データ保持
        
        // ❌ 問題:イベントリスナーが削除されない
        document.addEventListener('click', this.handleClick.bind(this));
        setInterval(this.periodicTask.bind(this), 1000);
    }
    
    handleClick(event) {
        // イベント処理中にさらにメモリを使用
        this.temporaryData = new Array(5000).fill(event.target.innerText);
    }
    
    periodicTask() {
        // タイマー処理でメモリ使用増加
        this.additionalData = (this.additionalData || []).concat(new Array(1000).fill(Date.now()));
    }
    
    // ❌ 問題:デストラクタでリソース解放していない
}

// ✅ 改善版:適切なリソース管理
class ImprovedEventHandler {
    constructor() {
        this.data = new Array(10000).fill('large data');
        
        // イベントハンドラーの参照保持
        this.boundHandleClick = this.handleClick.bind(this);
        this.intervalId = null;
        
        document.addEventListener('click', this.boundHandleClick);
        this.intervalId = setInterval(this.periodicTask.bind(this), 1000);
        
        // New Relic でメモリ使用量監視
        this.monitorMemoryUsage();
    }
    
    handleClick(event) {
        // 一時データは適切にスコープ管理
        const temporaryData = new Array(5000).fill(event.target.innerText);
        // 処理後、temporaryData は自動的にガベージコレクション対象
    }
    
    periodicTask() {
        // データサイズ制限
        if (!this.additionalData) this.additionalData = [];
        
        this.additionalData.push(Date.now());
        
        // 古いデータの削除(メモリリーク防止)
        if (this.additionalData.length > 1000) {
            this.additionalData = this.additionalData.slice(-1000);  // 最新1000件のみ保持
        }
        
        // メモリ使用量記録
        const memUsage = process.memoryUsage();
        newrelic.recordMetric('Custom/Memory/PeriodicTask/HeapUsed', memUsage.heapUsed);
    }
    
    monitorMemoryUsage() {
        setInterval(() => {
            const memUsage = process.memoryUsage();
            
            // ヒープメモリ監視
            newrelic.recordMetric('Custom/Memory/EventHandler/HeapUsed', memUsage.heapUsed);
            newrelic.recordMetric('Custom/Memory/EventHandler/HeapTotal', memUsage.heapTotal);
            
            // メモリリーク検出ロジック
            if (!this.memoryBaseline) {
                this.memoryBaseline = memUsage.heapUsed;
                this.baselineTime = Date.now();
                return;
            }
            
            const timeDiff = Date.now() - this.baselineTime;
            const memoryDiff = memUsage.heapUsed - this.memoryBaseline;
            
            // 10分間で10MB以上の継続増加 = リーク疑い
            if (timeDiff > 10 * 60 * 1000 && memoryDiff > 10 * 1024 * 1024) {
                newrelic.recordMetric('Custom/Memory/EventHandler/PotentialLeak', 1);
                
                // アラート情報
                newrelic.addCustomAttribute('memory.leak_rate_mb_per_min', 
                    (memoryDiff / 1024 / 1024) / (timeDiff / 60 / 1000));
                
                // ベースライン更新
                this.memoryBaseline = memUsage.heapUsed;
                this.baselineTime = Date.now();
            }
        }, 30000);  // 30秒間隔
    }
    
    // ✅ 適切なクリーンアップ
    destroy() {
        // イベントリスナー削除
        document.removeEventListener('click', this.boundHandleClick);
        
        // タイマー停止
        if (this.intervalId) {
            clearInterval(this.intervalId);
            this.intervalId = null;
        }
        
        // データクリア
        this.data = null;
        this.additionalData = null;
        
        newrelic.recordMetric('Custom/Memory/EventHandler/Destroyed', 1);
    }
}

// パターン2: 循環参照によるメモリリーク
class CircularReferenceExample {
    
    // ❌ 問題:循環参照でガベージコレクションされない
    static createProblematicStructure() {
        const parent = { children: [], data: new Array(5000).fill('data') };
        const child1 = { parent: parent, siblings: [], data: new Array(3000).fill('child1') };
        const child2 = { parent: parent, siblings: [], data: new Array(3000).fill('child2') };
        
        // 循環参照作成
        parent.children.push(child1, child2);
        child1.siblings.push(child2);
        child2.siblings.push(child1);
        
        // さらに複雑な参照
        child1.reference = child2;
        child2.reference = child1;
        
        return parent;  // 循環参照でメモリが解放されない
    }
    
    // ✅ 改善版:WeakMap を使用した弱参照
    static createImprovedStructure() {
        const parent = { children: [], data: new Array(5000).fill('data') };
        const child1 = { data: new Array(3000).fill('child1') };
        const child2 = { data: new Array(3000).fill('child2') };
        
        // WeakMap で弱参照関係を作成
        const parentChildMap = new WeakMap();
        const siblingMap = new WeakMap();
        
        // 親子関係
        parentChildMap.set(child1, parent);
        parentChildMap.set(child2, parent);
        parent.children.push(child1, child2);
        
        // 兄弟関係
        siblingMap.set(child1, [child2]);
        siblingMap.set(child2, [child1]);
        
        // メモリ使用量監視
        newrelic.recordMetric('Custom/Memory/Structure/Created', 1);
        
        return {
            parent,
            getParent: (child) => parentChildMap.get(child),
            getSiblings: (child) => siblingMap.get(child)
        };
    }
}

// パターン3: クロージャーによる意図しないメモリ保持
function createProblematicClosure() {
    const largeData = new Array(100000).fill('large data array');  // 大量データ
    const moreData = new Array(50000).fill('more data');
    
    // ❌ 問題:クロージャーが大量データを保持し続ける
    return function smallOperation(input) {
        // 実際には largeData は使用しないが、クロージャーにより参照が残る
        return input * 2 + moreData.length;  // moreData のみ使用
    };
}

// ✅ 改善版:必要なデータのみ抽出
function createImprovedClosure() {
    const largeData = new Array(100000).fill('large data array');
    const moreData = new Array(50000).fill('more data');
    
    // 必要なデータのみ抽出
    const moreDataLength = moreData.length;
    
    // largeData, moreData への参照を削除
    // (実際のコードでは適切なスコープ管理で自動的に解放)
    
    return function smallOperation(input) {
        return input * 2 + moreDataLength;  // 数値のみ保持
    };
}

自動メモリリーク検出システム

python
# Python 自動メモリリーク検出システム
import newrelic.agent
import gc
import sys
import threading
import time
import weakref
from collections import defaultdict, deque
from typing import Dict, List, Any, Optional

class MemoryLeakDetector:
    
    def __init__(self, check_interval: int = 60):
        self.check_interval = check_interval
        self.object_growth_history = defaultdict(deque)
        self.memory_snapshots = deque(maxlen=10)
        self.leak_candidates = set()
        self.monitoring_active = True
        
        # 監視対象オブジェクトタイプ
        self.monitored_types = {
            'list', 'dict', 'set', 'tuple',
            'str', 'bytes', 'bytearray',
            'function', 'type', 'module'
        }
        
        self.start_monitoring()
    
    def start_monitoring(self):
        """メモリリーク検出開始"""
        
        # バックグラウンドスレッドで定期チェック
        self.monitor_thread = threading.Thread(
            target=self._monitoring_loop,
            daemon=True
        )
        self.monitor_thread.start()
        
        print(f"Memory leak detection started (interval: {self.check_interval}s)")
    
    def _monitoring_loop(self):
        """メモリ監視ループ"""
        
        while self.monitoring_active:
            try:
                self._take_memory_snapshot()
                self._analyze_object_growth()
                self._detect_potential_leaks()
                
            except Exception as e:
                newrelic.agent.notice_error(e, attributes={'context': 'memory_monitoring'})
            
            time.sleep(self.check_interval)
    
    @newrelic.agent.function_trace('memory_snapshot')
    def _take_memory_snapshot(self):
        """メモリスナップショット取得"""
        
        snapshot_start = time.time()
        
        # ガベージコレクション実行(正確な測定のため)
        collected = gc.collect()
        
        # オブジェクト統計取得
        object_counts = defaultdict(int)
        total_objects = 0
        
        for obj in gc.get_objects():
            obj_type = type(obj).__name__
            if obj_type in self.monitored_types:
                object_counts[obj_type] += 1
                total_objects += 1
        
        # システムメモリ情報
        import psutil
        process = psutil.Process()
        memory_info = process.memory_info()
        
        snapshot = {
            'timestamp': time.time(),
            'object_counts': dict(object_counts),
            'total_objects': total_objects,
            'rss_bytes': memory_info.rss,
            'vms_bytes': memory_info.vms,
            'gc_collected': collected
        }
        
        self.memory_snapshots.append(snapshot)
        
        # New Relic メトリクス送信
        newrelic.agent.record_custom_metric('Custom/Memory/TotalObjects', total_objects)
        newrelic.agent.record_custom_metric('Custom/Memory/RSS', memory_info.rss)
        newrelic.agent.record_custom_metric('Custom/GC/CollectedObjects', collected)
        
        for obj_type, count in object_counts.items():
            newrelic.agent.record_custom_metric(f'Custom/Memory/Objects/{obj_type}', count)
        
        snapshot_duration = (time.time() - snapshot_start) * 1000
        newrelic.agent.record_custom_metric('Custom/Memory/SnapshotDuration', snapshot_duration)
    
    def _analyze_object_growth(self):
        """オブジェクト増加パターン分析"""
        
        if len(self.memory_snapshots) < 3:
            return
        
        current = self.memory_snapshots[-1]
        previous = self.memory_snapshots[-2]
        baseline = self.memory_snapshots[0]
        
        # オブジェクトタイプ別成長率分析
        for obj_type in self.monitored_types:
            current_count = current['object_counts'].get(obj_type, 0)
            previous_count = previous['object_counts'].get(obj_type, 0)
            baseline_count = baseline['object_counts'].get(obj_type, 0)
            
            # 成長履歴記録
            growth_history = self.object_growth_history[obj_type]
            growth_history.append(current_count)
            
            # 履歴サイズ制限
            if len(growth_history) > 20:
                growth_history.popleft()
            
            # 成長パターン分析
            if len(growth_history) >= 5:
                recent_growth = self._calculate_growth_trend(growth_history)
                
                # 急激な増加検出
                if recent_growth > 0.1 and current_count > baseline_count * 1.5:
                    self.leak_candidates.add(obj_type)
                    
                    newrelic.agent.record_custom_metric(
                        f'Custom/Memory/LeakCandidate/{obj_type}/GrowthRate', 
                        recent_growth
                    )
    
    def _calculate_growth_trend(self, growth_history: deque) -> float:
        """成長トレンド計算(線形回帰近似)"""
        
        if len(growth_history) < 2:
            return 0.0
        
        # 簡易線形回帰
        n = len(growth_history)
        x_values = list(range(n))
        y_values = list(growth_history)
        
        x_mean = sum(x_values) / n
        y_mean = sum(y_values) / n
        
        numerator = sum((x_values[i] - x_mean) * (y_values[i] - y_mean) for i in range(n))
        denominator = sum((x_values[i] - x_mean) ** 2 for i in range(n))
        
        if denominator == 0:
            return 0.0
        
        slope = numerator / denominator  # 傾き = 成長率
        return slope
    
    @newrelic.agent.function_trace('leak_detection')
    def _detect_potential_leaks(self):
        """メモリリーク検出"""
        
        if len(self.memory_snapshots) < 5:
            return
        
        current = self.memory_snapshots[-1]
        baseline = self.memory_snapshots[0]
        
        # RSS メモリ増加チェック
        rss_increase = current['rss_bytes'] - baseline['rss_bytes']
        rss_increase_mb = rss_increase / 1024 / 1024
        
        time_elapsed = current['timestamp'] - baseline['timestamp']
        time_elapsed_hours = time_elapsed / 3600
        
        if rss_increase_mb > 50 and time_elapsed_hours > 0.5:  # 50MB増加、30分経過
            leak_rate = rss_increase_mb / time_elapsed_hours  # MB/hour
            
            # リーク疑いレポート
            leak_report = self._generate_leak_report(current, baseline, leak_rate)
            
            # New Relic カスタムイベント送信
            newrelic.agent.record_custom_event('MemoryLeakDetected', {
                'leak_rate_mb_per_hour': leak_rate,
                'total_increase_mb': rss_increase_mb,
                'time_elapsed_hours': time_elapsed_hours,
                'leak_candidates': list(self.leak_candidates),
                'total_objects_increase': current['total_objects'] - baseline['total_objects']
            })
            
            # 詳細調査のトリガー
            self._trigger_detailed_analysis()
            
            print(f"Potential memory leak detected: {leak_rate:.2f} MB/hour")
    
    def _generate_leak_report(self, current: Dict, baseline: Dict, leak_rate: float) -> Dict:
        """メモリリークレポート生成"""
        
        report = {
            'detection_time': time.strftime('%Y-%m-%d %H:%M:%S'),
            'leak_rate_mb_per_hour': leak_rate,
            'memory_increase': {
                'rss_mb': (current['rss_bytes'] - baseline['rss_bytes']) / 1024 / 1024,
                'vms_mb': (current['vms_bytes'] - baseline['vms_bytes']) / 1024 / 1024
            },
            'object_increases': {},
            'leak_candidates': list(self.leak_candidates)
        }
        
        # オブジェクトタイプ別増加量
        for obj_type in self.monitored_types:
            current_count = current['object_counts'].get(obj_type, 0)
            baseline_count = baseline['object_counts'].get(obj_type, 0)
            increase = current_count - baseline_count
            
            if increase > 0:
                report['object_increases'][obj_type] = {
                    'increase': increase,
                    'percentage': (increase / baseline_count * 100) if baseline_count > 0 else float('inf')
                }
        
        return report
    
    def _trigger_detailed_analysis(self):
        """詳細分析トリガー"""
        
        try:
            # 現在のオブジェクト参照解析
            self._analyze_object_references()
            
            # ガベージコレクションの詳細統計
            self._analyze_gc_statistics()
            
        except Exception as e:
            newrelic.agent.notice_error(e, attributes={'context': 'detailed_leak_analysis'})
    
    def _analyze_object_references(self):
        """オブジェクト参照関係分析"""
        
        # 最も多いオブジェクトタイプの詳細分析
        if not self.leak_candidates:
            return
        
        top_candidate = max(self.leak_candidates, 
                           key=lambda x: self.memory_snapshots[-1]['object_counts'].get(x, 0))
        
        # そのタイプのオブジェクトを詳細調査
        target_objects = []
        for obj in gc.get_objects():
            if type(obj).__name__ == top_candidate and len(target_objects) < 100:
                target_objects.append(obj)
        
        # 参照カウント分析
        high_refcount_objects = []
        for obj in target_objects:
            refcount = sys.getrefcount(obj)
            if refcount > 10:  # 高参照カウントオブジェクト
                high_refcount_objects.append({
                    'object_id': id(obj),
                    'refcount': refcount,
                    'size': sys.getsizeof(obj)
                })
        
        if high_refcount_objects:
            newrelic.agent.record_custom_event('HighRefCountObjects', {
                'object_type': top_candidate,
                'high_refcount_count': len(high_refcount_objects),
                'max_refcount': max(obj['refcount'] for obj in high_refcount_objects)
            })
    
    def _analyze_gc_statistics(self):
        """ガベージコレクション統計分析"""
        
        gc_stats = gc.get_stats()
        
        for generation, stats in enumerate(gc_stats):
            newrelic.agent.record_custom_metric(
                f'Custom/GC/Generation{generation}/Collections', 
                stats['collections']
            )
            newrelic.agent.record_custom_metric(
                f'Custom/GC/Generation{generation}/Collected', 
                stats['collected']
            )
            newrelic.agent.record_custom_metric(
                f'Custom/GC/Generation{generation}/Uncollectable', 
                stats['uncollectable']
            )
    
    def stop_monitoring(self):
        """監視停止"""
        self.monitoring_active = False
        if hasattr(self, 'monitor_thread'):
            self.monitor_thread.join(timeout=5)
        print("Memory leak detection stopped")

# 使用例:Djangoアプリケーションでの統合
def setup_memory_monitoring():
    """メモリ監視セットアップ(Django settings.pyなどで呼び出し)"""
    
    if not hasattr(setup_memory_monitoring, '_detector'):
        setup_memory_monitoring._detector = MemoryLeakDetector(check_interval=60)
    
    return setup_memory_monitoring._detector

# アプリケーション起動時の初期化
if __name__ == "__main__":
    # 本番環境でのメモリリーク検出有効化
    import os
    if os.environ.get('ENABLE_MEMORY_LEAK_DETECTION', 'false').lower() == 'true':
        detector = setup_memory_monitoring()
        print("Memory leak detection enabled")

まとめ

本章では、New Relicのコードレベルパフォーマンス分析により、従来のAPM監視では発見できない詳細な問題を特定・解決する手法を解説しました。

🎯 重要なポイント

1. Code-Level Metricsの価値

  • 関数レベルの可視性:「どのサービスが遅い」から「どの関数が遅い」まで詳細特定
  • ボトルネックの正確な局在化:推測ではなくデータに基づく最適化
  • 開発効率の向上:デバッグ時間の大幅短縮

2. プロファイリングによる深い洞察

  • CPU使用パターン:処理効率の定量的評価
  • メモリ使用分析:メモリリークの早期検出と予防
  • 継続的監視:パフォーマンス劣化の予防的検出

3. メモリリーク対策の実装

  • 自動検出システム:人手に頼らない継続的監視
  • 根本原因分析:循環参照・イベントリスナー等の特定
  • 予防的対策:適切なリソース管理パターンの実装

4. 実践的最適化戦略

  • データ駆動型改善:推測ではなく実測に基づく最適化
  • 段階的改善:高インパクト・低コストから着手
  • 継続的改善サイクル:監視→分析→改善→検証の反復

💡 期待される成果

技術的改善

  • パフォーマンス向上: 関数レベル最適化による20-50%の応答時間改善
  • メモリ効率化: メモリリーク対策による安定したリソース使用
  • CPU効率向上: ホットスポット最適化による処理能力向上

ビジネス効果

  • ユーザーエクスペリエンス向上: 快適な操作性による満足度向上
  • インフラコスト削減: 効率化によるサーバーリソース節約
  • 開発生産性向上: 問題特定時間短縮による開発速度向上

運用改善

  • 予防的運用: 問題が顕在化する前の検出・対処
  • 根拠のある意思決定: データに基づいた技術選択
  • 技術負債削減: 継続的なコード品質改善

🚀 次のステップ

実装ロードマップ

  1. Code-Level Metrics有効化: 主要サービスでの詳細監視開始
  2. プロファイリング導入: CPU・メモリの継続的分析
  3. メモリリーク検出: 自動検出システムの構築
  4. 最適化サイクル: 定期的な分析・改善の仕組み化

New Relicのコードレベル分析機能を活用することで、「なんとなく遅い」から「どこが、なぜ遅いか」を正確に把握し、科学的なパフォーマンス最適化を実現できます。これにより、真に高性能で信頼性の高いシステムを構築・運用することが可能になります。


📖 ナビゲーション

メイン: 第5章 New Relic APM(高度化)
前セクション: 第5.2章 分散トレーシング
次セクション: 第6章 New Relic Log Management

関連記事:
第5.1章: APM基本機能
第4章: New Relic Infrastructure
パフォーマンス最適化実践ガイド