Ruby アプリケーション向け New Relic APM 設定ガイド

Ruby アプリケーションでの New Relic APM 導入は、Ruby エコシステムの豊富なフレームワークとライブラリとの深い統合を通じて、詳細なパフォーマンス監視を実現します。Ruby エージェントは、Rails の規約に従った自動計測、Rubyの動的な性質を活かした柔軟な監視オプション、豊富なgemエコシステムとの統合を提供します。

Ruby エージェントの特徴

New Relic Ruby エージェントは、Ruby の動的メソッド呼び出しとメタプログラミング機能を活用して、既存のコードを最小限の変更で監視対象にできます。メソッドエイリアシングとモジュール拡張により、フレームワークの内部動作を詳細に追跡し、パフォーマンスボトルネックを特定します。

Rails、Sinatra、Padrino、Hanami などの主要フレームワークとの自動統合により、MVC パターンの各層、Active Record のデータベース操作、Action Cable のWebSocket通信まで包括的に監視できます。また、Sidekiq、Resque、Delayed Job などのバックグラウンドジョブ処理も詳細に追跡します。

導入前の準備

環境要件の確認

Ruby エージェントは以下のバージョンをサポートしています:

  • Ruby バージョン: 2.7 以降(Ruby 3.x 推奨)
  • フレームワーク: Rails 6.0 以降、Sinatra 2.0 以降
  • Web サーバー: Puma、Unicorn、Passenger、Thin
  • プラットフォーム: Linux、macOS、Windows(WSL2)

Bundler プロジェクトの確認

Ruby アプリケーションでは、Bundler を使用した依存関係管理が前提となります。

エージェントのインストール

Gemfile への追加

ruby
# Gemfile
gem 'newrelic_rpm'

# 開発・テスト環境での詳細なログが必要な場合
group :development, :test do
  gem 'newrelic_rpm', require: false
end

Bundle install の実行

bash
bundle install

Rails での自動設定

Rails アプリケーションでは、gem の追加だけで基本的な監視が自動的に有効になります。

基本設定の構成

newrelic.yml 設定ファイルの生成

bash
# 設定ファイルの生成
bundle exec newrelic install YOUR_LICENSE_KEY "My Ruby Application"

newrelic.yml の詳細設定

yaml
# config/newrelic.yml
common: &default_settings
  license_key: '<%= ENV["NEW_RELIC_LICENSE_KEY"] %>'
  app_name: My Ruby Application
  distributed_tracing:
    enabled: true
  span_events:
    enabled: true
    max_samples_stored: 2000

production:
  <<: *default_settings
  app_name: My Ruby Application (Production)
  log_level: info
  
  # トランザクション トレーサー設定
  transaction_tracer:
    enabled: true
    transaction_threshold: apdex_f
    record_sql: obfuscated
    explain_enabled: true
    explain_threshold: 0.5
    stack_trace_threshold: 0.5
  
  # エラー コレクター設定
  error_collector:
    enabled: true
    capture_source: true
    ignore_errors: "ActionController::RoutingError,ActionController::InvalidAuthenticityToken"
  
  # ブラウザ監視設定
  browser_monitoring:
    auto_instrument: true

development:
  <<: *default_settings
  app_name: My Ruby Application (Development)
  log_level: debug
  developer_mode: true

test:
  <<: *default_settings
  app_name: My Ruby Application (Test)
  monitor_mode: false

環境変数による設定

bash
# .env ファイル
NEW_RELIC_LICENSE_KEY=your_license_key_here
NEW_RELIC_APP_NAME="My Ruby Application"
NEW_RELIC_ENABLED=true
NEW_RELIC_LOG_LEVEL=info

Rails アプリケーションでの統合

application.rb での設定

ruby
# config/application.rb
module MyApp
  class Application < Rails::Application
    # New Relic の初期化設定
    config.after_initialize do
      if defined?(NewRelic::Agent)
        NewRelic::Agent.manual_start(
          app_name: ENV['NEW_RELIC_APP_NAME'],
          license_key: ENV['NEW_RELIC_LICENSE_KEY']
        )
      end
    end
  end
end

コントローラーでのカスタム計測

