New Relic 分散トレーシング設定ガイド

分散トレーシングは、現代のマイクロサービスアーキテクチャにおいて、複数のサービス間にまたがるリクエストの追跡と分析を可能にする重要な監視手法です。New Relic の分散トレーシングは、サービス間の依存関係を可視化し、パフォーマンスのボトルネックを特定して、複雑な分散システムの理解と最適化を支援します。

パフォーマンス影響: 分散トレーシング有効化による追加オーバーヘッドは1-2%程度で、大規模なマイクロサービス環境でも安全に使用できます。ヘッダー伝播による通信オーバーヘッドは数KB程度の軽微な増加となります。

分散トレーシングの概念

分散トレーシングは、単一のユーザーリクエストが複数のサービスを通過する際の処理フローを追跡します。各サービスでの処理時間、エラーの発生箇所、サービス間の依存関係を詳細に記録し、システム全体のパフォーマンスを包括的に分析できます。

トレースは複数のスパンで構成され、各スパンは個別のサービスまたは処理単位での作業を表現します。親子関係を持つスパンの階層構造により、リクエストの完全な実行パスを可視化できます。これにより、分散システムの複雑な相互作用を理解し、問題の根本原因を迅速に特定できるようになります。

分散トレーシングの有効化

グローバル設定

各言語エージェントで分散トレーシングを有効化する必要があります。

Java での設定

yaml
# newrelic.yml
common: &default_settings
  distributed_tracing:
    enabled: true
  span_events:
    enabled: true
    max_samples_stored: 2000
java
// プログラムでの設定
NewRelic.getAgent().getConfig().getValue("distributed_tracing.enabled", true);

Python での設定

ini
# newrelic.ini
[newrelic]
distributed_tracing.enabled = true
span_events.enabled = true
span_events.max_samples_stored = 2000

Node.js での設定

javascript
// newrelic.js
exports.config = {
  distributed_tracing: {
    enabled: true
  },
  span_events: {
    enabled: true,
    max_samples_stored: 2000
  }
}

.NET での設定

xml
<configuration xmlns="urn:newrelic-config">
  <distributedTracing enabled="true" />
  <spanEvents enabled="true" maxSamplesStored="2000" />
</configuration>

HTTP ヘッダーベースの実装

送信側サービスでのヘッダー挿入

分散トレーシングでは、各サービスがHTTPリクエストにトレース情報を含めて次のサービスに送信します。

Java での実装

java
import com.newrelic.api.agent.NewRelic;
import com.newrelic.api.agent.Token;
import org.springframework.web.client.RestTemplate;
import org.springframework.http.HttpHeaders;

@Service
public class OrderService {
    
    @Autowired
    private RestTemplate restTemplate;
    
    public PaymentResult processPayment(Order order) {
        HttpHeaders headers = new HttpHeaders();
        
        // 分散トレーシング ヘッダーの追加
        NewRelic.getAgent().getTransaction().insertDistributedTraceHeaders(headers::add);
        
        HttpEntity<PaymentRequest> request = new HttpEntity<>(
            new PaymentRequest(order.getTotalAmount()), 
            headers
        );
        
        return restTemplate.postForObject(
            "http://payment-service/process", 
            request, 
            PaymentResult.class
        );
    }
}

Python での実装

python
import requests
import newrelic.agent

class OrderService:
    def process_payment(self, order):
        headers = {}
        
        # 分散トレーシング ヘッダーの追加
        newrelic.agent.insert_distributed_trace_headers(headers)
        
        response = requests.post(
            'http://payment-service/process',
            json={'amount': order.total_amount},
            headers=headers
        )
        
        return response.json()

Node.js での実装

javascript
const newrelic = require('newrelic');
const axios = require('axios');

class OrderService {
    async processPayment(order) {
        const headers = {};
        
        // 分散トレーシング ヘッダーの追加
        newrelic.insertDistributedTraceHeaders(headers);
        
        const response = await axios.post(
            'http://payment-service/process',
            { amount: order.totalAmount },
            { headers }
        );
        
        return response.data;
    }
}

