2

(以前:ActiveRecordでのリバースイーガーローディング

この奇妙な問題があり、積極的な読み込みを使用する必要があることがわかっていますが、これは非常に奇妙なユースケースであるため、うまく機能しません。

コード

class Task < ActiveRecord::Base
 belongs_to :project

class Project < ActiveRecord::Base
 has_many :tasks

問題

プロジェクトがあり、タスクをレンダリングする従来の設定では、タスクを順番に繰り返すのではなく、eager-loadingを使用してタスクを1回ロードすることを知っています。ただし、私の場合、タスクのリストがあり、タスクごとに適切なプロジェクトをフェッチする必要があります。順次レンダリングする場合、Rails SQLキャッシュは役立ちますが、タスクがたくさんあるため、同じプロジェクトを何度もロードすることになります。

この厄介な状況を回避するために私は何ができますか?

編集

私は状況を明らかにしようとしています。タスクIDの配列が複数あります。すなわち

type_a_tasks = [1,2,3,1,2,3]
type_b_tasks = [1,2,2,3,3]

同じタスクが存在する可能性があることに注意してください。ここで、関数型プログラミングのように、リストをマップして、IDの代わりに、実際のタスクとその関連付けを取得できるようにしたいのです。

type_a_tasks = [Task #1, Task #2, etc.]
type_b_tasks = [Task #1, Task #2, etc.]

私は私がちょうどによってタスクを得ることができることを知っています

Task.includes(:project).find(task_a_tasks.concat(task_b_tasks))

しかし、それを一連のタスクに減らして、コレクションの順序を失います。それはより明確ですか?

4

3 に答える 3

2

最初に最も明白なアプローチから始めましょう:

type_a_task_ids = [1,2,3,1,2,3]
type_b_task_ids = [1,2,2,3,3] 
type_a_tasks = type_a_task_ids.map { |task_id| Task.includes(:project).find(task_id) }
type_b_tasks = type_b_task_ids.map { |task_id| Task.includes(:project).find(task_id) }

上記は単純で読みやすいですが、潜在的に低速です。特定のタスクで、個別ごとに1つのデータベースラウンドトリップと、個別ごとに1つのデータベースラウンドトリップを実行task_id します。project_idすべてのレイテンシーが合計されるため、タスク(および対応するプロジェクト)をまとめてロードする必要があります。

Railsをバルクロード(プリフェッチ)して、同じレコードを2回のラウンドトリップ(1つはすべての個別のタスク用、もう1つはすべての個別の関連プロジェクト用)で事前にキャッシュしてから、正確なものを取得できれば素晴らしいと思います。上記と同じコード-findデータベースではなく常にキャッシュにヒットすることを除いて。

残念ながら、RailsではクエリキャッシュActiveRecord を使用するため、(デフォルトでは)そのようには機能しません。最初のクエリは2番目のクエリとは異なるため、()の後に()を実行しても、クエリキャッシュは活用さTask.find(1)SELECT * FROM tasks WHERE id=1ませTask.find([1,2,3])ん。SELECT * FROM tasks WHERE id IN (1,2,3)(ただし、Railsはまったく同じクエリが複数回実行され、キャッシュされた結果セットを返すためTask.find(1)、2回目、3回目などの時間を実行すると、クエリキャッシュが活用されます。)SELECT

IdentityMapキャッシュを入力します。Identity Map Cachingは、テーブルごとおよび主キーごとに、クエリではなくレコードをキャッシュするという意味で異なります。したがって、実行Task.find([1,2,3])すると、テーブルのIDマップキャッシュに3つのレコード(それぞれIDをtasks持つエントリ)が入力され、その後、実行すると、テーブルとIDのキャッシュされたレコードがすぐに返されます。123Task.find(1)tasks1

# with IdentityMap turned on (see IdentityMap documentation)
# prefetch all distinct tasks and their associated projects
# throw away the result, we only want to prep the cache
Task.includes(:project).find(type_a_task_ids & type_b_task_ids)
# proceed with regular logic
type_a_task_ids = [1,2,3,1,2,3]
type_b_task_ids = [1,2,2,3,3] 
type_a_tasks = type_a_task_ids.map { |task_id| Task.includes(:project).find(task_id) }
type_b_tasks = type_b_task_ids.map { |task_id| Task.includes(:project).find(task_id) }

ただし、IdentityMap (正当な理由で)デフォルトでアクティブになったことはなく最終的にRailsから削除されました

なしで同じ結果をどのように達成しますIdentityMapか?単純:

# prefetch all distinct tasks and their associated projects
# store the result in our own identity cache
my_tasks_identity_map = \
  Hash[Task.includes(:project).find(type_a_task_ids & type_b_task_ids).map { |task|
    [ task.id, task ]
  }]
# proceed with cache-centric logic
type_a_task_ids = [1,2,3,1,2,3]
type_b_task_ids = [1,2,2,3,3] 
type_a_tasks = type_a_task_ids.map { |task_id| my_tasks_identity_map[task_id] }
type_b_tasks = type_b_task_ids.map { |task_id| my_tasks_identity_map[task_id] }
于 2012-11-27T03:53:33.230 に答える
0

私はあなたの問題を見ていると思います。それは、すべてが同じプロジェクトに属するタスクがたくさんある場合、そのプロジェクトを複数回ロードすることになるということです。

Taskオブジェクトの配列がすでにあると仮定すると、これはどうでしょうか。

project_ids = @tasks.map{|task| task.project_id}.uniq
@projects = Project.find(project_ids)
于 2012-11-26T06:06:38.013 に答える
0

次のような行を介してRailsでIdentityMapを有効にした場合config/application.rb

config.active_record.identity_map = true

その場合、ActiveRecordは実際にはDBに戻って、Project以前にロードしたものをロードすることはありません。メモリ内の同じオブジェクトを参照するだけです。

于 2012-11-27T17:44:34.597 に答える