ruby
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  include NewRelic::Agent::Instrumentation::ControllerInstrumentation
  
  before_action :set_newrelic_user_attributes
  around_action :monitor_performance
  
  private
  
  def set_newrelic_user_attributes
    if current_user
      NewRelic::Agent.set_user_attributes(
        user_id: current_user.id,
        user_name: current_user.name,
        user_email: current_user.email
      )
      
      NewRelic::Agent.add_custom_attributes(
        user_role: current_user.role,
        user_plan: current_user.plan&.name,
        user_created_at: current_user.created_at
      )
    end
  end
  
  def monitor_performance
    start_time = Time.current
    yield
  ensure
    if defined?(NewRelic::Agent)
      duration = Time.current - start_time
      NewRelic::Agent.record_custom_metric("Custom/Controller/ResponseTime", duration)
    end
  end
end
ruby
# app/controllers/orders_controller.rb
class OrdersController < ApplicationController
  add_transaction_tracer :create, category: :controller
  add_transaction_tracer :complex_calculation, category: :task
  
  def index
    NewRelic::Agent.add_custom_attributes(
      orders_count: current_user.orders.count,
      filter_applied: params[:filter].present?
    )
    
    @orders = NewRelic::Agent::MethodTracer.trace_execution_scoped("Custom/Orders/FetchUserOrders") do
      current_user.orders.includes(:items).page(params[:page])
    end
  end
  
  def create
    @order = NewRelic::Agent::MethodTracer.trace_execution_scoped("Custom/Orders/Create") do
      OrderCreationService.new(current_user, order_params).call
    end
    
    if @order.persisted?
      # 注文成功のメトリクス記録
      NewRelic::Agent.record_custom_metric("Custom/Orders/Created", 1)
      NewRelic::Agent.record_custom_metric("Custom/Revenue", @order.total_amount)
      
      # カスタムイベントの記録
      NewRelic::Agent.record_custom_event("OrderCreated", {
        order_id: @order.id,
        customer_id: current_user.id,
        total_amount: @order.total_amount,
        item_count: @order.items.count
      })
      
      redirect_to @order, notice: 'Order was successfully created.'
    else
      NewRelic::Agent.record_custom_metric("Custom/Orders/CreationFailed", 1)
      render :new
    end
  end
  
  private
  
  def complex_calculation
    # 複雑な計算処理の監視
    result = NewRelic::Agent::MethodTracer.trace_execution_scoped("Custom/Orders/ComplexCalculation") do
      # 時間のかかる計算処理
      sleep(1)
      42
    end
    
    NewRelic::Agent.add_custom_attributes(calculation_result: result)
    result
  end
end

Active Record モデルでの監視

ruby
# app/models/order.rb
class Order < ApplicationRecord
  include NewRelic::Agent::MethodTracer
  
  belongs_to :user
  has_many :order_items
  
  after_create :record_order_metrics
  after_update :record_status_change
  
  def calculate_total
    NewRelic::Agent::MethodTracer.trace_execution_scoped("Custom/Order/CalculateTotal") do
      order_items.sum { |item| item.price * item.quantity }
    end
  end
  
  def process_payment
    NewRelic::Agent.start_transaction(name: "OrderPayment/process") do
      payment_result = PaymentGateway.charge(
        amount: total_amount,
        card_token: payment_token
      )
      
      if payment_result.success?
        NewRelic::Agent.record_custom_metric("Custom/Payments/Success", 1)
        update!(status: 'paid', payment_id: payment_result.id)
      else
        NewRelic::Agent.notice_error("Payment failed: #{payment_result.error}")
        NewRelic::Agent.record_custom_metric("Custom/Payments/Failed", 1)
        raise PaymentError, payment_result.error
      end
    end
  end
  
  private
  
  def record_order_metrics
    NewRelic::Agent.record_custom_event("OrderCreated", {
      order_id: id,
      user_id: user_id,
      total_amount: total_amount,
      items_count: order_items.count
    })
  end
  
  def record_status_change
    if saved_change_to_status?
      NewRelic::Agent.record_custom_event("OrderStatusChanged", {
        order_id: id,
        old_status: status_change[0],
        new_status: status_change[1]
      })
    end
  end
  
  add_method_tracer :calculate_total, 'Custom/Order/calculate_total'
  add_method_tracer :process_payment, 'Custom/Order/process_payment'
end

サービスオブジェクトでの監視

