最初に最も明白なアプローチから始めましょう:
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のキャッシュされたレコードがすぐに返されます。1
2
3
Task.find(1)
tasks
1
# 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] }