How to optimise and speed up .find and .build on large number of records?

207 Views Asked by At

I have a form that accepts a true/false value for every hour of the day for 3 weeks (total of 504 1 hour time slots).

I am trying to reduce the time it takes for this page to load (currently 3 seconds locally, but 15 seconds on heroku; more than twice the time of any other page on the site).

The code

The form looks like this:

<% 504.times do |i| %>
    
  <% availability = @physician.availabilities.find { |a| a.time_slot == @current_hour + (i + 1)*3600 } %>
  <% availability ||= @physician.availabilities.build(time_slot: all_one_hour_slots[i] ) %>

# Form etc

Small numbers of i would be fine, but because it happens 504 times, it's slow.

When I view the server logs, it has the appearance of an n+1 problem (which I think may be caused by @physician.availabilities.find). But I'm not sure if that can be successfully avoided whilst maintaining functionality.

Question

How can I retain functionality, but make the page load faster?

Other notes

  • Every page on the site loads in under 7 seconds, so this page being 15 seconds is quite problematic.
  • The heroku dyno is standard, and the heroku postgres database is hobby-basic.
  • I am open to using whatever approach is best to reduce time. For example, if code optimisation is possible, that would be great. But if increasing the postgres database from hobby-basic to standard-0 would help, I'd consider that too.
  • A sample of the logs (this is 6 lines almost identical, but there are 504 total like this!):
  CACHE  (0.0ms)  SELECT "appointments"."start_time" FROM "appointments" WHERE "appointments"."physician_id" = $1  [["physician_id", 10]]
  ↳ app/views/physicians/_form.html.erb:37
  CACHE  (0.0ms)  SELECT "appointments"."start_time" FROM "appointments" WHERE "appointments"."physician_id" = $1  [["physician_id", 10]]
  ↳ app/views/physicians/_form.html.erb:37
  CACHE  (0.0ms)  SELECT "appointments"."start_time" FROM "appointments" WHERE "appointments"."physician_id" = $1  [["physician_id", 10]]
  ↳ app/views/physicians/_form.html.erb:37
  CACHE  (0.0ms)  SELECT "appointments"."start_time" FROM "appointments" WHERE "appointments"."physician_id" = $1  [["physician_id", 10]]
  ↳ app/views/physicians/_form.html.erb:37
  CACHE  (0.0ms)  SELECT "appointments"."start_time" FROM "appointments" WHERE "appointments"."physician_id" = $1  [["physician_id", 10]]
  ↳ app/views/physicians/_form.html.erb:37
  CACHE  (0.0ms)  SELECT "appointments"."start_time" FROM "appointments" WHERE "appointments"."physician_id" = $1  [["physician_id", 10]]
  ↳ app/views/physicians/_form.html.erb:37

Longer explanation

Here's the form in the view

<% 504.times do |i| %>

  <% availability = @physician.availabilities.find { |a| a.time_slot == @current_hour + (i + 1)*3600 } %>
  <% availability ||= @physician.availabilities.build(time_slot: all_one_hour_slots_from_one_hour_after_current_time[i] ) %>


  <% if @appointments.pluck(:start_time).any? { |e| e == availability.time_slot } %> 
    <b><%= availability.time_slot.to_s + " Time slot booked" %></b>
  <% else %><%# Line 37 %>
    <%= form.fields_for :availabilities, availability do |availability_form| %>

      <%= availability_form.hidden_field :time_slot, value: availability.time_slot %>

      <%= availability_form.label :_destroy do %>
        <%= availability_form.check_box :_destroy, {checked: availability.persisted?}, '0', '1' %>
        <%= nice_datetime(availability.time_slot) %>
      <% end %>

    <% end %><%# end form.fields_for %>
  <% end %><%# end booked vs available %>

<% end %><%# end 504 loop %>

And here's the controller action

# physicians controller
@physician = current_user.physician
@appointments = Physician.find(current_user.physician.id).appointments
@current_hour = Time.current.beginning_of_hour

A quick explanation in words: basically the 504 loop is just looping through 504 1 hour time slots and either displaying " Time slot booked" if that time is already booked, or a checkbox if the time slot is not booked (so the physician can decide whether to make themselves available for that time slot).

Lastly, full logs here

0

There are 0 best solutions below