ruby
# app/services/order_creation_service.rb
class OrderCreationService
  include NewRelic::Agent::MethodTracer
  
  def initialize(user, order_params)
    @user = user
    @order_params = order_params
  end
  
  def call
    NewRelic::Agent.start_transaction(name: "OrderCreation/execute") do
      ActiveRecord::Base.transaction do
        create_order
        validate_inventory
        process_payment
        send_confirmation_email
        @order
      end
    rescue => e
      NewRelic::Agent.notice_error(e)
      raise
    end
  end
  
  private
  
  def create_order
    NewRelic::Agent.add_custom_attributes(
      service_action: 'create_order',
      user_id: @user.id
    )
    
    @order = @user.orders.build(@order_params)
    @order.save!
    
    NewRelic::Agent.record_custom_metric("Custom/OrderCreation/OrderCreated", 1)
  end
  
  def validate_inventory
    @order.order_items.each do |item|
      unless item.product.sufficient_inventory?(item.quantity)
        raise InsufficientInventoryError, "Not enough inventory for #{item.product.name}"
      end
    end
    
    NewRelic::Agent.record_custom_metric("Custom/OrderCreation/InventoryValidated", 1)
  end
  
  def process_payment
    payment_service = PaymentService.new(@order)
    payment_service.process
    
    NewRelic::Agent.record_custom_metric("Custom/OrderCreation/PaymentProcessed", 1)
  end
  
  def send_confirmation_email
    OrderMailer.confirmation_email(@order).deliver_later
    
    NewRelic::Agent.record_custom_metric("Custom/OrderCreation/EmailQueued", 1)
  end
  
  add_method_tracer :call, 'Custom/OrderCreationService/call'
  add_method_tracer :create_order, 'Custom/OrderCreationService/create_order'
  add_method_tracer :validate_inventory, 'Custom/OrderCreationService/validate_inventory'
end

バックグラウンドジョブの監視

Sidekiq 統合

ruby
# app/workers/order_processing_worker.rb
class OrderProcessingWorker
  include Sidekiq::Worker
  include NewRelic::Agent::Instrumentation::Sidekiq
  
  sidekiq_options retry: 3, backtrace: true
  
  def perform(order_id)
    NewRelic::Agent.start_transaction(name: "OrderProcessing/#{self.class.name}") do
      order = Order.find(order_id)
      
      NewRelic::Agent.add_custom_attributes(
        order_id: order.id,
        order_status: order.status,
        worker_class: self.class.name
      )
      
      process_order(order)
      
      NewRelic::Agent.record_custom_metric("Custom/Workers/OrderProcessed", 1)
    rescue => e
      NewRelic::Agent.notice_error(e)
      raise
    end
  end
  
  private
  
  def process_order(order)
    NewRelic::Agent::MethodTracer.trace_execution_scoped("Custom/Workers/ProcessOrder") do
      # 注文処理ロジック
      order.update!(status: 'processing')
      sleep(2) # 模擬処理時間
      order.update!(status: 'completed')
    end
  end
end

Delayed Job 統合

ruby
# app/jobs/email_notification_job.rb
class EmailNotificationJob < ActiveJob::Base
  include NewRelic::Agent::Instrumentation::ActiveJob
  
  queue_as :default
  
  def perform(user_id, notification_type)
    user = User.find(user_id)
    
    NewRelic::Agent.add_custom_attributes(
      user_id: user.id,
      notification_type: notification_type,
      job_class: self.class.name
    )
    
    case notification_type
    when 'welcome'
      send_welcome_email(user)
    when 'order_confirmation'
      send_order_confirmation(user)
    end
    
    NewRelic::Agent.record_custom_metric("Custom/Jobs/EmailSent", 1)
  end
  
  private
  
  def send_welcome_email(user)
    NewRelic::Agent::MethodTracer.trace_execution_scoped("Custom/Email/Welcome") do
      UserMailer.welcome_email(user).deliver_now
    end
  end
  
  def send_order_confirmation(user)
    NewRelic::Agent::MethodTracer.trace_execution_scoped("Custom/Email/OrderConfirmation") do
      order = user.orders.recent.first
      OrderMailer.confirmation_email(order).deliver_now
    end
  end
end

Sinatra アプリケーションでの統合

ruby
# app.rb
require 'sinatra'
require 'newrelic_rpm'

