Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

ActiveRecordパフォーマンスのトラブルシューティング

ActiveRecordは、RubyonRailsの最も魅力的な機能です。通常、内部の動作について心配する必要はありませんが、心配する場合は、AppSignalが内部で何が起こっているのかを知るのにどのように役立つかを説明します。

ActiveRecordとは何ですか?

ActiveRecordについて話すには、最初にフレームワーク、特にMVCフレームワークについて考える必要があります。 MVCはModel-View-Controllerの略で、グラフィックおよびWebアプリケーションで人気のあるソフトウェアデザインパターンです。

MVCフレームワークは次のもので構成されています:

  • モデル :ビジネスロジックとデータの永続性を処理します。
  • 表示 :プレゼンテーション層を駆動し、ユーザーインターフェイスを描画します。
  • コントローラー :すべてを結び付けます。

ActiveRecordはモデルです RubyinRailsフレームワークのコンポーネント。コードとデータの間に抽象化レイヤーが導入されるため、SQLコードを自分で作成する必要はありません。各モデルは1つのテーブルにマップされ、CRUD操作(作成、読み取り、更新、削除)を実行するためのさまざまなメソッドを提供します。

AppSignalを使用したActiveRecordの監視

抽象化は魔法のように感じます。抽象化は、私たちが知る必要のない詳細を無視し、目前のタスクに集中するのに役立ちます。しかし、物事が期待どおりに機能しない場合、それらが追加する複雑さにより、根本的な原因を特定することが難しくなる可能性があります。 AppSignalは、Railsで実際に起こっていることの詳細な内訳を提供してくれます。

応答時間グラフ

問題を最初に見てみましょう。パフォーマンスの問題のトラブルシューティングは、反復的なプロセスです。 RailsアプリケーションがAppSignalに報告したら、インシデントに移動します>パフォーマンス

応答時間グラフには、各名前空間の応答時間のパーセンタイルが表示されます。 ActiveRecordイベントは、クエリが実行されるリクエストまたはバックグラウンドジョブの名前空間に自動的に割り当てられます。

次に、イベントグループのグラフを見てください。カテゴリごとに消費された時間を示します。 active_recordが費やした相対時間を確認します 。すべての名前空間での使用法を確認してください。

グラフから、コード最適化の取り組みをどこに集中させるべきかがすぐにわかります。

パフォーマンスグラフダッシュボードを表示しているときに、応答時間とスループットをチェックして、アプリケーションに通常よりも高いアクティビティがないことを確認します。

低速クエリダッシュボード

問題がデータに依存していることがわかったので、ズームインして根本的な原因を特定できるかどうかを見てみましょう。

改善を開きます>遅いクエリ ダッシュボード。このページには、全体の時間への影響によってランク付けされたSQLクエリのリストが表示されます。 ActiveRecordから発信されたすべてのクエリは、sql.active_recordとして表示されます イベント。

一番上のクエリをクリックして、詳細を確認してください。ダッシュボードには、平均期間とクエリテキストが表示されます。

下にスクロールすると、過去数時間のクエリの応答時間と元のアクションが表示されます。

一部のアクションに関連するインシデントがある場合があります。これは、クエリの実行中にAppSignalがパフォーマンスインシデントを作成したことを意味しますが、必ずしもActiveRecordがその原因であるとは限りません。

パフォーマンス測定ダッシュボード

AppSignalが新しいエンドポイントまたはバックグラウンドジョブを記録すると、パフォーマンス測定インシデントが開かれます。

インシデントはパフォーマンスにあります>問題リスト ダッシュボード。

インシデントページには、各MVCコンポーネントの経過時間と割り当て数が表示されます。 ActiveRecordの問題は、active_recordで長時間発生します カテゴリ。

イベントのタイムラインは、イベントが時間の経過とともにどのように進行したかを示しています。

ActiveRecordの問題を見つける

このセクションでは、AppSignalがいくつかの一般的なActiveErrorの問題を特定するのにどのように役立つかを説明します。

関連する列の選択

データベースの知恵は、ジョブに必要な列を常に取得する必要があると言っています。たとえば、SELECT * FROM peopleの代わりに SELECT first_name, surname, birthdate FROM people 。それはすべてうまくいっていますが、Railsでどのように行うのですか?

デフォルトでは、ActiveRecordはすべての列を取得します。

Person.all.each {
      # process data
}

幸い、selectがあります 必要な列を選択する方法:

Person.select(:name, :address, :birthdate).each {
      # process data
}

細部にこだわっているように聞こえるかもしれません。しかし、幅の広いテーブルでは、すべての列を選択するのは無駄です。これが発生すると、ActiveRecordが大量のメモリを割り当てます。

N+1の問題

N + 1の問題は、アプリケーションがデータベースから一連のレコードを取得し、それをループするときに発生します。これにより、アプリケーションはN + 1クエリを実行します。ここで、Nは最初に取得された行数です。ご想像のとおり、このパターンはテーブルが大きくなるにつれてスケーリングが不十分になります。 AppSignalが特に​​警告するほどの有害な問題です:

