What if we can increase page load the speed by not duplicating work in the server rendering process? Since this app is set up to broadcast changes to the front end clients, it’s possible to reuse that work when a request comes in for the page. Russian Doll Caching describes layers of caching. Start from the smallest element, and then wrap it in bigger and bigger layers of caching, punching through to invalidate the cache with model updates.
Implementing Russian Doll Caching inside the Hacker News Progressive Web App requires exploiting nested relationships in the data model. The Top Items list is cached, it’s made up of cached TopItem
records, which is made up of a cached Item
. Updating one TopItem
invalidates the list, but since all the other TopItem
s will be cached, it’s still fewer database calls. Updating one Item
invalidates the associated TopItem
, which invalidates the list. There are still fewer database calls over the aggregate of app usage, speeding up the overall page rendering.
If you’re also using TurboStreams, then the rendering to the cache is happening before any pages render, meaning there are already cached items the first time you need to load a page.
View Caching
Each Item
is cached by adding the cache
directive to the top of partial app/views/items/_item.html.erb
and a matching end
at the bottom:
<%= cache item do %>
<div class="border rounded-sm flex flex-col h-full w-full" id="<%= dom_id item %>">
<%= turbo_stream_from item %>
<header class="border flex justify-between p-1">
<span class="rounded-full py-1 px-3 bg-red-600 text-white"><%= item.score %></span>
<p class="py-1 px-3">
<% "job" if item.job? %>
<%= item.host unless item.host.nil? %>
</p>
</header>
<article class="border flex flex-col flex-grow">
<p class="py-1 px-3 flex-grow text-2xl">
<% if item.url.nil? %>
<%= link_to item.title, item_path(item.hn_id) %>
<% else %>
<%= link_to item.title, item.url, { target: '_blank', rel: 'noopener' } %>
<% end %>
</p>
<p class="py-1 px-3">
<em><%= item.time.strftime('%c') %></em>
</p>
</article>
<% unless item.job? %>
<footer class="border flex justify-between">
<p class="py-1 px-3">
<%= link_to pluralize(item.descendants, 'comment'), item_path(item.hn_id) %>
</p>
<p class="py-1 px-3">
<a href="/user/<%= item.by %>"><%= item.by %></a>
</p>
</footer>
<% end %>
</div>
<% end %>
Then, the TopItem
partial, app/views/top_items/_top_item.html.erb
is cached in a similar way:
<%= cache top_item do %>
<div class="h-full flex-grow" id="<%= dom_id top_item %>">
<%= turbo_stream_from top_item %>
<%= render top_item.item %>
</div>
<% end %>
In app/views/tops/show.html.erb
, add the caching based on the most recent TopItem
and the current page:
<div class=" w-3/4 mx-auto p-4">
<header class="bg-red-600 py-1 px-3 text-white flex">
<p class=" text-2xl flex-grow">Top Stories</p>
<%= link_to "Refresh", top_path, method: :create, class: "pt-1" %>
</header>
<%= render partial: 'layouts/page_navigation', locals: { page: @page, total_pages: @total_pages, path: top_path } %>
<%= cache ['top-list', @top_item, @page] do %>
<div class="grid grid-cols-3 gap-4">
<%= render @stories %>
</div>
<% end %>
</div>
@top_item
comes from app/controllers/tops_controller.rb
, where the most recent TopItem
is loaded from the database.
def show
@page = params[:page] ? params[:page].to_i : FIRST_PAGE
@total_pages = TopItem.count / ITEMS_PER_PAGE
@stories = TopItem.order(:location)
.limit(ITEMS_PER_PAGE)
.offset(@page * ITEMS_PER_PAGE)
.includes(:item)
@top_item = TopItem.order(:updated_at).last
end
Breaking through the cache
Since the Item
can change without the TopItem
changing, it’s important that the cache is busted so the page is shown correctly. Item
will now have a callback to update the associated TopItem
.
class Item < ApplicationRecord
has_one :top_item
after_save :update_associates
broadcasts
enum hn_type: [:story, :job]
def populate(json)
if json.nil?
return
end
self.hn_id = json['id'] if json['id']
self.hn_type = json['type'] if json['type']
self.by = json['by'] if json['by']
self.time = DateTime.strptime("#{json['time']}",'%s') if json['time']
self.text = json['text'] if json['text']
self.parent = json['parent'] if json['parent']
if json['url']
self.url = json['url']
host = URI.parse( json['url'] ).host
self.host = host.gsub("www.", "") unless host.nil?
end
self.score = json['score'] if json['score']
self.descendants = json['descendants'] if json['descendants']
self.title = json['title'] if json['title']
end
def update_associates
top_item.touch if top_item.present?
end
end
The Result
If you get rid of includes(:item)
optimization in top_items#show
and clear the cache, this is a log of the rendering. It’s 152 milliseconds, but when the page is refreshed, it’s only 16 milliseconds. That’s about a 10x difference. The rendering that happens in the background because of TurboStreams is also going to cut down on processing, since each Item
and TopItem
is cached.
Processing by TopsController#show as TURBO_STREAM
Parameters: {"page"=>"2"}
(1.0ms) SELECT COUNT(*) FROM "top_items"
↳ app/controllers/tops_controller.rb:8:in `show'
TopItem Load (1.1ms) SELECT "top_items".* FROM "top_items" ORDER BY "top_items"."updated_at" DESC LIMIT $1 [["LIMIT", 1]]
↳ app/controllers/tops_controller.rb:12:in `show'
Rendering layout layouts/application.html.erb
Rendering tops/show.html.erb within layouts/application
Rendered layouts/_page_navigation.html.erb (Duration: 0.1ms | Allocations: 74)
Read fragment views/tops/show:22c98a01c47411a5d6288e4df0b392c2/top-list/top_items/502-20201229191446314957/2 (0.2ms)
TopItem Load (1.3ms) SELECT "top_items".* FROM "top_items" ORDER BY "top_items"."location" ASC LIMIT $1 OFFSET $2 [["LIMIT", 30], ["OFFSET", 60]]
↳ app/views/tops/show.html.erb:9
Read fragment views/top_items/_top_item:668ab0ca71e68ba60f0c5a8c6792672d/top_items/63-20201229191122794264 (0.2ms)
Item Load (1.7ms) SELECT "items".* FROM "items" WHERE "items"."id" = $1 LIMIT $2 [["id", 980], ["LIMIT", 1]]
↳ app/views/top_items/_top_item.html.erb:4
Read fragment views/items/_item:85e5b8da7bdc64d700153bd8f212848d/items/980-20201229191122780225 (0.2ms)
Write fragment views/items/_item:85e5b8da7bdc64d700153bd8f212848d/items/980-20201229191122780225 (0.1ms)
Rendered items/_item.html.erb (Duration: 1.2ms | Allocations: 353) [cache miss]
Write fragment views/top_items/_top_item:668ab0ca71e68ba60f0c5a8c6792672d/top_items/63-20201229191122794264 (0.1ms)
Read fragment views/top_items/_top_item:668ab0ca71e68ba60f0c5a8c6792672d/top_items/64-20201229191123154327 (0.1ms)
Item Load (1.5ms) SELECT "items".* FROM "items" WHERE "items"."id" = $1 LIMIT $2 [["id", 721], ["LIMIT", 1]]
↳ app/views/top_items/_top_item.html.erb:4
Read fragment views/items/_item:85e5b8da7bdc64d700153bd8f212848d/items/721-20201229191123141063 (0.1ms)
Write fragment views/items/_item:85e5b8da7bdc64d700153bd8f212848d/items/721-20201229191123141063 (0.2ms)
Rendered items/_item.html.erb (Duration: 1.2ms | Allocations: 351) [cache miss]
Write fragment views/top_items/_top_item:668ab0ca71e68ba60f0c5a8c6792672d/top_items/64-20201229191123154327 (0.1ms)
Read fragment views/top_items/_top_item:668ab0ca71e68ba60f0c5a8c6792672d/top_items/65-20201229191123588280 (0.1ms)
Item Load (1.1ms) SELECT "items".* FROM "items" WHERE "items"."id" = $1 LIMIT $2 [["id", 725], ["LIMIT", 1]]
↳ app/views/top_items/_top_item.html.erb:4
Read fragment views/items/_item:85e5b8da7bdc64d700153bd8f212848d/items/725-20201229191123577284 (0.1ms)
Write fragment views/items/_item:85e5b8da7bdc64d700153bd8f212848d/items/725-20201229191123577284 (0.1ms)
Rendered items/_item.html.erb (Duration: 0.7ms | Allocations: 351) [cache miss]
Write fragment views/top_items/_top_item:668ab0ca71e68ba60f0c5a8c6792672d/top_items/65-20201229191123588280 (0.1ms)
Read fragment views/top_items/_top_item:668ab0ca71e68ba60f0c5a8c6792672d/top_items/66-20201229191123984823 (0.1ms)
Item Load (1.0ms) SELECT "items".* FROM "items" WHERE "items"."id" = $1 LIMIT $2 [["id", 724], ["LIMIT", 1]]
↳ app/views/top_items/_top_item.html.erb:4
Read fragment views/items/_item:85e5b8da7bdc64d700153bd8f212848d/items/724-20201229191123973217 (0.1ms)
Write fragment views/items/_item:85e5b8da7bdc64d700153bd8f212848d/items/724-20201229191123973217 (0.1ms)
Rendered items/_item.html.erb (Duration: 0.9ms | Allocations: 351) [cache miss]
Write fragment views/top_items/_top_item:668ab0ca71e68ba60f0c5a8c6792672d/top_items/66-20201229191123984823 (0.1ms)
Read fragment views/top_items/_top_item:668ab0ca71e68ba60f0c5a8c6792672d/top_items/67-20201229191124338618 (0.1ms)
Item Load (1.0ms) SELECT "items".* FROM "items" WHERE "items"."id" = $1 LIMIT $2 [["id", 723], ["LIMIT", 1]]
↳ app/views/top_items/_top_item.html.erb:4
Read fragment views/items/_item:85e5b8da7bdc64d700153bd8f212848d/items/723-20201229182850194829 (0.2ms)
Write fragment views/items/_item:85e5b8da7bdc64d700153bd8f212848d/items/723-20201229182850194829 (0.1ms)
Rendered items/_item.html.erb (Duration: 1.3ms | Allocations: 351) [cache miss]
Write fragment views/top_items/_top_item:668ab0ca71e68ba60f0c5a8c6792672d/top_items/67-20201229191124338618 (0.1ms)
Read fragment views/top_items/_top_item:668ab0ca71e68ba60f0c5a8c6792672d/top_items/68-20201229191124704711 (0.1ms)
Item Load (1.5ms) SELECT "items".* FROM "items" WHERE "items"."id" = $1 LIMIT $2 [["id", 726], ["LIMIT", 1]]
↳ app/views/top_items/_top_item.html.erb:4
Read fragment views/items/_item:85e5b8da7bdc64d700153bd8f212848d/items/726-20201229191124694651 (0.1ms)
Write fragment views/items/_item:85e5b8da7bdc64d700153bd8f212848d/items/726-20201229191124694651 (0.1ms)
Rendered items/_item.html.erb (Duration: 0.9ms | Allocations: 351) [cache miss]
Write fragment views/top_items/_top_item:668ab0ca71e68ba60f0c5a8c6792672d/top_items/68-20201229191124704711 (0.2ms)
Read fragment views/top_items/_top_item:668ab0ca71e68ba60f0c5a8c6792672d/top_items/69-20201229191125153308 (0.1ms)
Item Load (1.1ms) SELECT "items".* FROM "items" WHERE "items"."id" = $1 LIMIT $2 [["id", 981], ["LIMIT", 1]]
↳ app/views/top_items/_top_item.html.erb:4
Read fragment views/items/_item:85e5b8da7bdc64d700153bd8f212848d/items/981-20201229191125138731 (0.1ms)
Write fragment views/items/_item:85e5b8da7bdc64d700153bd8f212848d/items/981-20201229191125138731 (0.1ms)
Rendered items/_item.html.erb (Duration: 0.9ms | Allocations: 352) [cache miss]
↳ app/views/top_items/_top_item.html.erb:4ab0ca71e68ba60f0c5a8c6792672d/top_items/69-20201229191125153308 (0.1ms)
Read fragment views/top_items/_top_item:668ab0ca71e68ba60f0c5a8c6792672d/top_items/70-20201229191125516443 (0.1ms)
Item Load (0.9ms) SELECT "items".* FROM "items" WHERE "items"."id" = $1 LIMIT $2 [["id", 982], ["LIMIT", 1]]
↳ app/views/top_items/_top_item.html.erb:4
Read fragment views/items/_item:85e5b8da7bdc64d700153bd8f212848d/items/982-20201229191125499167 (0.1ms)
Write fragment views/items/_item:85e5b8da7bdc64d700153bd8f212848d/items/982-20201229191125499167 (0.1ms)
Rendered items/_item.html.erb (Duration: 0.7ms | Allocations: 352) [cache miss]
Write fragment views/top_items/_top_item:668ab0ca71e68ba60f0c5a8c6792672d/top_items/70-20201229191125516443 (0.1ms)
Read fragment views/top_items/_top_item:668ab0ca71e68ba60f0c5a8c6792672d/top_items/71-20201229191125902277 (0.1ms)
Item Load (1.2ms) SELECT "items".* FROM "items" WHERE "items"."id" = $1 LIMIT $2 [["id", 969], ["LIMIT", 1]]
↳ app/views/top_items/_top_item.html.erb:4
Read fragment views/items/_item:85e5b8da7bdc64d700153bd8f212848d/items/969-20201229191125891173 (0.1ms)
Write fragment views/items/_item:85e5b8da7bdc64d700153bd8f212848d/items/969-20201229191125891173 (0.1ms)
Rendered items/_item.html.erb (Duration: 0.8ms | Allocations: 352) [cache miss]
Write fragment views/top_items/_top_item:668ab0ca71e68ba60f0c5a8c6792672d/top_items/71-20201229191125902277 (0.1ms)
Read fragment views/top_items/_top_item:668ab0ca71e68ba60f0c5a8c6792672d/top_items/72-20201229191126343039 (0.1ms)
Item Load (1.0ms) SELECT "items".* FROM "items" WHERE "items"."id" = $1 LIMIT $2 [["id", 731], ["LIMIT", 1]]
↳ app/views/top_items/_top_item.html.erb:4
Read fragment views/items/_item:85e5b8da7bdc64d700153bd8f212848d/items/731-20201229191126331992 (0.2ms)
Write fragment views/items/_item:85e5b8da7bdc64d700153bd8f212848d/items/731-20201229191126331992 (0.1ms)
Rendered items/_item.html.erb (Duration: 1.1ms | Allocations: 351) [cache miss]
Write fragment views/top_items/_top_item:668ab0ca71e68ba60f0c5a8c6792672d/top_items/72-20201229191126343039 (0.1ms)
Read fragment views/top_items/_top_item:668ab0ca71e68ba60f0c5a8c6792672d/top_items/73-20201229191126779284 (0.1ms)
Item Load (1.2ms) SELECT "items".* FROM "items" WHERE "items"."id" = $1 LIMIT $2 [["id", 729], ["LIMIT", 1]]
↳ app/views/top_items/_top_item.html.erb:4
Read fragment views/items/_item:85e5b8da7bdc64d700153bd8f212848d/items/729-20201229181339631341 (0.1ms)
Write fragment views/items/_item:85e5b8da7bdc64d700153bd8f212848d/items/729-20201229181339631341 (0.1ms)
Rendered items/_item.html.erb (Duration: 1.1ms | Allocations: 350) [cache miss]
Write fragment views/top_items/_top_item:668ab0ca71e68ba60f0c5a8c6792672d/top_items/73-20201229191126779284 (0.1ms)
Read fragment views/top_items/_top_item:668ab0ca71e68ba60f0c5a8c6792672d/top_items/74-20201229191127230847 (0.1ms)
Item Load (1.0ms) SELECT "items".* FROM "items" WHERE "items"."id" = $1 LIMIT $2 [["id", 505], ["LIMIT", 1]]
↳ app/views/top_items/_top_item.html.erb:4
Read fragment views/items/_item:85e5b8da7bdc64d700153bd8f212848d/items/505-20201229181341812678 (0.1ms)
Write fragment views/items/_item:85e5b8da7bdc64d700153bd8f212848d/items/505-20201229181341812678 (0.1ms)
Rendered items/_item.html.erb (Duration: 0.8ms | Allocations: 351) [cache miss]
Write fragment views/top_items/_top_item:668ab0ca71e68ba60f0c5a8c6792672d/top_items/74-20201229191127230847 (0.1ms)
Read fragment views/top_items/_top_item:668ab0ca71e68ba60f0c5a8c6792672d/top_items/75-20201229191127585826 (0.1ms)
Item Load (1.0ms) SELECT "items".* FROM "items" WHERE "items"."id" = $1 LIMIT $2 [["id", 745], ["LIMIT", 1]]
↳ app/views/top_items/_top_item.html.erb:4
Read fragment views/items/_item:85e5b8da7bdc64d700153bd8f212848d/items/745-20201229191127570616 (0.1ms)
Write fragment views/items/_item:85e5b8da7bdc64d700153bd8f212848d/items/745-20201229191127570616 (0.1ms)
Rendered items/_item.html.erb (Duration: 1.0ms | Allocations: 352) [cache miss]
Write fragment views/top_items/_top_item:668ab0ca71e68ba60f0c5a8c6792672d/top_items/75-20201229191127585826 (0.1ms)
Read fragment views/top_items/_top_item:668ab0ca71e68ba60f0c5a8c6792672d/top_items/76-20201229191127984311 (0.1ms)
Item Load (0.9ms) SELECT "items".* FROM "items" WHERE "items"."id" = $1 LIMIT $2 [["id", 970], ["LIMIT", 1]]
Read fragment views/items/_item:85e5b8da7bdc64d700153bd8f212848d/items/970-20201229191127972006 (0.1ms)
Write fragment views/items/_item:85e5b8da7bdc64d700153bd8f212848d/items/970-20201229191127972006 (0.1ms)
Rendered items/_item.html.erb (Duration: 1.1ms | Allocations: 350) [cache miss]
Write fragment views/top_items/_top_item:668ab0ca71e68ba60f0c5a8c6792672d/top_items/76-20201229191127984311 (0.1ms)
Read fragment views/top_items/_top_item:668ab0ca71e68ba60f0c5a8c6792672d/top_items/77-20201229191128461470 (0.1ms)
Item Load (1.0ms) SELECT "items".* FROM "items" WHERE "items"."id" = $1 LIMIT $2 [["id", 733], ["LIMIT", 1]]
↳ app/views/top_items/_top_item.html.erb:4
Read fragment views/items/_item:85e5b8da7bdc64d700153bd8f212848d/items/733-20201229181341203527 (0.1ms)
Write fragment views/items/_item:85e5b8da7bdc64d700153bd8f212848d/items/733-20201229181341203527 (0.1ms)
Rendered items/_item.html.erb (Duration: 0.9ms | Allocations: 351) [cache miss]
Write fragment views/top_items/_top_item:668ab0ca71e68ba60f0c5a8c6792672d/top_items/77-20201229191128461470 (0.1ms)
Read fragment views/top_items/_top_item:668ab0ca71e68ba60f0c5a8c6792672d/top_items/78-20201229191128861481 (0.1ms)
Item Load (1.1ms) SELECT "items".* FROM "items" WHERE "items"."id" = $1 LIMIT $2 [["id", 732], ["LIMIT", 1]]
↳ app/views/top_items/_top_item.html.erb:4
Read fragment views/items/_item:85e5b8da7bdc64d700153bd8f212848d/items/732-20201229181340874908 (0.1ms)
Write fragment views/items/_item:85e5b8da7bdc64d700153bd8f212848d/items/732-20201229181340874908 (0.1ms)
Rendered items/_item.html.erb (Duration: 1.0ms | Allocations: 350) [cache miss]
Write fragment views/top_items/_top_item:668ab0ca71e68ba60f0c5a8c6792672d/top_items/78-20201229191128861481 (0.1ms)
Read fragment views/top_items/_top_item:668ab0ca71e68ba60f0c5a8c6792672d/top_items/79-20201229191129304039 (0.1ms)
Item Load (1.2ms) SELECT "items".* FROM "items" WHERE "items"."id" = $1 LIMIT $2 [["id", 737], ["LIMIT", 1]]
↳ app/views/top_items/_top_item.html.erb:4
Read fragment views/items/_item:85e5b8da7bdc64d700153bd8f212848d/items/737-20201229191129290517 (0.2ms)
Write fragment views/items/_item:85e5b8da7bdc64d700153bd8f212848d/items/737-20201229191129290517 (0.1ms)
Rendered items/_item.html.erb (Duration: 1.7ms | Allocations: 351) [cache miss]
Write fragment views/top_items/_top_item:668ab0ca71e68ba60f0c5a8c6792672d/top_items/79-20201229191129304039 (0.1ms)
Read fragment views/top_items/_top_item:668ab0ca71e68ba60f0c5a8c6792672d/top_items/80-20201229191129746965 (0.1ms)
Item Load (1.2ms) SELECT "items".* FROM "items" WHERE "items"."id" = $1 LIMIT $2 [["id", 736], ["LIMIT", 1]]
↳ app/views/top_items/_top_item.html.erb:4
Read fragment views/items/_item:85e5b8da7bdc64d700153bd8f212848d/items/736-20201229183133401103 (0.1ms)
Write fragment views/items/_item:85e5b8da7bdc64d700153bd8f212848d/items/736-20201229183133401103 (0.1ms)
Rendered items/_item.html.erb (Duration: 0.8ms | Allocations: 351) [cache miss]
Write fragment views/top_items/_top_item:668ab0ca71e68ba60f0c5a8c6792672d/top_items/80-20201229191129746965 (0.1ms)
Read fragment views/top_items/_top_item:668ab0ca71e68ba60f0c5a8c6792672d/top_items/81-20201229191130181131 (0.1ms)
Item Load (1.0ms) SELECT "items".* FROM "items" WHERE "items"."id" = $1 LIMIT $2 [["id", 738], ["LIMIT", 1]]
↳ app/views/top_items/_top_item.html.erb:4
Read fragment views/items/_item:85e5b8da7bdc64d700153bd8f212848d/items/738-20201229182856474404 (0.1ms)
Write fragment views/items/_item:85e5b8da7bdc64d700153bd8f212848d/items/738-20201229182856474404 (0.1ms)
Rendered items/_item.html.erb (Duration: 1.0ms | Allocations: 351) [cache miss]
Write fragment views/top_items/_top_item:668ab0ca71e68ba60f0c5a8c6792672d/top_items/81-20201229191130181131 (0.1ms)
Read fragment views/top_items/_top_item:668ab0ca71e68ba60f0c5a8c6792672d/top_items/82-20201229191130541701 (0.1ms)
Item Load (1.1ms) SELECT "items".* FROM "items" WHERE "items"."id" = $1 LIMIT $2 [["id", 739], ["LIMIT", 1]]
↳ app/views/top_items/_top_item.html.erb:4
Read fragment views/items/_item:85e5b8da7bdc64d700153bd8f212848d/items/739-20201229191130530560 (0.2ms)
Write fragment views/items/_item:85e5b8da7bdc64d700153bd8f212848d/items/739-20201229191130530560 (0.1ms)
Rendered items/_item.html.erb (Duration: 1.1ms | Allocations: 352) [cache miss]
Write fragment views/top_items/_top_item:668ab0ca71e68ba60f0c5a8c6792672d/top_items/82-20201229191130541701 (0.1ms)
Read fragment views/top_items/_top_item:668ab0ca71e68ba60f0c5a8c6792672d/top_items/83-20201229191130939531 (0.1ms)
Item Load (1.1ms) SELECT "items".* FROM "items" WHERE "items"."id" = $1 LIMIT $2 [["id", 740], ["LIMIT", 1]]
↳ app/views/top_items/_top_item.html.erb:4
Read fragment views/items/_item:85e5b8da7bdc64d700153bd8f212848d/items/740-20201229181344557695 (0.1ms)
Write fragment views/items/_item:85e5b8da7bdc64d700153bd8f212848d/items/740-20201229181344557695 (0.1ms)
Rendered items/_item.html.erb (Duration: 0.9ms | Allocations: 351) [cache miss]
Write fragment views/top_items/_top_item:668ab0ca71e68ba60f0c5a8c6792672d/top_items/83-20201229191130939531 (0.1ms)
Read fragment views/top_items/_top_item:668ab0ca71e68ba60f0c5a8c6792672d/top_items/84-20201229191131339796 (0.1ms)
Item Load (1.0ms) SELECT "items".* FROM "items" WHERE "items"."id" = $1 LIMIT $2 [["id", 741], ["LIMIT", 1]]
↳ app/views/top_items/_top_item.html.erb:4
Read fragment views/items/_item:85e5b8da7bdc64d700153bd8f212848d/items/741-20201229181346433167 (0.1ms)
Write fragment views/items/_item:85e5b8da7bdc64d700153bd8f212848d/items/741-20201229181346433167 (0.1ms)
Rendered items/_item.html.erb (Duration: 0.7ms | Allocations: 351) [cache miss]
Unsubscribing from channel: {"channel":"Turbo::StreamsChannel","signed_stream_name":"IloybGtPaTh2YUdodWNIZGhMMVJ2Y0VsMFpXMHZNek0i--1ed0dfda6af953cac3909192c617768195ceb2f09482619cd656c1b54ac46193"}
Read fragment views/top_items/_top_item:668ab0ca71e68ba60f0c5a8c6792672d/top_items/85-20201229191131702623 (0.1ms)
Item Load (0.9ms) SELECT "items".* FROM "items" WHERE "items"."id" = $1 LIMIT $2 [["id", 730], ["LIMIT", 1]]
↳ app/views/top_items/_top_item.html.erb:4
Read fragment views/items/_item:85e5b8da7bdc64d700153bd8f212848d/items/730-20201229191131690621 (0.1ms)
Write fragment views/items/_item:85e5b8da7bdc64d700153bd8f212848d/items/730-20201229191131690621 (0.2ms)
Rendered items/_item.html.erb (Duration: 0.9ms | Allocations: 351) [cache miss]
Write fragment views/top_items/_top_item:668ab0ca71e68ba60f0c5a8c6792672d/top_items/85-20201229191131702623 (0.1ms)
Read fragment views/top_items/_top_item:668ab0ca71e68ba60f0c5a8c6792672d/top_items/86-20201229191132062586 (0.1ms)
Item Load (1.2ms) SELECT "items".* FROM "items" WHERE "items"."id" = $1 LIMIT $2 [["id", 734], ["LIMIT", 1]]
↳ app/views/top_items/_top_item.html.erb:4
Read fragment views/items/_item:85e5b8da7bdc64d700153bd8f212848d/items/734-20201229191132050650 (0.1ms)
Write fragment views/items/_item:85e5b8da7bdc64d700153bd8f212848d/items/734-20201229191132050650 (0.1ms)
Rendered items/_item.html.erb (Duration: 1.0ms | Allocations: 352) [cache miss]
Write fragment views/top_items/_top_item:668ab0ca71e68ba60f0c5a8c6792672d/top_items/86-20201229191132062586 (0.1ms)
Read fragment views/top_items/_top_item:668ab0ca71e68ba60f0c5a8c6792672d/top_items/87-20201229191132424954 (0.1ms)
Item Load (1.0ms) SELECT "items".* FROM "items" WHERE "items"."id" = $1 LIMIT $2 [["id", 742], ["LIMIT", 1]]
↳ app/views/top_items/_top_item.html.erb:4
Read fragment views/items/_item:85e5b8da7bdc64d700153bd8f212848d/items/742-20201229191132412114 (0.2ms)
Write fragment views/items/_item:85e5b8da7bdc64d700153bd8f212848d/items/742-20201229191132412114 (0.1ms)
Rendered items/_item.html.erb (Duration: 1.1ms | Allocations: 352) [cache miss]
Write fragment views/top_items/_top_item:668ab0ca71e68ba60f0c5a8c6792672d/top_items/87-20201229191132424954 (0.1ms)
Read fragment views/top_items/_top_item:668ab0ca71e68ba60f0c5a8c6792672d/top_items/88-20201229191132790004 (0.1ms)
Item Load (1.2ms) SELECT "items".* FROM "items" WHERE "items"."id" = $1 LIMIT $2 [["id", 728], ["LIMIT", 1]]
↳ app/views/top_items/_top_item.html.erb:4
Read fragment views/items/_item:85e5b8da7bdc64d700153bd8f212848d/items/728-20201229181339168369 (0.2ms)
Write fragment views/items/_item:85e5b8da7bdc64d700153bd8f212848d/items/728-20201229181339168369 (0.1ms)
Rendered items/_item.html.erb (Duration: 1.0ms | Allocations: 352) [cache miss]
Write fragment views/top_items/_top_item:668ab0ca71e68ba60f0c5a8c6792672d/top_items/88-20201229191132790004 (0.1ms)
Read fragment views/top_items/_top_item:668ab0ca71e68ba60f0c5a8c6792672d/top_items/89-20201229191133147506 (0.1ms)
Item Load (1.1ms) SELECT "items".* FROM "items" WHERE "items"."id" = $1 LIMIT $2 [["id", 743], ["LIMIT", 1]]
↳ app/views/top_items/_top_item.html.erb:4
Read fragment views/items/_item:85e5b8da7bdc64d700153bd8f212848d/items/743-20201229191133135191 (0.1ms)
Write fragment views/items/_item:85e5b8da7bdc64d700153bd8f212848d/items/743-20201229191133135191 (0.1ms)
Rendered items/_item.html.erb (Duration: 0.9ms | Allocations: 351) [cache miss]
Write fragment views/top_items/_top_item:668ab0ca71e68ba60f0c5a8c6792672d/top_items/89-20201229191133147506 (0.1ms)
Read fragment views/top_items/_top_item:668ab0ca71e68ba60f0c5a8c6792672d/top_items/90-20201229191133542295 (0.1ms)
Item Load (1.0ms) SELECT "items".* FROM "items" WHERE "items"."id" = $1 LIMIT $2 [["id", 727], ["LIMIT", 1]]
↳ app/views/top_items/_top_item.html.erb:4
Read fragment views/items/_item:85e5b8da7bdc64d700153bd8f212848d/items/727-20201229182853324197 (0.1ms)
Write fragment views/items/_item:85e5b8da7bdc64d700153bd8f212848d/items/727-20201229182853324197 (0.1ms)
Rendered items/_item.html.erb (Duration: 1.0ms | Allocations: 351) [cache miss]
Write fragment views/top_items/_top_item:668ab0ca71e68ba60f0c5a8c6792672d/top_items/90-20201229191133542295 (0.1ms)
Read fragment views/top_items/_top_item:668ab0ca71e68ba60f0c5a8c6792672d/top_items/91-20201229191133984661 (0.1ms)
Item Load (1.0ms) SELECT "items".* FROM "items" WHERE "items"."id" = $1 LIMIT $2 [["id", 715], ["LIMIT", 1]]
↳ app/views/top_items/_top_item.html.erb:4
Read fragment views/items/_item:85e5b8da7bdc64d700153bd8f212848d/items/715-20201229191133972911 (0.1ms)
Write fragment views/items/_item:85e5b8da7bdc64d700153bd8f212848d/items/715-20201229191133972911 (0.1ms)
Rendered items/_item.html.erb (Duration: 0.9ms | Allocations: 351) [cache miss]
Write fragment views/top_items/_top_item:668ab0ca71e68ba60f0c5a8c6792672d/top_items/91-20201229191133984661 (0.1ms)
Read fragment views/top_items/_top_item:668ab0ca71e68ba60f0c5a8c6792672d/top_items/92-20201229191134341696 (0.1ms)
Item Load (1.2ms) SELECT "items".* FROM "items" WHERE "items"."id" = $1 LIMIT $2 [["id", 735], ["LIMIT", 1]]
↳ app/views/top_items/_top_item.html.erb:4
Read fragment views/items/_item:85e5b8da7bdc64d700153bd8f212848d/items/735-20201229191134330370 (0.2ms)
Write fragment views/items/_item:85e5b8da7bdc64d700153bd8f212848d/items/735-20201229191134330370 (0.1ms)
Rendered items/_item.html.erb (Duration: 1.4ms | Allocations: 351) [cache miss]
Write fragment views/top_items/_top_item:668ab0ca71e68ba60f0c5a8c6792672d/top_items/92-20201229191134341696 (0.1ms)
Rendered collection of top_items/_top_item.html.erb [30 times] (Duration: 131.5ms | Allocations: 46011)
Write fragment views/tops/show:22c98a01c47411a5d6288e4df0b392c2/top-list/top_items/502-20201229191446314957/2 (0.1ms)
Rendered tops/show.html.erb within layouts/application (Duration: 136.8ms | Allocations: 47454)
[Webpacker] Everything's up-to-date. Nothing to do
Rendered layout layouts/application.html.erb (Duration: 144.8ms | Allocations: 50988)
Completed 200 OK in 152ms (Views: 110.6ms | ActiveRecord: 36.8ms | Allocations: 52640)
Here is the cached result:
Started GET "/top?page=2" for 127.0.0.1 at 2020-12-29 14:19:47 -0500
Processing by TopsController#show as TURBO_STREAM
Parameters: {"page"=>"2"}
[1m[35m (1.2ms)[0m [1m[34mSELECT COUNT(*) FROM "top_items"[0m
↳ app/controllers/tops_controller.rb:8:in `show'
[1m[36mTopItem Load (1.1ms)[0m [1m[34mSELECT "top_items".* FROM "top_items" ORDER BY "top_items"."updated_at" DESC LIMIT $1[0m [["LIMIT", 1]]
↳ app/controllers/tops_controller.rb:12:in `show'
Rendering layout layouts/application.html.erb
Rendering tops/show.html.erb within layouts/application
Rendered layouts/_page_navigation.html.erb (Duration: 0.2ms | Allocations: 74)
Read fragment views/tops/show:22c98a01c47411a5d6288e4df0b392c2/top-list/top_items/502-20201229191446314957/2 (0.2ms)
Rendered tops/show.html.erb within layouts/application (Duration: 0.9ms | Allocations: 299)
[Webpacker] Everything's up-to-date. Nothing to do
Rendered layout layouts/application.html.erb (Duration: 8.0ms | Allocations: 3833)
Completed 200 OK in 16ms (Views: 8.5ms | ActiveRecord: 2.3ms | Allocations: 5483)
Subscribe for updates as this project takes shape. You can see all the code at Github: https://github.com/OnRailsBlog/hhnpwa
Other HOTWire Tutorials
- HOTWire HNPWA #1: Setting up for Top Stories
- Turbo Streaming Top Items – HOTWire HNPWA #2
- Paginating Top Items – HOTWire HNPWA #3
- Russian Doll Caching – Building HOTWire HNPWA #4
- Lazy Loading Lots of Comments HOTWire Tutorial #5
2 comments on “Russian Doll Caching – Building HOTWire HNPWA #4”
[…] HOTWire HNPWA Tutorial #4:Russian Doll Caching […]
[…] Russian Doll Caching – Building HOTWire HNPWA #4 […]