New Relic 分散トレーシング設定ガイド
分散トレーシングは、現代のマイクロサービスアーキテクチャにおいて、複数のサービス間にまたがるリクエストの追跡と分析を可能にする重要な監視手法です。New Relic の分散トレーシングは、サービス間の依存関係を可視化し、パフォーマンスのボトルネックを特定して、複雑な分散システムの理解と最適化を支援します。
パフォーマンス影響: 分散トレーシング有効化による追加オーバーヘッドは1-2%程度で、大規模なマイクロサービス環境でも安全に使用できます。ヘッダー伝播による通信オーバーヘッドは数KB程度の軽微な増加となります。
分散トレーシングの概念
分散トレーシングは、単一のユーザーリクエストが複数のサービスを通過する際の処理フローを追跡します。各サービスでの処理時間、エラーの発生箇所、サービス間の依存関係を詳細に記録し、システム全体のパフォーマンスを包括的に分析できます。
トレースは複数のスパンで構成され、各スパンは個別のサービスまたは処理単位での作業を表現します。親子関係を持つスパンの階層構造により、リクエストの完全な実行パスを可視化できます。これにより、分散システムの複雑な相互作用を理解し、問題の根本原因を迅速に特定できるようになります。
分散トレーシングの有効化
グローバル設定
各言語エージェントで分散トレーシングを有効化する必要があります。
Java での設定
# newrelic.yml
common: &default_settings
distributed_tracing:
enabled: true
span_events:
enabled: true
max_samples_stored: 2000
// プログラムでの設定
NewRelic.getAgent().getConfig().getValue("distributed_tracing.enabled", true);
Python での設定
# newrelic.ini
[newrelic]
distributed_tracing.enabled = true
span_events.enabled = true
span_events.max_samples_stored = 2000
Node.js での設定
// newrelic.js
exports.config = {
distributed_tracing: {
enabled: true
},
span_events: {
enabled: true,
max_samples_stored: 2000
}
}
.NET での設定
<configuration xmlns="urn:newrelic-config">
<distributedTracing enabled="true" />
<spanEvents enabled="true" maxSamplesStored="2000" />
</configuration>
HTTP ヘッダーベースの実装
送信側サービスでのヘッダー挿入
分散トレーシングでは、各サービスがHTTPリクエストにトレース情報を含めて次のサービスに送信します。
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 での実装
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 での実装
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 での実装
@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 での実装
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 での実装
@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());
});
}
}
データベースアクセスでのスパン作成
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()
外部サービス呼び出しでのスパン作成
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 での分散トレーシング
@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 での分散トレーシング
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}")
カスタムスパンの作成
詳細なビジネスロジック追跡
// .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 での非同期処理追跡
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 での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)
カスタム計測器の実装
// 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();
}
});
}
}
トラブルシューティングと最適化
分散トレーシングの診断
# 診断用の詳細ログ設定
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
セキュリティ考慮事項
分散トレーシング環境でのセキュリティベストプラクティス:
# セキュリティ強化設定
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 でのセキュアなトレース実装
@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 でのインテリジェントサンプリング設定
@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 での効率的なトレース管理
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設定完全リファレンス