1

I'm working on a app which ties to a legacy database. The primary model is based on a stupidly large 100+ column table. I don't know too much about the inner-workings of ActiveRecord but it seems to me that any request on this model is slowing down because it's creating objects with 100+ attributes. Let's call this SlowModel.

Rendering pages with this model sometimes take 17 seconds on my dev computer. Straight up mysql queries only take ~ 0.5 - 1 second.

I've managed to speed up one portion of the app by using a MySQL view that selects a subset of fields (20 or so). We'll call this QuickModel. Using views is OK but isn't the most portable solution.

I will likely continue to try and add this QuickModel into other parts of the site but I was wondering if anyone had other ideas in speeding up the original object. For instance, is there a way to specify in the model what columns activerecord should just ignore and avoid building? Maybe there are specific column types (:text??) that cause bloat in ActiveRecord objects.

Assume that columns have proper indices.

Tron
  • 693
  • 1
  • 6
  • 17

3 Answers3

6

You can specify which columns are returned in the model lookup using the :select option of the ActiveRecord lookup:

SlowModel.all(:select => 'id, col1, col2, col3')

...will load instances of SlowModel with only the specified columns populated.

Winfield
  • 18,985
  • 3
  • 52
  • 65
1

How about having a completely new QuickModel that sits to its own table... and a QuickModel has_one SlowModel?

You can use SQL to move the most-necessary data into the QuickModel table and only refer to the SlowModel using my_quick_model.slow_model when necessary.

Alternatively, you can add a "select" to the default scope (you can google "rails default scope" for more). By default it'll only fetch the reduced set - but you can ask for all attributes by passing :select => "*" if necessary.

Taryn East
  • 27,486
  • 9
  • 86
  • 108
  • You've hit on an interesting point. We actually have a scope set up to return less fields but for some reason it doesn't actually seem to work. We call this scope :stubbed it's odd because Model.select(...) does work but when using Model.stubbed it gets overwritten somehow by subsequent operations. That's probably just a bug somewhere in our code though. I'd like to run some tests and see if running a reduced select is faster. – Tron Aug 31 '11 at 15:49
  • It turns out that http://www.snowgiraffe.com/tech/327/when-to-select-and-include-your-rubies-and-rails/ is our problem with our :stubbed scope because we have a .includes in there as well. – Tron Aug 31 '11 at 16:03
  • The solution for this problem was to change .includes to .joins as shown in the second answer from http://stackoverflow.com/questions/4047833/rails-3-select-with-include – Tron Aug 31 '11 at 18:08
  • "How about having a completely new QuickModel that sits to its own table... and a QuickModel has_one SlowModel?" is also a really interesting solution, might try to implement something like that with the view we already have. – Tron Sep 02 '11 at 15:30
1

Along the lines of what Winfield is saying, you may want to take a look at using an attribute tracker like SlimScrooge. The tracker attempts to fetch only the data that you're using, which reduces overhead. It attempts to automatically do what Winfield is suggesting.

Example from the Readme:

# 1st request, sql is unchanged but columns accesses are recorded
Brochure Load SlimScrooged 1st time (27.1ms)   SELECT * FROM `brochures` WHERE (expires_at IS NULL)

# 2nd request, only fetch columns that were used the first time
Brochure Load SlimScrooged (4.5ms)   SELECT `brochures`.expires_at,`brochures`.operator_id,`brochures`.id FROM `brochures` WHERE (expires_at IS NULL)

# 2nd request, later in code we need another column which causes a reload of all remaining columns
Brochure Reload SlimScrooged (0.6ms) `brochures`.name,`brochures`.comment,`brochures`.image_height,`brochures`.id, `brochures`.tel,`brochures`.long_comment,`brochures`.image_name,`brochures`.image_width FROM `brochures` WHERE `brochures`.id IN ('5646','5476','4562','3456','4567','7355')

# 3rd request
Brochure Load SlimScrooged (4.5ms)   SELECT `brochures`.expires_at,`brochures`.operator_id,`brochures`.name, `brochures`.id FROM `brochures` WHERE (expires_at IS NULL)
jcnnghm
  • 7,426
  • 7
  • 32
  • 38