N + 1の問題は通常、関連するモデルで発生します。 Personモデルがあると想像してください:

class Person < ApplicationRecord
    has_many :addresses
end

各人は多くの住所を持つことができます:

class Address < ApplicationRecord
    belongs_to :person
end

データを取得する最も簡単な方法は、N+1の問題につながります。

class RelatedTablesController < ApplicationController
    def index
        Person.all.each do |person|
            person.addresses.each do |address|
            address.address
            end
        end
    end
end

AppSignalで、アプリケーションがSELECTを実行していることがわかります。 お一人様:

この特定のケースの修正は簡単です。includesを使用して、必要な列のクエリを最適化するようにActiveRecordに指示します。

class RelatedTablesController < ApplicationController
    def index
        Person.all.includes(:addresses).each do |person|
            person.addresses.each do |address|
            address.address
            end
        end
    end
end

これで、N+1の代わりに2つのクエリがあります。

Processing by RelatedTablesController#index as HTML
   (0.2ms)  SELECT sqlite_version(*)
  ↳ app/controllers/related_tables_controller.rb:12:in `index'
  Person Load (334.6ms)  SELECT "people".* FROM "people"
  ↳ app/controllers/related_tables_controller.rb:12:in `index'

  Address Load (144.4ms)  SELECT "addresses".* FROM "addresses" WHERE "addresses"."person_id" IN (1, 2, 3, . . .)

テーブルごとに必要最小限のクエリを実行する

これはN+1の問題と混同されることがありますが、少し異なります。テーブルにクエリを実行するときは、読み取り操作を最小限に抑えるために必要と思われるすべてのデータを取得する必要があります。ただし、冗長なクエリをトリガーする、見た目が無害なコードがたくさんあります。たとえば、countがどのようにカウントされるかを見てください 常にSELECT COUNT(*)になります 次のビューでクエリを実行します:

<ul>
    <% @people.each do |person| %>
        <li><%= person.name %></li>
    <% end %>
</ul>
 
<h2>Number of Persons: <%= @people.count %></h2>

ActiveRecordは2つのクエリを実行します:

Rendering duplicated_table_query/index.html.erb within layouts/application
  (69.1ms)  SELECT COUNT(*) FROM "people" WHERE "people"."name" = ?  [["name", "John Waters"]]
↳ app/views/duplicated_table_query/index.html.erb:3
Person Load (14.6ms)  SELECT "people".* FROM "people" WHERE "people"."name" = ?  [["name", "John Waters"]]
↳ app/views/duplicated_table_query/index.html.erb:6

AppSignalで気付く症状は、2つのactive_recordがあることです。 同じテーブルのイベント:

現実には、2つのクエリは必要ありません。必要なすべてのデータがすでにメモリにあります。この場合の解決策は、countを交換することです sizeを使用 :

<ul>
<% @people.each do |person| %>
<li><%= person.name %></li>
<% end %>
</ul>
 
<h2>Number of Persons: <%= @people.size %></h2>

これで、単一のSELECTができました。 、あるべき姿:

Rendering duplicated_table_query/index.html.erb within layouts/application
Person Load (63.2ms)  SELECT "people".* FROM "people" WHERE "people"."name" = ?  [["name", "Abdul Strosin"]]
↳ app/views/duplicated_table_query/index.html.erb:5

もう1つの解決策は、プリロードを使用してデータをメモリにキャッシュすることです。

Railsでの集合データの計算

集計は、一連のデータに基づいて値を計算するために使用されます。データベースは、大きなデータセットの操作に優れています。これが彼らが行うことであり、私たちがそれらを使用する目的です。一方、Railsを使用して集計するには、データベースからすべてのレコードを取得し、それらをメモリに保持してから、高水準コードを使用して計算する必要があるため、スケーリングされません。

maxのようなRuby関数を使用する場合は常に、Railsで集計を行っています。 、min 、またはsum ActiveRecord要素または他の列挙可能要素を介して。

class AggregatedColumnsController < ApplicationController
    def index
        @mean = Number.pluck(:number).sum()
    end
end

幸い、ActiveRecordモデルには、データベース内の集計関数にマップする特定のメソッドが含まれています。たとえば、次のクエリはSELECT SUM(number) FROM ...にマップされます。 、これは前の例よりもはるかに高速で安価に実行できます:

# controller
 
class AggregatedColumnsController < ApplicationController
    def index
        @mean = Number.sum(:number)
    end
end
Processing by AggregatedColumnsController#index as */*
  (2.4ms)  SELECT SUM("numbers"."number") FROM "numbers"

より複雑な、または組み合わせた集計関数が必要な場合は、生のSQLコードを少し含める必要があります。