class MyApp < Sinatra::Base
  configure do
    NewRelic::Agent.manual_start(
      app_name: 'My Sinatra App',
      license_key: ENV['NEW_RELIC_LICENSE_KEY']
    )
  end
  
  before do
    NewRelic::Agent.add_custom_attributes(
      request_method: request.request_method,
      request_path: request.path,
      user_agent: request.user_agent
    )
  end
  
  get '/api/users/:id' do
    NewRelic::Agent.start_transaction(name: 'Web/Users/show') do
      user_id = params[:id]
      
      user = NewRelic::Agent::MethodTracer.trace_execution_scoped("Database/Users/find") do
        User.find(user_id)
      end
      
      if user
        NewRelic::Agent.add_custom_attributes(user_id: user.id)
        content_type :json
        user.to_json
      else
        status 404
        { error: 'User not found' }.to_json
      end
    end
  rescue => e
    NewRelic::Agent.notice_error(e)
    status 500
    { error: 'Internal server error' }.to_json
  end
  
  post '/api/orders' do
    NewRelic::Agent.start_transaction(name: 'Web/Orders/create') do
      order_data = JSON.parse(request.body.read)
      
      order = NewRelic::Agent::MethodTracer.trace_execution_scoped("Business/Orders/create") do
        Order.create!(order_data)
      end
      
      NewRelic::Agent.record_custom_event("OrderCreated", {
        order_id: order.id,
        total_amount: order.total_amount
      })
      
      status 201
      content_type :json
      order.to_json
    end
  rescue => e
    NewRelic::Agent.notice_error(e)
    status 500
    { error: 'Failed to create order' }.to_json
  end
end

カスタム計測の詳細実装

メソッドレベルの詳細監視

ruby
# lib/performance_monitor.rb
module PerformanceMonitor
  extend ActiveSupport::Concern
  
  included do
    extend NewRelic::Agent::MethodTracer
  end
  
  class_methods do
    def monitor_method(method_name, metric_name = nil)
      metric_name ||= "Custom/#{self.name}/#{method_name}"
      add_method_tracer method_name, metric_name
    end
    
    def monitor_class_method(method_name, metric_name = nil)
      metric_name ||= "Custom/#{self.name}/#{method_name}"
      add_method_tracer method_name, metric_name, push_scope: false
    end
  end
  
  def with_monitoring(operation_name)
    start_time = Time.current
    
    result = NewRelic::Agent::MethodTracer.trace_execution_scoped("Custom/#{operation_name}") do
      yield
    end
    
    duration = Time.current - start_time
    NewRelic::Agent.record_custom_metric("Custom/#{operation_name}/Duration", duration)
    
    result
  rescue => e
    NewRelic::Agent.notice_error(e)
    NewRelic::Agent.record_custom_metric("Custom/#{operation_name}/Error", 1)
    raise
  end
end

ビジネスメトリクスの包括的な記録

ruby
# app/services/analytics_service.rb
class AnalyticsService
  include PerformanceMonitor
  
  def self.record_user_action(user, action, metadata = {})
    NewRelic::Agent.record_custom_event("UserAction", {
      user_id: user.id,
      action: action,
      timestamp: Time.current.to_i,
      **metadata
    })
    
    NewRelic::Agent.record_custom_metric("Custom/UserActions/#{action}", 1)
  end
  
  def self.record_business_metric(metric_name, value, attributes = {})
    NewRelic::Agent.record_custom_metric("Business/#{metric_name}", value)
    
    if attributes.any?
      NewRelic::Agent.record_custom_event("BusinessMetric", {
        metric_name: metric_name,
        value: value,
        **attributes
      })
    end
  end
  
  def self.track_conversion(funnel_step, user, metadata = {})
    NewRelic::Agent.record_custom_event("ConversionFunnel", {
      user_id: user.id,
      step: funnel_step,
      timestamp: Time.current.to_i,
      **metadata
    })
    
    NewRelic::Agent.record_custom_metric("Custom/Conversions/#{funnel_step}", 1)
  end
  
  monitor_class_method :record_user_action
  monitor_class_method :record_business_metric
  monitor_class_method :track_conversion
end

パフォーマンス最適化

設定の最適化