受信側サービスでのヘッダー処理

Java での実装

java
@RestController
@RequestMapping("/api/payments")
public class PaymentController {
    
    @PostMapping("/process")
    public PaymentResult processPayment(
            @RequestBody PaymentRequest request,
            HttpServletRequest httpRequest) {
        
        // 分散トレーシング ヘッダーの受信
        Map<String, String> headers = extractHeaders(httpRequest);
        NewRelic.getAgent().getTransaction().acceptDistributedTraceHeaders(
            TransportType.HTTP, headers
        );
        
        // ペイメント処理
        return paymentService.process(request);
    }
    
    private Map<String, String> extractHeaders(HttpServletRequest request) {
        Map<String, String> headers = new HashMap<>();
        Collections.list(request.getHeaderNames())
            .forEach(name -> headers.put(name, request.getHeader(name)));
        return headers;
    }
}

Python での実装

python
from flask import Flask, request
import newrelic.agent

app = Flask(__name__)

@app.route('/api/payments/process', methods=['POST'])
def process_payment():
    # 分散トレーシング ヘッダーの受信
    headers = dict(request.headers)
    newrelic.agent.accept_distributed_trace_headers(headers)
    
    # ペイメント処理
    payment_data = request.get_json()
    result = payment_service.process(payment_data)
    
    return result

マイクロサービス間の詳細な追跡

サービス境界でのスパン作成

各サービスでは、重要な処理単位ごとにスパンを作成して詳細な追跡を行います。

API Gateway での実装

java
@Component
public class ApiGatewayFilter implements GlobalFilter {
    
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        return NewRelic.getAgent().getTransaction().startSpan("ApiGateway/Route")
            .doOnNext(span -> {
                span.addCustomAttribute("route.path", exchange.getRequest().getPath().value());
                span.addCustomAttribute("route.method", exchange.getRequest().getMethodValue());
            })
            .flatMap(span -> {
                return chain.filter(exchange)
                    .doFinally(signalType -> span.end());
            });
    }
}

データベースアクセスでのスパン作成

python
import newrelic.agent

class UserRepository:
    @newrelic.agent.datastore_trace('PostgreSQL', 'users', 'select')
    def find_user(self, user_id):
        with newrelic.agent.DatabaseTrace('PostgreSQL', 'SELECT', 'users'):
            # データベースクエリの実行
            cursor = self.connection.cursor()
            cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
            return cursor.fetchone()
    
    @newrelic.agent.datastore_trace('PostgreSQL', 'users', 'insert')
    def create_user(self, user_data):
        with newrelic.agent.DatabaseTrace('PostgreSQL', 'INSERT', 'users'):
            cursor = self.connection.cursor()
            cursor.execute(
                "INSERT INTO users (name, email) VALUES (%s, %s)",
                (user_data['name'], user_data['email'])
            )
            self.connection.commit()

外部サービス呼び出しでのスパン作成

javascript
const newrelic = require('newrelic');

class NotificationService {
    async sendEmail(recipient, message) {
        return newrelic.startSegment('External/EmailService/send', true, async () => {
            const headers = {};
            newrelic.insertDistributedTraceHeaders(headers);
            
            try {
                const response = await axios.post(
                    'http://email-service/send',
                    { recipient, message },
                    { headers }
                );
                
                newrelic.addCustomAttribute('email.recipient', recipient);
                newrelic.addCustomAttribute('email.status', 'sent');
                
                return response.data;
            } catch (error) {
                newrelic.noticeError(error);
                newrelic.addCustomAttribute('email.status', 'failed');
                throw error;
            }
        });
    }
}

メッセージキューとの統合

Apache Kafka での分散トレーシング

java
@Component
public class KafkaProducerService {
    
    @Autowired
    private KafkaTemplate<String, Object> kafkaTemplate;
    
    public void sendMessage(String topic, Object message) {
        ProducerRecord<String, Object> record = new ProducerRecord<>(topic, message);
        
        // 分散トレーシング ヘッダーの追加
        Headers headers = record.headers();
        NewRelic.getAgent().getTransaction().insertDistributedTraceHeaders(
            (key, value) -> headers.add(key, value.getBytes())
        );
        
        kafkaTemplate.send(record);
    }
}

