New Relic入門 第3A部 - APM基礎:アプリケーション監視をマスターしよう
Part 2でセットアップを完了したら、いよいよNew Relicの真価を発揮する**APM(Application Performance Monitoring)**を本格活用しましょう。本記事では、実際の問題解決事例とともに、APMによるアプリケーション監視の基礎から実践まで詳しく解説します。
🎯 この記事で習得できること
- [ ] APMの基本概念とアーキテクチャ理解
- [ ] パフォーマンス問題の特定手法(実際の改善事例付き)
- [ ] メモリリーク検出と対処方法
- [ ] 分散トレーシングによるマイクロサービス問題特定
- [ ] エラー追跡とアラート設定のベストプラクティス
3.1 APMの基本概念とアーキテクチャ
📊 APMが監視する主要メトリクス
**APM(Application Performance Monitoring)**は、アプリケーションのパフォーマンスをリアルタイムで監視し、問題の早期発見と迅速な解決を支援する機能です。
yaml
# Core APM Metrics
Response Time(レスポンス時間):
- Application response time(平均・95パーセンタイル)
- Database query time
- External service call time
Throughput(スループット):
- Requests per minute (RPM)
- Pages per minute (PPM)
- Background job processing rate
Error Rate(エラー率):
- Error percentage
- Exception frequency
- HTTP error codes (4xx, 5xx)
Resource Utilization(リソース使用率):
- CPU usage by transaction
- Memory consumption patterns
- Database connection pool usage
🏗️ APMアーキテクチャの理解
mermaid
graph TD
A[ユーザーリクエスト] --> B[ロードバランサー]
B --> C[Webサーバー]
C --> D[APMエージェント]
D --> E[アプリケーション]
E --> F[データベース]
E --> G[外部API]
D --> H[New Relic Collector]
H --> I[New Relic Platform]
I --> J[ダッシュボード]
style D fill:#f9f,stroke:#333,stroke-width:2px
style H fill:#9ff,stroke:#333,stroke-width:2px
APMエージェントの役割:
- アプリケーション内でのメトリクス収集
- 分散トレーシング情報の生成
- エラーとエクセプションの自動検出
- リアルタイムでのデータ送信
3.2 パフォーマンス問題の特定手法
🔍 1. レスポンス時間劣化の分析
実際の問題シナリオ:ECサイトの商品検索API
状況: ECサイトの商品検索APIが急激に遅くなった(通常200ms → 現在1.2s)
分析ステップ:
javascript
// Step 1: New Relic APMダッシュボードでの確認項目
1. Overview画面でのResponse Time trend確認
- 通常: 200ms → 現在: 1.2s
- 問題発生時刻: 2025-07-15 14:30頃
2. Transactionsページでのボトルネック特定
- GET /api/products/search: 85% of total time
- 他のエンドポイントは正常
3. Database queriesタブでのSlow Query特定
- SELECT * FROM products WHERE name LIKE '%keyword%': 950ms
- 実行回数: 1回のリクエストで平均3.2回実行
実際のクエリ最適化例:
sql
-- 🚫 問題のあるクエリ
SELECT * FROM products
WHERE name LIKE '%キーワード%'
OR description LIKE '%キーワード%'
ORDER BY created_at DESC;
-- ✅ 最適化後のクエリ
SELECT id, name, price, thumbnail_url, category_id
FROM products
WHERE MATCH(name, description) AGAINST('キーワード' IN BOOLEAN MODE)
AND status = 'active'
AND stock_quantity > 0
ORDER BY relevance_score DESC, created_at DESC
LIMIT 20;
-- インデックス追加
CREATE FULLTEXT INDEX idx_products_search ON products(name, description);
CREATE INDEX idx_products_status_stock ON products(status, stock_quantity, created_at);
CREATE INDEX idx_products_category ON products(category_id, status);
最適化結果の測定:
yaml
改善前:
- レスポンス時間: 1.2s
- スループット: 50 RPM
- エラー率: 3.2%
- DB接続時間: 950ms
改善後:
- レスポンス時間: 180ms(85%改善)
- スループット: 300 RPM(6倍向上)
- エラー率: 0.1%(96%削減)
- DB接続時間: 45ms(95%改善)
New RelicでのSlow Queryの確認方法
javascript
// APMダッシュボードでの確認手順
1. APM → Applications → [Your App Name]
2. "Database" タブをクリック
3. "Slow Queries" セクションで詳細を確認
4. クエリ実行時間、呼び出し頻度、影響度を分析
// カスタムメトリクスでの追跡
const newrelic = require('newrelic');
async function searchProducts(keyword) {
const startTime = Date.now();
try {
const results = await database.search(keyword);
// 検索パフォーマンスをトラッキング
newrelic.recordMetric('Custom/Search/ResponseTime', Date.now() - startTime);
newrelic.recordMetric('Custom/Search/ResultCount', results.length);
return results;
} catch (error) {
newrelic.noticeError(error, {
searchKeyword: keyword,
searchDuration: Date.now() - startTime
});
throw error;
}
}
🧠 2. メモリリーク検出と対処
Node.js アプリケーションのメモリリーク例
問題の症状:
- アプリケーションが時間経過とともに徐々に遅くなる
- メモリ使用量が増加し続ける
- 最終的にOut of Memory エラーでクラッシュ
javascript
// 🚫 メモリリークを起こすコード例
class DataProcessor {
constructor() {
this.cache = new Map();
this.eventListeners = [];
}
// 問題1: キャッシュのクリアが不十分
processData(data) {
const cacheKey = `${data.id}_${Date.now()}`;
this.cache.set(cacheKey, data); // キャッシュが永続的に蓄積
return this.transformData(data);
}
// 問題2: イベントリスナーの解除忘れ
setupWebSocket(ws) {
const handler = (message) => {
this.handleMessage(message);
};
ws.on('message', handler);
this.eventListeners.push(handler); // 参照が残り続ける
}
}
// ✅ 修正されたコード
class OptimizedDataProcessor {
constructor() {
this.cache = new Map();
this.eventListeners = new WeakMap(); // WeakMapを使用
this.maxCacheSize = 1000;
}
processData(data) {
// キャッシュサイズ制限
if (this.cache.size >= this.maxCacheSize) {
this.cleanupOldCache();
}
const cacheKey = `${data.id}`;
this.cache.set(cacheKey, {
data: data,
timestamp: Date.now(),
ttl: 300000 // 5分でTTL
});
return this.transformData(data);
}
cleanupOldCache() {
const now = Date.now();
for (const [key, value] of this.cache.entries()) {
if (now - value.timestamp > value.ttl) {
this.cache.delete(key);
}
}
}
setupWebSocket(ws) {
const handler = (message) => {
this.handleMessage(message);
};
ws.on('message', handler);
// クリーンアップ関数を提供
const cleanup = () => {
ws.removeListener('message', handler);
};
ws.on('close', cleanup);
ws.on('error', cleanup);
return cleanup;
}
}
New Relicでのメモリ監視設定:
javascript
// カスタムメトリクスでメモリ使用量を追跡
const newrelic = require('newrelic');
// 定期的なメモリ監視
setInterval(() => {
const memUsage = process.memoryUsage();
newrelic.recordMetric('Custom/Memory/RSS', memUsage.rss);
newrelic.recordMetric('Custom/Memory/HeapUsed', memUsage.heapUsed);
newrelic.recordMetric('Custom/Memory/HeapTotal', memUsage.heapTotal);
newrelic.recordMetric('Custom/Memory/External', memUsage.external);
// メモリリークの早期警告
const heapUsedMB = memUsage.heapUsed / 1024 / 1024;
if (heapUsedMB > 500) { // 500MB閾値
newrelic.addCustomAttribute('memory_warning', true);
newrelic.recordMetric('Custom/Memory/Warning', 1);
}
}, 30000); // 30秒間隔
🔗 3. 分散トレーシングによるマイクロサービス問題特定
マイクロサービス環境での問題分析
シナリオ: ユーザー注文処理で断続的にタイムアウトが発生
yaml
# マイクロサービス構成
Frontend API → Auth Service → Order Service → Payment Service → Inventory Service
→ Email Service
→ Notification Service
分散トレーシングでの問題特定:
javascript
// Frontend API (Node.js)
const newrelic = require('newrelic');
app.post('/api/orders', async (req, res) => {
const transaction = newrelic.getTransaction();
try {
// 1. 認証サービス呼び出し
const authResult = await authService.validateUser(req.headers.authorization);
newrelic.addCustomAttribute('auth.user_id', authResult.userId);
// 2. 注文処理開始
const orderData = {
...req.body,
userId: authResult.userId,
traceId: transaction.traceId // トレースIDを伝播
};
const order = await orderService.createOrder(orderData);
newrelic.addCustomAttribute('order.id', order.id);
res.json({ success: true, orderId: order.id });
} catch (error) {
newrelic.noticeError(error, {
service: 'frontend-api',
endpoint: '/api/orders',
userId: req.body.userId
});
res.status(500).json({ error: 'Order processing failed' });
}
});
// Order Service (Java)
@RestController
public class OrderController {
@PostMapping("/orders")
@Trace(dispatcher = true)
public ResponseEntity<Order> createOrder(@RequestBody OrderRequest request) {
try {
// 分散トレーシング情報を追加
NewRelic.addCustomParameter("order.total", request.getTotal());
NewRelic.addCustomParameter("order.items_count", request.getItems().size());
// 在庫確認サービス呼び出し
InventoryResponse inventory = inventoryService.checkAvailability(request);
if (!inventory.isAvailable()) {
NewRelic.recordMetric("Custom/Order/InventoryUnavailable", 1);
throw new InventoryUnavailableException("Items not available");
}
// 決済処理
PaymentResponse payment = paymentService.processPayment(request.getPayment());
NewRelic.addCustomParameter("payment.transaction_id", payment.getTransactionId());
// 注文作成
Order order = orderRepository.save(new Order(request, payment.getTransactionId()));
// 非同期通知(パフォーマンス改善)
notificationService.sendOrderConfirmationAsync(order);
return ResponseEntity.ok(order);
} catch (Exception e) {
NewRelic.noticeError(e);
throw e;
}
}
}
分散トレーシングでの問題発見:
yaml
# New Relicで発見された問題パターン
Trace Analysis Results:
- Frontend API: 50ms (正常)
- Auth Service: 120ms (正常)
- Order Service: 200ms (正常)
- Inventory Service: 15,000ms (タイムアウト!)
- Payment Service: 実行されず
- Email Service: 実行されず
Root Cause:
- Inventory Serviceで外部API呼び出しがタイムアウト
- データベースのデッドロック発生
- 接続プールの枯渇
Solution:
- 在庫確認のキャッシュ実装
- データベースクエリの最適化
- 接続プール設定の見直し
- サーキットブレーカーパターンの導入
3.3 エラー追跡とアラート設定
🚨 エラー分類とコンテキスト情報
javascript
// 効果的なエラー追跡の実装
const newrelic = require('newrelic');
class ErrorTracker {
static trackError(error, context = {}) {
// エラー分類
const errorType = this.classifyError(error);
// 詳細なコンテキスト情報
const enrichedContext = {
...context,
errorType: errorType,
timestamp: new Date().toISOString(),
userAgent: context.userAgent || 'unknown',
sessionId: context.sessionId || 'anonymous',
apiVersion: process.env.API_VERSION,
environment: process.env.NODE_ENV
};
// New Relicにエラー送信
newrelic.noticeError(error, enrichedContext);
// カスタムメトリクス更新
newrelic.recordMetric(`Custom/Errors/${errorType}`, 1);
// 重要度に応じた追加処理
if (this.isCriticalError(error)) {
this.alertCriticalError(error, enrichedContext);
}
}
static classifyError(error) {
const errorMapping = {
'ValidationError': 'validation',
'AuthenticationError': 'authentication',
'AuthorizationError': 'authorization',
'DatabaseError': 'database',
'NetworkError': 'network',
'TimeoutError': 'timeout',
'RateLimitError': 'rate_limit'
};
return errorMapping[error.constructor.name] || 'unknown';
}
static isCriticalError(error) {
const criticalTypes = [
'DatabaseError',
'PaymentError',
'SecurityError'
];
return criticalTypes.includes(error.constructor.name);
}
static alertCriticalError(error, context) {
// 重要なエラーの場合、追加のカスタム属性
newrelic.addCustomAttribute('critical_error', true);
newrelic.addCustomAttribute('alert_level', 'critical');
newrelic.recordMetric('Custom/Errors/Critical', 1);
}
}
// 使用例
app.use((error, req, res, next) => {
ErrorTracker.trackError(error, {
url: req.url,
method: req.method,
userAgent: req.get('User-Agent'),
sessionId: req.session?.id,
userId: req.user?.id
});
res.status(500).json({ error: 'Internal Server Error' });
});
📢 実践的なアラート設定
yaml
# New Relicアラートポリシー設定例
alert_policies:
- name: "Application Performance Alerts"
incident_preference: "PER_POLICY"
conditions:
- name: "High Error Rate"
type: "apm_app_metric"
entities: ["MyApp"]
metric: "error_percentage"
condition_scope: "application"
terms:
- priority: "warning"
threshold: "5" # 5%
threshold_duration: "300" # 5 minutes
threshold_occurrences: "all"
- priority: "critical"
threshold: "10" # 10%
threshold_duration: "300" # 5 minutes
threshold_occurrences: "all"
- name: "Slow Response Time"
type: "apm_app_metric"
entities: ["MyApp"]
metric: "response_time_web"
condition_scope: "application"
terms:
- priority: "warning"
threshold: "500" # 500ms
threshold_duration: "600" # 10 minutes
threshold_occurrences: "all"
- priority: "critical"
threshold: "1000" # 1 second
threshold_duration: "300" # 5 minutes
threshold_occurrences: "all"
notification_channels:
- name: "DevOps Team Slack"
type: "slack"
configuration:
url: "https://hooks.slack.com/services/xxx"
channel: "#alerts"
- name: "PagerDuty Integration"
type: "pagerduty"
configuration:
service_key: "your-pagerduty-key"
🎯 アラート疲れを防ぐベストプラクティス
yaml
# 効果的なアラート戦略
Smart_Alerting:
1. 段階的な重要度設定:
Info: → Slackに通知
Warning: → Slack + Email
Critical: → Slack + Email + PagerDuty
2. 時間ベースのしきい値:
営業時間中: より敏感な設定
営業時間外: 重要なアラートのみ
3. 動的ベースライン:
静的な閾値ではなく、過去のパフォーマンスベース
季節性やトレンドを考慮
4. アラートのグルーピング:
関連するアラートをまとめて通知
同一インシデントの重複通知を防止
# 実装例
alert_optimization:
escalation_policy:
- level: 1
wait_time: 5_minutes
targets: ["slack_channel"]
- level: 2
wait_time: 15_minutes
targets: ["on_call_engineer"]
- level: 3
wait_time: 30_minutes
targets: ["engineering_manager"]
🎉 APM基礎マスター完了
✅ 習得チェックリスト
APM基礎を完了すると、以下ができるようになります:
- [ ] APMダッシュボードでパフォーマンス問題を特定できる
- [ ] レスポンス時間劣化の原因分析と対処ができる
- [ ] メモリリークの検出と修正ができる
- [ ] 分散トレーシングで複雑なサービス間の問題を追跡できる
- [ ] 効果的なエラー追跡とアラート設定ができる
📈 実践的な改善事例
このPartで学んだ手法を実践することで実現できる改善:
yaml
Performance_Improvements:
- レスポンス時間: 85%改善(1.2s → 180ms)
- スループット: 6倍向上(50 RPM → 300 RPM)
- エラー率: 96%削減(3.2% → 0.1%)
- メモリ使用量: 40%削減(適切なキャッシュ管理)
Operational_Benefits:
- MTTR(平均復旧時間): 90%短縮(4時間 → 30分)
- 問題発見時間: 95%短縮(手動発見 → 自動アラート)
- デプロイメント信頼性: 向上(パフォーマンス回帰の自動検出)
🚀 次のステップ
APM基礎をマスターしたら、以下の学習を進めてください:
- Part 3B: Infrastructure Monitoring - サーバー・インフラ監視の詳細活用
- 第9章: Browser・RUM監視 - フロントエンド監視とユーザー体験最適化
- Part 4: データ分析とカスタマイゼーション - NRQL習得とダッシュボード作成
💡 実践のコツ
yaml
APM_Best_Practices:
日常運用:
- 毎朝APM Overviewで前日の状況確認
- 週次でSlow Queryの分析と最適化
- 月次でエラー傾向の分析と対策立案
問題対応:
- まずAPM Overviewで全体像を把握
- Transactionで具体的なボトルネックを特定
- 分散トレーシングで問題の根本原因を追跡
継続改善:
- パフォーマンスベースラインの定期見直し
- アラート精度の継続的改善
- チーム内でのAPMノウハウ共有
🤖 より深い監視機能を学ぶ: Part 3B: Infrastructure Monitoringで、サーバーとクラウド環境の包括的な監視手法を習得しましょう!