yaml
# config/newrelic.yml での最適化設定
production:
  # トランザクション サンプリングの調整
  transaction_tracer:
    transaction_threshold: 1.0  # 1秒以上のトランザクションのみトレース
    top_n: 20                   # 保存するトレース数を制限
    
  # スロークエリの設定
  slow_sql:
    enabled: true
    explain_threshold: 0.5      # 0.5秒以上のクエリを説明
    
  # エラー レート制限
  error_collector:
    max_stack_trace_lines: 50   # スタックトレースの行数制限
    
  # メトリクス制限
  agent_limits:
    transaction_traces_nodes: 2000
    custom_attributes: 64
    custom_events_max_samples_stored: 1200

メモリ使用量の監視

ruby
# config/initializers/performance_monitoring.rb
if Rails.env.production?
  Thread.new do
    loop do
      begin
        # メモリ使用量の監視
        memory_usage = `ps -o rss= -p #{Process.pid}`.to_i * 1024 # バイト単位
        NewRelic::Agent.record_custom_metric("Custom/Memory/RSS", memory_usage)
        
        # GC統計の記録
        gc_stat = GC.stat
        NewRelic::Agent.record_custom_metric("Custom/GC/Count", gc_stat[:count])
        NewRelic::Agent.record_custom_metric("Custom/GC/HeapUsed", gc_stat[:heap_used])
        NewRelic::Agent.record_custom_metric("Custom/GC/HeapLength", gc_stat[:heap_length])
        
        sleep 60 # 1分間隔
      rescue => e
        Rails.logger.error "Performance monitoring error: #{e.message}"
      end
    end
  end
end

本番環境でのデプロイメント

Dockerfile での設定

dockerfile
FROM ruby:3.2-alpine

WORKDIR /app

# Gemfile のコピーと bundle install
COPY Gemfile Gemfile.lock ./
RUN bundle install --without development test

# アプリケーションコードのコピー
COPY . .

# New Relic 設定の環境変数
ENV NEW_RELIC_ENABLED=true
ENV NEW_RELIC_LOG_LEVEL=info

# Rails アプリケーションの起動
CMD ["bundle", "exec", "rails", "server", "-b", "0.0.0.0"]

Kubernetes での設定

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: ruby-app
spec:
  template:
    spec:
      containers:
      - name: app
        image: myrubyapp:latest
        env:
        - name: NEW_RELIC_LICENSE_KEY
          valueFrom:
            secretKeyRef:
              name: newrelic-secret
              key: license_key
        - name: NEW_RELIC_APP_NAME
          value: "MyRubyApp (Kubernetes)"
        - name: RAILS_ENV
          value: "production"
        resources:
          requests:
            memory: "256Mi"
            cpu: "200m"
          limits:
            memory: "512Mi"
            cpu: "500m"

Capistrano でのデプロイメント連携

ruby
# config/deploy.rb
after 'deploy:updated', 'newrelic:notice_deployment'

namespace :newrelic do
  desc 'Notify New Relic of deployment'
  task :notice_deployment do
    on roles(:app) do
      within release_path do
        execute :bundle, :exec, 'newrelic', 'deployments', 
                '--revision', fetch(:current_revision),
                '--description', "Deployed at #{Time.current}"
      end
    end
  end
end

トラブルシューティング

エージェント状態の診断

ruby
# app/controllers/health_controller.rb
class HealthController < ApplicationController
  def newrelic_status
    status = {
      agent_enabled: defined?(NewRelic::Agent),
      connected: NewRelic::Agent.agent&.connected?,
      app_name: NewRelic::Agent.config[:app_name],
      license_key_configured: NewRelic::Agent.config[:license_key].present?
    }
    
    if defined?(NewRelic::Agent)
      status.merge!(
        agent_version: NewRelic::VERSION::STRING,
        host: NewRelic::Agent.config[:host],
        port: NewRelic::Agent.config[:port]
      )
    end
    
    render json: status
  end
end

Ruby アプリケーションでの New Relic APM 導入により、Ruby エコシステムの豊富なフレームワークとライブラリを活かした詳細なパフォーマンス監視を実現できます。Rails の規約に従った自動計測と柔軟なカスタマイズオプションにより、アプリケーションの継続的な最適化を効果的に支援します。


関連記事: 分散トレーシング設定関連記事: APM設定完全リファレンス