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

ActiveRecordがデータベースへの不必要なトリップを回避するためにキャッシュを使用する方法

キャッシュを説明する一般的な方法は、後ですばやく取得できるように、コードの結果を保存することです。場合によっては、これは、後で再計算する必要がないように、計算された値を格納することを意味します。ただし、計算を実行せずにデータをメモリに保持するだけでデータをキャッシュして、ハードドライブからの読み取りやネットワーク要求の実行を回避することもできます。

この後者の形式は、データベースが別のサーバーで実行されることが多いActiveRecordに特に関係があります。したがって、クエリが再度実行されるときにデータベースサーバーにかかる負荷は言うまでもなく、すべての要求でネットワークトラフィックのオーバーヘッドが発生します。

幸いなことに、Rails開発者にとって、ActiveRecord自体は、おそらく私たちがそれを意識することなく、すでに多くのことを処理しています。これは生産性にとっては良いことですが、舞台裏で何がキャッシュされているかを知ることが重要な場合があります。たとえば、値が別のプロセスによって変更されていることがわかっている(または予期している)場合、または絶対に最新の値が必要です。このような場合、ActiveRecordは、データのキャッシュされていない読み取りを強制するために、いくつかの「エスケープハッチ」を提供します。

ActiveRecordの遅延評価

ActiveRecordの遅延評価自体はキャッシュではありませんが、後でコード例で遭遇するため、簡単な概要を説明します。 ActiveRecordクエリを作成する場合、多くの場合、コードはデータベースへの即時呼び出しを発行しません。これにより、複数の.whereをチェーンできます。 毎回データベースにアクセスする必要のない句:

@posts = Post.where(published: true)
# no DB hit yet
@posts = @posts.where(publied_at: Date.today)
# still nothing
@posts.count
# SELECT COUNT(*) FROM "posts" WHERE...

これにはいくつかの例外があります。たとえば、.findを使用する場合 、.find_by.pluck.to_a 、または.first 、追加の句を連鎖させることはできません。以下のほとんどの例では、.to_aを使用します DB呼び出しを強制する簡単な方法として。

Railsコンソールでこれを実験している場合は、「エコー」モードをオフにする必要があることに注意してください。それ以外の場合、コンソール(irbまたはpry)は.inspectを呼び出します 'Enter'を押すと、オブジェクトでDBクエリが強制されます。エコーモードを無効にするには、次のコードを使用できます。

conf.echo = false # for irb
pry_instance.config.print = proc {} # for pry

ActiveRecordの関係

ここで取り上げるActiveRecordの組み込みキャッシュの最初の部分はリレーションです。たとえば、典型的なUser-Postsがあります 関係:

# app/models/user.rb
class User < ApplicationRecord
  has_many :posts
end

# app/models/post.rb
class Post < ApplicationRecord
  belongs_to :user
end

これにより、便利なuser.postsが得られます およびpost.user 関連するレコードを見つけるためにデータベースクエリを実行するメソッド。コントローラとビューでこれらを使用しているとしましょう:

# app/controllers/posts_controller.rb
class PostsController < ApplicationController
  def index
    @user = User.find(params[:user_id])
    @posts = @user.posts
  end
...

# app/views/posts/index.html.erb
...
<%= render 'shared/sidebar' %>
<% @posts.each do |post| %>
  <%= render post %>
<% end %>

# app/views/shared/_sidebar.html.erb
...
<% @posts.each do |post| %>
  <li><%= post.title %></li>
<% end %>

基本的なindexがあります @user.postsを取得するアクション 。前のセクションと同様に、データベースクエリはこの時点では実行されていません。次に、Railsはindexをレンダリングします ビュー。これにより、サイドバーがレンダリングされます。サイドバーは@posts.each ...を呼び出します 、そしてこの時点で、ActiveRecordはデータベースクエリを実行してデータを取得します。

次に、残りのindexに戻ります。 テンプレート、別の @posts.each;ただし、今回はデータベース呼び出しはありません。何が起こっているのかというと、ActiveRecordはこれらすべての投稿をキャッシュしていて、データベースから再度読み取ろうとすることはありません。

エスケープハッチ

ActiveRecordに関連するレコードを再度フェッチさせたい場合があります。おそらく、別のプロセス(たとえば、バックグラウンドジョブ)によって変更されていることがわかっています。もう1つの一般的な状況は、データベース内の最新の値を取得して、コードが正しく更新したことを検証する自動テストです。

状況に応じて、これを行うための2つの一般的な方法があります。最も一般的な方法は、単に.reloadを呼び出すことだと思います アソシエーションで、キャッシュされたものをすべて無視し、データベースから最新バージョンを取得することをActiveRecordに通知します:

@user = User.find(1)
@user.posts # DB Call
@user.posts # Cached, no DB call
@user.posts.reload # DB call
@user.posts # Cached new version, no DB call

もう1つのオプションは、ActiveRecordモデルの新しいインスタンスを取得することです(たとえば、findを呼び出すことによって) もう一度):

@user = User.find(1)
@user.posts # DB Call
@user.posts # Cached, no DB call
@user = User.find(1) # @user is now a new instance of User
@user.posts # DB Call, no cache in this instance

キャッシングの関係は良好ですが、多くの場合、複雑な.where(...)になります。 単純な関係ルックアップを超えたクエリ。これがActiveRecordのSQLキャッシュの出番です。

ActiveRecordのSQLキャッシュ

