ActiveSupport::Notifications のインストゥルメントで不要フレームを除去
ActiveSupport::Notifications.instrument がブロックを渡す際に生成していた余分なスタックフレームが削除され、バックトレースや Flamegraph の可読性が向上します。機能的な振る舞いは変わらないため、既存アプリケーションへの影響はありません。
背景
ActiveSupport::Notifications の instrument メソッドは、内部でブロックを直接 instrumenter.instrument に委譲していました。この委譲により、呼び出しスタックに不要なフレームが残り、デバッグや性能分析時にノイズが増えていました。PR #57928 はこのフレームを排除し、同じ振る舞いを保ちつつトレースをシンプルにすることを目的としています。その結果、開発者がバックトレースや Flamegraph を読む際の負担が軽減されます。
技術的な変更
この PR では instrument のシグネチャを def instrument(name, payload = {}, &block) に変更し、ブロックを明示的に受け取るようにしました。その上で、notifier.listening?(name) が真の場合は instrumenter.instrument(name, payload, &block) を呼び出すだけに留め、ブロックの実行は instrumenter に委ねません。else ブロックでは、ブロックが与えられたときに yield payload するだけです。この変更により、余分な yield とブロックの再ラップが排除され、スタックが一段階浅くなります。
変更前:
def instrument(name, payload = {})
if notifier.listening?(name)
instrumenter.instrument(name, payload) { yield payload if block_given? }
else
yield payload if block_given?
end
end
変更後:
def instrument(name, payload = {}, &block)
if notifier.listening?(name)
instrumenter.instrument(name, payload, &block)
else
yield payload if block_given?
end
end
設計判断
設計上、instrument の外部公開インターフェースは変更せず、既存の呼び出し側コードへの影響をゼロに保ちました。ブロックを直接受け取り instrumenter.instrument に渡すだけの実装にすることで、不要なフレームが削除され、トレース情報が整理されます。このアプローチは後方互換性とデバッグ体験の改善という二つの要件を同時に満たす、慎重な判断といえます。結果として、Rails のコア機能はそのままに、開発者の診断コストが下がります。
まとめ
不要フレームの除去は機能的な変化を伴わないものの、バックトレースや Flamegraph の可読性を向上させ、開発者体験を改善します。今回の変更は最小限のコード差分で実装され、既存コードへの互換性を維持したまま内部処理をすっきりさせました。今後も同様のトレース最適化が Rails の他のコンポーネントでも検討される可能性があります。