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

複雑なデータモデルをキャッシュするためのより高速な方法

データモデルが複雑になり、APIがその悲しい1秒の応答時間に達した場合、通常は簡単な修正があります::includesモデルの関連付けをプリロードすると、SQL呼び出しはそれほど多くなりません。 そして、それはあなたにたくさんの時間を節約することができます。

しかし、その後、サイトの速度が再び低下し、応答のキャッシュについて考えます。そして今、あなたは問題を抱えています。キャッシュから応答を取得したい場合:

results = {lawyer_1: 1, lawyer_2: 2, lawyer_3: 3}
cached_objects = Rails.cache.fetch_multi(results.keys) do |key|
  Lawyer.find(results[key]).as_json
end

これで、すべての:includesが失われました 。あなたは両方を持つことができますか? キャッシュされたオブジェクトに対して高速な応答を取得し、それでもキャッシュにないオブジェクトをすばやくロードするにはどうすればよいですか?

やることがたくさんあるので、それについて考えるのは難しいです。問題を細かく分割して、次の簡単なステップを考え出すと、より簡単になります。

では、最初にできることは何ですか? 多くのことを行うには、キャッシュ内にあるオブジェクトと、まだ見つける必要のあるオブジェクトを知る必要があります。

キャッシュされたものとキャッシュされていないものを分離する

したがって、キャッシュキーがたくさんあるとしましょう:

cache_keys = [:key_1, :key_2, :key_3]

これらのどれがキャッシュにあるかをどうやって見分けることができますか?

ActiveSupport::Cache read_multiと呼ばれる便利なメソッドがあります

# When only lawyer_1 is cached

cache_keys = [:lawyer_1, :lawyer_2, :lawyer_3]
Rails.cache.read_multi(cache_keys) # => {:lawyer_1 => {"id": 1, "name": "Bob the Lawyer"} }

read_multi {key: value}のハッシュを返します キャッシュで見つかったキーごとに。しかし、ではないすべてのキーをどのように見つけますか キャッシュに?簡単な方法で行うことができます。すべてのキャッシュキーを調べて、read_multiのハッシュに含まれていないキーを見つけます。 返品:

cache_keys = [:lawyer_1, :lawyer_2, :lawyer_3]
uncached_keys = []

cached_keys_with_values = Rails.cache.read_multi(cache_keys)

cache_keys.each do |key|
  uncached_keys << key unless cached_keys_with_values.has_key?(key)
end

それで、あなたは今何を持っていますか?

  • オブジェクトが必要なすべてのキャッシュキーの配列。
  • {key: value}のハッシュ キャッシュで見つけた各オブジェクトのペア。
  • キャッシュになかったキーのリスト。

そして次に何が必要ですか?

  • キャッシュになかったキーの場合。できれば一度に取得します。

それがあなたの次のステップです。

キャッシュされていない値をプリロードします

間もなく、キャッシュキーを使用してオブジェクトを見つける必要があります。作業を簡単にするために、コードを次のように変更できます。

cache_identifiers = {lawyer_1: 1, lawyer_2: 2, lawyer_3: 3}
cache_keys = cache_identifiers.keys
uncached_keys = []

cached_keys_with_values = Rails.cache.read_multi(cache_keys)

cache_keys.each do |key|
  uncached_keys << key unless cached_keys_with_values.has_key?(key)
end

つまり、cache_identifiers キャッシュキーを追跡するようになりましたおよび フェッチするオブジェクトID。

次に、キャッシュされていないキーを使用します:

uncached_keys # => [:lawyer_2, :lawyer_3]

そして、あなたのcache_identifiers ハッシュ:

cache_identifiers # => {lawyer_1: 1, lawyer_2: 2, lawyer_3: 3}

これらすべてのオブジェクトを一度にフェッチ、プリロード、およびシリアル化できます。

uncached_ids = uncached_keys.map { |key| cache_identifiers[key] }
uncached_lawyers = Lawyer.where(id: uncached_ids)
                         .includes([:address, :practice_areas, :awards, ...])
                         .map(&:as_json))

それで、あなたは今何を持っていますか?

  • オブジェクトの最初に必要なすべてのキャッシュキーの配列。
  • {key: value}のハッシュ キャッシュで見つかった各オブジェクトのペア。
  • キャッシュになかったキーのリスト。
  • キャッシュで見つからなかったすべての値。

そして次に何が必要ですか?

  • フェッチしたばかりのすべての値をキャッシュするため、次回このプロセス全体を実行する必要はありません。
  • キャッシュからのものかどうかに関係なく、すべてのオブジェクトの最終リスト。
キャッシュされていない値をキャッシュする

2つのリストがあります。1つはキャッシュされていないキーのリストで、もう1つはキャッシュされていない値のリストです。 ただし、それらをキャッシュするには、1つあれば簡単です。 [key, value]のリスト ペアになるので、value keyのすぐ隣にあります 。これは、私のお気に入りの方法の1つであるzipを使用する言い訳です。 :