@KafkaListener(topics = "order-events")
public class OrderEventListener {
    
    public void handleOrderEvent(
            @Payload OrderEvent event,
            @Header Map<String, Object> headers) {
        
        // 分散トレーシング ヘッダーの受信
        Map<String, String> traceHeaders = extractTraceHeaders(headers);
        NewRelic.getAgent().getTransaction().acceptDistributedTraceHeaders(
            TransportType.KAFKA, traceHeaders
        );
        
        // イベント処理
        orderService.processEvent(event);
    }
    
    private Map<String, String> extractTraceHeaders(Map<String, Object> headers) {
        return headers.entrySet().stream()
            .filter(entry -> entry.getKey().startsWith("newrelic"))
            .collect(Collectors.toMap(
                Map.Entry::getKey,
                entry -> new String((byte[]) entry.getValue())
            ));
    }
}

RabbitMQ での分散トレーシング

python
import pika
import newrelic.agent

class MessageProducer:
    def __init__(self):
        self.connection = pika.BlockingConnection(
            pika.ConnectionParameters('localhost')
        )
        self.channel = self.connection.channel()
    
    def publish_message(self, queue_name, message):
        # 分散トレーシング ヘッダーの準備
        headers = {}
        newrelic.agent.insert_distributed_trace_headers(headers)
        
        properties = pika.BasicProperties(headers=headers)
        
        self.channel.basic_publish(
            exchange='',
            routing_key=queue_name,
            body=message,
            properties=properties
        )

class MessageConsumer:
    def __init__(self):
        self.connection = pika.BlockingConnection(
            pika.ConnectionParameters('localhost')
        )
        self.channel = self.connection.channel()
    
    def consume_messages(self, queue_name):
        def callback(ch, method, properties, body):
            # 分散トレーシング ヘッダーの受信
            headers = properties.headers or {}
            newrelic.agent.accept_distributed_trace_headers(headers)
            
            # メッセージ処理
            self.process_message(body.decode())
            
            ch.basic_ack(delivery_tag=method.delivery_tag)
        
        self.channel.basic_consume(
            queue=queue_name,
            on_message_callback=callback
        )
        
        self.channel.start_consuming()
    
    @newrelic.agent.background_task()
    def process_message(self, message):
        # メッセージ処理ロジック
        print(f"Processing message: {message}")

カスタムスパンの作成

詳細なビジネスロジック追跡

csharp
// .NET での詳細なスパン作成
using NewRelic.Api.Agent;

public class OrderProcessingService
{
    [Transaction]
    public async Task<Order> ProcessOrderAsync(OrderRequest request)
    {
        using (var span = NewRelic.StartSpan("OrderProcessing/ValidateOrder"))
        {
            span.AddCustomAttribute("order.customerId", request.CustomerId);
            span.AddCustomAttribute("order.itemCount", request.Items.Count);
            
            await ValidateOrderAsync(request);
        }
        
        using (var span = NewRelic.StartSpan("OrderProcessing/CalculatePricing"))
        {
            var pricing = await CalculatePricingAsync(request);
            span.AddCustomAttribute("order.totalAmount", pricing.TotalAmount);
        }
        
        using (var span = NewRelic.StartSpan("OrderProcessing/CreateOrder"))
        {
            var order = await CreateOrderAsync(request);
            span.AddCustomAttribute("order.id", order.Id);
            return order;
        }
    }
    
    [Trace]
    private async Task ValidateOrderAsync(OrderRequest request)
    {
        // バリデーション ロジック
        await InventoryService.CheckAvailabilityAsync(request.Items);
        await CustomerService.ValidateCustomerAsync(request.CustomerId);
    }
}

非同期処理での追跡

go
// Go での非同期処理追跡
package main

import (
    "context"
    "github.com/newrelic/go-agent/v3/newrelic"
)

type OrderService struct {
    app *newrelic.Application
}