sql = "SELECT AVG(number), STDDEV(number), VAR(number) FROM ..."
@results = ActiveRecord::Base.connection.execute(sql)

大規模なトランザクションの管理

SQLトランザクションは、一貫性のあるアトミックな更新を保証します。トランザクションを使用して変更を加えると、すべての行が正常に更新されるか、すべてがロールバックされます。いずれの場合も、データベースは常に一貫性のある状態に保たれます。

ActiveRecord::Base.transactionを使用して、変更のバッチを1つのトランザクションにバンドルできます。 。

class BigTransactionController < ApplicationController
    def index
        ActiveRecord::Base.transaction do
            (1..1000).each do
                Person.create(name: 'Les Claypool')
            end
        end
    end
end

大規模なトランザクションを使用するための正当なケースはたくさんあります。ただし、これらはデータベースの速度を低下させるリスクに直面します。さらに、特定のしきい値を超えるトランザクションは、データベースがそれらを拒否する結果になります。

トランザクションが大きすぎることの最初の兆候は、commit transactionに多くの時間を費やしていることです。 イベント:

データベース自体の構成の問題を除けば、解決策はトランザクションをより小さなチャンクに分割することです。

データベースの監視

コードを検索しても、問題が見つからない場合があります。次に、データベース自体に問題がある可能性があります。データベースエンジンは複雑で、メモリ不足、デフォルト設定、インデックスの欠落、不便なスケジュールのバックアップジョブなど、多くの問題が発生する可能性があります。データベースを実行しているマシンに関する情報を取得しない限り、全体像を完成させることはできません。

独自のデータベースを実行している場合は、スタンドアロンエージェントをインストールしてホストメトリックをキャプチャできます。スタンドアロンエージェントの使用方法の詳細については、「StatsDおよびAppSignalのスタンドアロンエージェントを使用したシステムの監視」を参照してください。

次の兆候は、データベースで何かが起こっていることを示している可能性があります。 検査に移動します>ホストメトリクス サーバーのリソース使用量を確認するダッシュボード:

  • メモリ使用量が多い :データベースを正しく実行するには、他のほとんどのシステムよりも多くのメモリが必要です。データセットが大きくなるにつれて、メモリ要件は通常それに沿って拡大します。時々、メモリを追加するか、データセットを異なるマシン間で分割する必要があります。
  • スワップの使用法 :理想的には、データベースマシンはスワップメモリ​​をまったく必要としないはずです。交換すると、データベースのパフォーマンスが低下します。その存在は、より詳細な構成またはメモリの問題があることを意味します。
  • I/Oの使用率が高い :ディスクアクティビティのピークは、データベースのインデックスの再作成やバックアップなどのメンテナンスタスクが原因である可能性があります。ピーク時にこれらのタスクを実行すると、パフォーマンスが確実に低下します。

👋この記事が気に入った場合は、Ruby(on Rails)のパフォーマンスについてさらに多くのことを書いています。Rubyのパフォーマンス監視チェックリストを確認してください。

結論

パフォーマンスの問題の診断は決して簡単な作業ではありません。今日は、Appsignalを使用して問題の原因をすばやく特定する方法を学びました。

AppSignalとRubyonRailsについて学び続けましょう:

  • ActiveRecordのパフォーマンス:N+1クエリのアンチパターン
  • レールは高速です:ビューのパフォーマンスを最適化します
  • RubyonRailsのパターンとアンチパターンの概要

P.S。 Ruby Magicの投稿をマスコミから離れたらすぐに読みたい場合は、Ruby Magicニュースレターを購読して、投稿を1つも見逃さないでください。


  1. Redisのパフォーマンスについての考え

    単純なキャッシングから数テラバイトサイズのセットアップまで、さまざまなユースケースでRedisを使用している多くの人々や企業と話す特権が与えられているので、私が他の何よりも取り上げるよう求められているトピックの1つはパフォーマンスです。 Redisは、パフォーマンスへのアプローチ方法が異なります。ほとんどではないにしても、多くのデータベースサーバーでパフォーマンスを向上させようとしています。 Redisの目標は、速度を落とさないことです。これは非常に異なるアプローチであり、それを利用するには異なる考え方が必要です。 パフォーマンスメトリクス–レイテンシーは王様ですか? Redisを使用すると

  2. Windows 11 を高速化する 12 の方法

    Windows は時間の経過とともに遅くなることが知られています。そのため、一部のユーザーが Windows 11 の速度がすでに低下していることについて懸念を表明したときは驚きました。これにはさまざまな原因が考えられますが、ありがたいことに、それぞれのシナリオで、いくつかの簡単な調整を行うだけで、システムの速度が大幅に向上します。低速のコンピューターは効率が低下します。ただし、一般に信じられていることとは反対に、Windows コンピューターは時間の経過とともに速度が低下するようには設計されていません。システムのパフォーマンスが低下していたり​​、アプリの起動に時間がかかっている場合は、シス