[1, 2, 3].zip(["a", "b", "c"]) # => [[1, "a"], [2, "b"], [3, "c"]]

zipを使用 、フェッチした値を簡単にキャッシュできます:

uncached_keys.zip(uncached_lawyers).each do |key, value|
  Rails.cache.write(key, value)
end

あなたは今何を持っていますか?

  • オブジェクトの最初に必要なすべてのキャッシュキーの配列。
  • {key: value}のハッシュ キャッシュで見つかった各オブジェクトのペア。
  • キャッシュしたばかりの以前はキャッシュされていなかった値のリスト。

そして、あなたはまだ何が必要ですか?

  • キャッシュからのものかどうかに関係なく、すべてのオブジェクトの1つの大きなリスト。
すべてをまとめる

これで、キャッシュキーの順序付きリストができました:

cache_keys = cache_identifiers.keys

キャッシュからフェッチしたオブジェクトのリスト:

cached_keys_with_values = Rails.cache.read_multi(cache_keys)

そして、データベースから取得したばかりのオブジェクトのリスト:

uncached_ids = uncached_keys.map { |key| cache_identifiers[key] }
uncached_lawyers = Lawyer.where(id: uncached_ids)
                         .includes([:address, :practice_areas, :awards, ...])
                         .map(&:as_json))

これで、すべてをまとめるのに最後のループが1つ必要になります:

results = []
cache_keys.each do |key|
  results << cache_keys_with_values[key] || uncached_lawyers.shift
end

つまり、キャッシュキーごとに、そのキーのキャッシュで見つけたオブジェクトを取得します。そのキーが元々キャッシュになかった場合は、データベースからプルした次のオブジェクトを取得します。

その後、完了です!

全体は次のようになります。

cache_identifiers = {lawyer_1: 1, lawyer_2: 2, lawyer_3: 3}
cache_keys = cache_identifiers.keys
uncached_keys = []

# Fetch the cached values from the cache
cached_keys_with_values = Rails.cache.read_multi(cache_keys)

# Create the list of keys that weren't in the cache
cache_keys.each do |key|
  uncached_keys << key unless cached_keys_with_values.has_key?(key)
end

# Fetch all the uncached values, in bulk
uncached_ids = uncached_keys.map { |key| cache_identifiers[key] }
uncached_lawyers = Lawyer.where(id: uncached_ids)
                         .includes([:address, :practice_areas, :awards, ...])
                         .map(&:as_json))

# Write the uncached values back to the cache
uncached_keys.zip(uncached_lawyers).each do |key, value|
  Rails.cache.write(key, value)
end

# Create our final result set from the cached and uncached values
results = []
cache_keys.each do |key|
  results << cache_keys_with_values[key] || uncached_lawyers.shift
end
results

それは価値がありました?多分。たくさんのコードです。 ただし、関連付けが多いオブジェクトをキャッシュしている場合は、数十または数百のSQL呼び出しを節約できます。 これにより、API応答の時間を大幅に短縮できます。

Avvoでは、このパターンは非常に便利です。多くのJSON APIは、このパターンを使用して、キャッシュされた応答を非常に迅速に返します。

このパターンは非常に便利だったので、bulk_cache_fetcherと呼ばれるカプセル化するgemを作成しました。 したがって、大きくて複雑なデータモデルをキャッシュしようとしていることに気付いた場合は、試してみてください。


  1. Windows 10 の Chrome で Cookie とキャッシュを最速で消去する方法

    Web サイトにアクセスすると、サイトの設定やプロファイル情報などの閲覧情報を保存するための Cookie が作成され、Web ページ、画像、CSS、オーディオ、ビデオ、その他のダウンロードされたコンテンツを保存するブラウザーのキャッシュにより、ページの読み込みが速くなります。より簡単に Web を閲覧できるようにします。ただし、すべての Cookie が適切であるとは限りません。このデータをハード ドライブに保存すると、プライバシーが漏洩し、ディスク容量が不足してコンピューターの速度が低下する可能性があります。 したがって、ブラウザの Cookie とキャッシュを定期的にクリアすることが重

  2. ステガノグラフィ:マルウェアを広める新しい方法

    ゼロデイ脅威、一般的なエクスプロイト、致命的な COVID-19 ウイルスと戦う準備が整っている間.ハッカーは、マシンにマルウェアを渡すための新しい手法を開発しています。 1499 年に導入された古代から存在する概念が新しい武器です。 「ステガノグラフィー」と呼ばれるこの新しい手法は、データを非表示の形式で送信して読み取ることができないようにするために使用されます。隠された、隠されているという意味のギリシャ語 (ステガノス) と、書くことを意味する「グラフィティ」の組み合わせは、危険な新しいトレンドになりつつあります。 今日のこの投稿では、この新しいフロンティアと、そこから保護され続ける方法