func (s *OrderService) ProcessOrderAsync(ctx context.Context, order Order) error {
    txn := newrelic.FromContext(ctx)
    
    // 非同期処理のためのゴルーチン起動
    go func() {
        // 新しいゴルーチンでのトランザクション作成
        asyncTxn := txn.NewGoroutine()
        defer asyncTxn.End()
        
        // 在庫確認
        segment := asyncTxn.StartSegment("OrderProcessing/CheckInventory")
        available := s.checkInventory(order.Items)
        segment.AddAttribute("inventory.available", available)
        segment.End()
        
        if available {
            // 支払い処理
            paymentSegment := asyncTxn.StartSegment("OrderProcessing/ProcessPayment")
            err := s.processPayment(order.PaymentInfo)
            if err != nil {
                asyncTxn.NoticeError(err)
            }
            paymentSegment.End()
        }
    }()
    
    return nil
}

OpenTelemetry との統合

OpenTelemetry SDK の設定

python
# Python でのOpenTelemetry統合
from opentelemetry import trace
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor

# New Relic OTLP エンドポイント設定
otlp_exporter = OTLPSpanExporter(
    endpoint="https://otlp.nr-data.net:4317",
    headers={"api-key": "YOUR_LICENSE_KEY"}
)

# トレーサープロバイダーの設定
trace.set_tracer_provider(TracerProvider())
tracer = trace.get_tracer(__name__)

span_processor = BatchSpanProcessor(otlp_exporter)
trace.get_tracer_provider().add_span_processor(span_processor)

# カスタムスパンの作成
def process_order(order_data):
    with tracer.start_as_current_span("order.process") as span:
        span.set_attribute("order.id", order_data["id"])
        span.set_attribute("order.amount", order_data["amount"])
        
        # 詳細な処理
        with tracer.start_as_current_span("order.validate"):
            validate_order(order_data)
        
        with tracer.start_as_current_span("order.save"):
            save_order(order_data)

カスタム計測器の実装

javascript
// Node.js でのOpenTelemetry カスタム計測
const { trace } = require('@opentelemetry/api');
const tracer = trace.getTracer('my-service');

class PaymentService {
    async processPayment(paymentData) {
        return tracer.startActiveSpan('payment.process', async (span) => {
            try {
                span.setAttributes({
                    'payment.amount': paymentData.amount,
                    'payment.currency': paymentData.currency,
                    'payment.method': paymentData.method
                });
                
                // 支払いゲートウェイ呼び出し
                const result = await tracer.startActiveSpan('payment.gateway.call', async (gatewaySpan) => {
                    const response = await this.callPaymentGateway(paymentData);
                    
                    gatewaySpan.setAttributes({
                        'gateway.response.status': response.status,
                        'gateway.transaction.id': response.transactionId
                    });
                    
                    return response;
                });
                
                span.setStatus({ code: SpanStatusCode.OK });
                return result;
                
            } catch (error) {
                span.recordException(error);
                span.setStatus({ 
                    code: SpanStatusCode.ERROR, 
                    message: error.message 
                });
                throw error;
            } finally {
                span.end();
            }
        });
    }
}

トラブルシューティングと最適化

分散トレーシングの診断

yaml
# 診断用の詳細ログ設定
common: &default_settings
  log_level: debug
  audit_log_file: stdout
  
  distributed_tracing:
    enabled: true
    
  # スパン イベントの詳細設定
  span_events:
    enabled: true
    max_samples_stored: 2000
    
  # トレース データの保持設定
  transaction_tracer:
    enabled: true
    transaction_threshold: 1.0
    explain_threshold: 0.5

セキュリティ考慮事項

分散トレーシング環境でのセキュリティベストプラクティス:

yaml
# セキュリティ強化設定
common: &default_settings
  distributed_tracing:
    enabled: true
    # 機密情報を含む属性の除外
    exclude_request_uri: true
    
  attributes:
    exclude:
      - request.parameters.password
      - request.parameters.credit_card
      - request.parameters.ssn
      - request.headers.authorization
      - request.headers.x-api-key
      - http.request.header.cookie
      
  # スパン属性の制限
  span_events:
    attributes:
      exclude:
        - db.statement  # SQLクエリの完全な除外
        - http.request.body  # リクエストボディの除外
