モノリス特有の運用課題
こんにちは。バックエンドエンジニアの須貝です。
タイミーのバックエンドAPIはモノリスなRuby on Railsアプリケーションです。2024年12月現在、このリポジトリ上で10程度のチームが開発しています。
モノリスは利点も多いのですが、チームが増加するにつれて運用面でモノリス特有の難しさを感じることも増えてきました。例えば、SentryやDatadogで何かエラーや問題を検知しても「これはどこのチームの持ち物なのか」という責任があいまいになってしまい改善がなかなか進まない、基盤的なチームがエラーのトリアージをするにしても調査の負担が大きい、といった課題がありました。
SentryとDatadogにコードオーナーを送る
「まずどこのチームの持ち物なのかわかりやすくしよう」ということで、SentryとDatadogにコードオーナー(リポジトリ内の特定のファイルやディレクトリに責任を持つチーム)の情報を送信するようにしました。すでにバックエンドのリポジトリではGitHubのCODEOWNERSを導入していたのでこちらを利用しました。
なお、CODEOWNERSファイルのパースと、ファイル・クラスなどからコードオーナーを取得する処理ではCodeOwnershipというgemを活用しています。 github.com
やることはシンプルでSentryとDatadogに情報を送信する際に、CodeOwnership gemを使ってコードオーナー名を取得し、tagのような形で付与してあげるだけです。以下、それぞれの実装例を紹介します。
Sentry編
まず、Sentryにコードオーナーを送信するところから見ていきましょう。
CodeOwnershipはbacktraceを渡してコードオーナーを返すこともできるので、Sentryにコードオーナー情報を送信する実装も簡潔です。以下の実装例のようにsentry-ruby gemの設定に数行追加するだけで済みます。
Sentry.init do |config| # ...諸々省略 config.before_send = ->(event, hint) do if hint[:exception] # コードオーナー名を取得 code_owner = CodeOwnership.for_backtrace(hint[:exception].backtrace)&.name || 'unknown' # code_ownerというtagでSentryに情報を送信する event.tags[:code_owner] = code_owner end end end
CodeOwnership.for_backtrace
は渡したbacktraceを先頭から見ていき、コードオーナーが見つかった場合はそのコードオーナーを返す便利なメソッドです。
これでSentry側にtagとしてコードオーナー情報を送信するようになったので、Sentryの管理画面上でも確認できるようになっています。下記の画像のようにIssueの画面のTagsにコードオーナー名(この例ではWorking Relationsというチーム)が表示されています。
さらに便利にするためにSentryから飛んでくるSlack通知でもコードオーナー情報を見られるようにしました。SentryのAlertルールの編集で「Set conditions」の「THEN」の「and show tags」の箇所にcode_owner
(上記のコード例で設定したtag名)を追加するだけです。
これでSlack通知で下記のようにコードオーナー名(この例ではIronBankというチーム)が見られるようになりました。
Datadog編
続いてDatadogのAPMでもコードオーナー情報を見られるようにしていきます。Controller(APIリクエスト)とSidekiqのjob(非同期処理)の2パターンに対応します。
まずControllerです。下記のようなModuleを書いてControllerでincludeします。
module DatadogTaggable extend ActiveSupport::Concern included do before_action :set_datadog_tags end private def set_datadog_tags span = Datadog::Tracing.active_span return unless span code_owner = CodeOwnership.for_class(self.class)&.name || 'unknown' span.set_tag('code_owner', code_owner) rescue StandardError => e Rails.logger.warn("Failed to set code_owner tag to span: #{e.message} (#{e.class})") end end
すると下記のようにDatadogのAPMでコードオーナー名(この例ではWork Wellというチーム)が確認できるようになります。
次はSidekiqです。下記のようなMiddlewareを書いて上記のModule同様にDatadogのspanにtagをセットしてあげましょう。
class SidekiqDatadogMiddleware include Sidekiq::ServerMiddleware # loggerを使用するため def call(worker, _job, _queue) span = Datadog::Tracing.active_span if span code_owner = CodeOwnership.for_class(worker.class)&.name || 'unknown' span.set_tag('code_owner', code_owner) end rescue StandardError => e logger.warn("Failed to set code_owner tag to span: #{e.message} (#{e.class})") ensure yield end end
あとはSidekiqの設定で上記のMiddlewareを追加してあげれば完了です。
Sidekiq.configure_server do |config| # ...略 config.server_middleware do |chain| chain.add SidekiqDatadogMiddleware end # ...略 end
おわりに
実際に上記の仕組みを導入してみて、自分がSentryのエラー通知をよく見ているというのもあり、ぱっと見で担当チームがわかるのはだいぶ助かるなあと日々感じています。主要なエンドポイントやJobは自分の中に脳内マッピングができている(気がする)ものの、さすがに限界を感じ始めていたのでこれ無しではもう生きていけません。後はSentryは最終的にはSlackでメンションまでできるようにしたいのですけど、あまり賢い方法が思いつかずに困っているので良い案のある方は教えてください。
また、上記では紹介しきれなかったのですが、ログにもコードオーナー名を付与しているのでDatadog Logsでもコードオーナーを見られるようにしています。コードオーナーを条件に入れてアラートを設定したり、ダッシュボードを作ることも可能になっているので、チーム単位でのオブザーバビリティの向上にも寄与するのではないかと考えています。
最後に自分が所属しているRailway(Rails WayではなくRailway)というチームではこんな感じでバックエンドアプリケーションの横断的な課題を解いたり、他のバックエンドエンジニアの開発生産性を上げたりするような活動を行っています。なんと、いま絶賛一緒に働いてくれる方を探しているので、カジュアル面談でお話しましょう。