Search code examples
sqlruby-on-railsperformancerails-activestorage

Rails: Reducing the amount of ActiveStorage queries


I've got an index page that displays 3 different resources (Ablum, Samplepack and Demo), and 2 of them has attached images. I'm loading them through my controller like so:

# static_controller.rb   
  @resources = []
  @resources.push(Album.all, Demo.all, Samplepack.all)

It results in a lot of SQL queries in order to load them. I was wondering if there was a most efficient way to loads attachments ? If anyone could copy/paste me a link to some readings it'd be awesome ! I was thinking about something with includes or joins but I can't find any reference on the Web.

Have a great day

EDIT: As Sebastian Palma mentioned, with_attached_<attachment> is required (non exhaustive logs for the sake of brevity)

 [Album.all, Demo.all, Samplepack.all]

# resulting SQL

  # Album n°1
  ActiveStorage::Attachment Load (1.8ms)   SELECT  "active_storage_attachments".* FROM "active_storage_attachments" WHERE "active_storage_attachments"."record_id" = $1 AND "active_storage_attachments"."record_type" = $2 AND "active_storage_attachments"."name" = $3 LIMIT $4  [["record_id", 82], ["record_type", "Album"], ["name", "album_artwork"], ["LIMIT", 1]]
  ActiveStorage::Blob Load (2.4ms)         SELECT  "active_storage_blobs".*       FROM "active_storage_blobs"       WHERE "active_storage_blobs"."id" = $1 LIMIT $2  [["id", 1227], ["LIMIT", 1]]
  # Ablum n°2 
  ActiveStorage::Attachment Load (0.2ms)   SELECT  "active_storage_attachments".* FROM "active_storage_attachments" WHERE "active_storage_attachments"."record_id" = $1 AND "active_storage_attachments"."record_type" = $2 AND "active_storage_attachments"."name" = $3 LIMIT $4  [["record_id", 83], ["record_type", "Album"], ["name", "album_artwork"], ["LIMIT", 1]]
  ActiveStorage::Blob Load (0.1ms)         SELECT  "active_storage_blobs".*       FROM "active_storage_blobs"       WHERE "active_storage_blobs"."id" = $1 LIMIT $2  [["id", 1228], ["LIMIT", 1]]
  # Album n°3
  ActiveStorage::Attachment Load (0.2ms)   SELECT  "active_storage_attachments".* FROM "active_storage_attachments" WHERE "active_storage_attachments"."record_id" = $1 AND "active_storage_attachments"."record_type" = $2 AND "active_storage_attachments"."name" = $3 LIMIT $4  [["record_id", 84], ["record_type", "Album"], ["name", "album_artwork"], ["LIMIT", 1]]
  ActiveStorage::Blob Load (0.2ms)         SELECT  "active_storage_blobs".*       FROM "active_storage_blobs"       WHERE "active_storage_blobs"."id" = $1 LIMIT $2  [["id", 1229], ["LIMIT", 1]]
  ...
  ... N+1 town (even N+2 ?)

Using with_attached_<attachment>:

 @resources = [
   Album.with_attached_album_artwork.all,
   Demo.all,
   Samplepack.with_attached_album_artwork.all
 ]

  # Albums
  ActiveStorage::Attachment Load (0.6ms)  SELECT "active_storage_attachments".* FROM "active_storage_attachments" WHERE "active_storage_attachments"."record_type" = $1 AND "active_storage_attachments"."name" = $2 AND "active_storage_attachments"."record_id" IN ($3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)  [["record_type", "Album"], ["name", "album_artwork"], ["record_id", 82], ["record_id", 83], ["record_id", 84], ["record_id", 85], ["record_id", 86], ["record_id", 87], ["record_id", 88], ["record_id", 89], ["record_id", 91], ["record_id", 93], ["record_id", 94], ["record_id", 95]]
  ActiveStorage::Blob Load (0.4ms)        SELECT "active_storage_blobs".*       FROM "active_storage_blobs"       WHERE "active_storage_blobs"."id" IN ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)  [["id", 1239], ["id", 1242], ["id", 1244], ["id", 1227], ["id", 1228], ["id", 1229], ["id", 1230], ["id", 1231], ["id", 1232], ["id", 1236], ["id", 1237], ["id", 1241]]

  # Samplepacks
  ActiveStorage::Attachment Load (0.3ms)  SELECT "active_storage_attachments".* FROM "active_storage_attachments" WHERE "active_storage_attachments"."record_type" = $1 AND "active_storage_attachments"."name" = $2 AND "active_storage_attachments"."record_id" = $3  [["record_type", "Samplepack"], ["name", "album_artwork"], ["record_id", 56]]
  ActiveStorage::Blob Load (0.2ms)        SELECT "active_storage_blobs".*       FROM "active_storage_blobs"       WHERE "active_storage_blobs"."id" = $1  [["id", 1243]]


Solution

  • Depending in your case (has_many/has_one) use with_attached_<attachment>:

    To avoid N+1 queries, you can include the attached blobs in your query like so:

    Gallery.where(user: Current.user).with_attached_photos

    So:

    [
      Album.all,
      Demo.with_attached_images.all,
      Samplepack.with_attached_images.all
    ]
    

    I think there's no need to create an empty array to fill it then.

    There's perhaps also a problem since you're using all, check what you really need from each model.