java
// Java でのセキュアなトレース実装
@Component
public class SecureTracingService {
    
    public void processSecureOperation(String userId, String operationType) {
        // 機密情報を含まない安全な属性のみ設定
        Map<String, Object> safeAttributes = new HashMap<>();
        safeAttributes.put("user.segment", getUserSegment(userId));
        safeAttributes.put("operation.type", operationType);
        safeAttributes.put("operation.timestamp", System.currentTimeMillis());
        
        // 機密情報は含めない
        // safeAttributes.put("user.email", userEmail); // 避けるべき
        // safeAttributes.put("payment.card_number", cardNumber); // 避けるべき
        
        NewRelic.addCustomAttributes(safeAttributes);
    }
    
    private String getUserSegment(String userId) {
        // ユーザーIDからセグメント情報のみを抽出
        return userService.getUserSegment(userId);
    }
}

パフォーマンス最適化

本番環境での効率的な分散トレーシング運用:

java
// Java でのインテリジェントサンプリング設定
@Configuration
public class OptimizedTracingConfiguration {
    
    @Bean
    public NewRelicSampler intelligentSampler() {
        return new NewRelicSampler() {
            @Override
            public boolean shouldSample(TraceContext traceContext) {
                String operationName = traceContext.getOperationName();
                
                // ヘルスチェックや内部APIは除外
                if (operationName.contains("health") || 
                    operationName.contains("metrics") ||
                    operationName.contains("internal")) {
                    return false;
                }
                
                // エラーを含むトレースは常に収集
                if (traceContext.hasError()) {
                    return true;
                }
                
                // 重要なビジネス処理は高いサンプリング率
                if (operationName.contains("payment") ||
                    operationName.contains("order") ||
                    operationName.contains("checkout")) {
                    return Math.random() < 0.5; // 50%サンプリング
                }
                
                // 一般的なAPIは低いサンプリング率
                return Math.random() < 0.05; // 5%サンプリング
            }
        };
    }
}
python
# Python での効率的なトレース管理
import newrelic.agent
from functools import wraps

class TraceOptimizer:
    """トレース最適化管理クラス"""
    
    CRITICAL_OPERATIONS = {'payment', 'order', 'checkout', 'auth'}
    SKIP_OPERATIONS = {'health', 'metrics', 'status'}
    
    @classmethod
    def should_trace(cls, operation_name: str, error_occurred: bool = False) -> bool:
        """トレース対象判定"""
        if error_occurred:
            return True  # エラー時は常にトレース
            
        if any(skip in operation_name.lower() for skip in cls.SKIP_OPERATIONS):
            return False  # スキップ対象
            
        if any(critical in operation_name.lower() for critical in cls.CRITICAL_OPERATIONS):
            return True  # 重要な操作は常にトレース
            
        # その他は10%の確率
        import random
        return random.random() < 0.1

def intelligent_trace(operation_name: str):
    """インテリジェントトレースデコレータ"""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            error_occurred = False
            
            try:
                result = func(*args, **kwargs)
                return result
            except Exception as e:
                error_occurred = True
                raise
            finally:
                # トレース対象判定
                if TraceOptimizer.should_trace(operation_name, error_occurred):
                    newrelic.agent.record_custom_event('OperationTrace', {
                        'operation_name': operation_name,
                        'error_occurred': error_occurred,
                        'traced': True
                    })
                    
        return wrapper
    return decorator

# 使用例
@intelligent_trace('payment_processing')
@newrelic.agent.function_trace()
def process_payment(payment_data):
    # 支払い処理ロジック
    pass

New Relic の分散トレーシングにより、複雑なマイクロサービス環境での包括的な可視性を実現できます。適切な設定と実装により、サービス間の依存関係を明確に把握し、パフォーマンスの問題を迅速に特定して解決できるようになります。


関連記事: APM概要・アーキテクチャ関連記事: APM設定完全リファレンス