ActiveRecordは、パフォーマンスを高速化するために実行したクエリの内部キャッシュを保持します。ただし、このキャッシュは特定のアクションに関連付けられていることに注意してください。アクションの開始時に作成され、アクションの終了時に破棄されます。これは、1つのコントローラーアクション内で同じクエリを2回実行した場合にのみ、これが表示されることを意味します。 また Railsコンソールでキャッシュが使用されていないことを意味します。キャッシュヒットは、CACHEとともにRailsログに表示されます 。たとえば、

class PostsController < ApplicationController
  def index
    ...
    Post.all.to_a # to_a to force DB query

    ...
    Post.all.to_a # to_a to force DB query

次のログ出力を生成します:

  Post Load (2.1ms)  SELECT "posts".* FROM "posts"
  ↳ app/controllers/posts_controller.rb:11:in `index'
  CACHE Post Load (0.0ms)  SELECT "posts".* FROM "posts"
  ↳ app/controllers/posts_controller.rb:13:in `index'

ActiveRecord::Base.connection.query_cacheを出力することで、アクションのキャッシュ内にあるものを実際に確認できます。 (またはActiveRecord::Base.connection.query_cache.keys SQLクエリのみ)。

エスケープハッチ

SQLキャッシュをバイパスする必要がある理由はおそらく多くありませんが、それでも、uncachedを使用して、ActiveRecordにSQLキャッシュをバイパスさせることができます。 ActiveRecord::Baseのメソッド :

class PostsController < ApplicationController
  def index
    ...
    Post.all.to_a # to_a to force DB query

    ...
    ActiveRecord::Base.uncached do
      Post.all.to_a # to_a to force DB query
    end

ActiveRecord::Baseのメソッドなので 、読みやすさが向上する場合は、モデルクラスの1つを介して呼び出すこともできます。たとえば、

  Post.uncached do
    Post.all.to_a
  end
カウンターキャッシュ

Webアプリケーションでは、リレーションシップ内のレコードをカウントするのが一般的です(たとえば、ユーザーにX件の投稿がある、またはチームアカウントにY人のユーザーがいる)。非常に一般的であるため、ActiveRecordには、大量の.countがないように、カウンターを自動的に最新の状態に保つ方法が含まれています。 データベースリソースを使い果たした呼び出し。それを有効にするのにほんの数ステップしかかかりません。まず、counter_cacheを追加します ActiveRecordが私たちのためにカウントをキャッシュすることを知っているように関係に:

class Post < ApplicationRecord
  belongs_to :user, counter_cache: true
end

また、Userに新しい列を追加する必要があります 、カウントが保存される場所。この例では、これはUser.posts_countになります。 。シンボルをcounter_cacheに渡すことができます 必要に応じて列名を指定します。

rails generate migration AddPostsCountToUsers posts_count:integer
rails db:migrate

これで、カウンターは0(デフォルト)に設定されます。アプリケーションにすでにいくつかの投稿がある場合は、それらを更新する必要があります。 ActiveRecordはreset_countersを提供します 本質的な詳細を処理するメソッドなので、IDを渡して、更新するカウンターを指定するだけです。

User.all.each do |user|
  User.reset_counters(user.id, :posts)
end

最後に、このカウントが使用されている場所を確認する必要があります。これは、.countを呼び出すためです カウンターをバイパスし、常にCOUNT()を実行します SQLクエリ。代わりに、.sizeを使用できます 、カウンターキャッシュが存在する場合はそれを使用することを認識しています。余談ですが、デフォルトで.sizeを使用することもできます。 また、関連付けがすでに存在する場合は関連付けを再読み込みしないため、データベースへのアクセスを節約できる可能性があります。

結論

ほとんどの場合、ActiveRecordの内部キャッシュは「正しく機能」します。それを回避する必要のあるケースをたくさん見たとは言えませんが、他のすべての場合と同様に、「内部」で何が起こっているかを知ることで、何かが必要な状況に遭遇したときに時間と苦痛を節約できます異常です。

もちろん、Railsが舞台裏でキャッシュを行っているのはデータベースだけではありません。 HTTP仕様には、変更されていないデータを再送信する必要がないように、クライアントとサーバー間で送信できるヘッダーが含まれています。キャッシングに関するこのシリーズの次の記事では、304 (Not Modified)を見ていきます。 HTTPステータスコード、Railsがそれを処理する方法、およびこの処理を微調整する方法。


  1. 偶発的なヘルプを回避するためにWindows10でF1ヘルプキーを無効にする方法

    Windowsキーボードのファンクションキー(F1からF12)には、特別に割り当てられた役割があります。例: F1 現在使用しているアプリケーションのヘルプページを開きます。これは多くの状況で役立ちます。ただし、何らかの理由で無効にすることをお勧めします。 WindowsオペレーティングシステムにはF1キーを無効にする方法はありませんが、それを実行するための微調整をいくつか紹介します。この記事では、キーボードのF1キーを一時的に無効にする方法と、いくつかの方法を使用してキーを再度有効にする方法を学習します。 キーボードのF1ヘルプキーを無効にする レジストリエディタなどのWindo

  2. アプリで先延ばしを避ける方法は?

    私たちはしばしば、いずれかのソーシャル メディア アプリを利用しています。それは私たちの生活の不可欠な部分になっており、必要なすべてのタスクを圧倒しているように見えることもあります.生活からスマートフォンを取り除いたら、人々がどのように見えるか見てみましょう。 物事を遅らせたり延期したりすることは大きな問題であり、この習慣では目標を達成することは不可能に思えます.これは、私たちの日常生活におけるスマートフォンの使用の増加に伴い、より大きな問題と見なされています.驚いたことに、先延ばしに打ち勝つのに役立つアプリがいくつかあるため、解決策は携帯電話にもあります。 私たちは、スマートフォンへの依