fix ETS cache resilience, skip heavy columns from queries
All checks were successful
deploy / deploy (push) Successful in 1m27s
All checks were successful
deploy / deploy (push) Successful in 1m27s
- settings cache: create ETS table in application.ex so it survives GenServer crashes (same pattern as redirects cache) - redirects: remove DB fallback on cache miss — cache is warmed on startup and kept in sync, so a miss means no redirect exists - product listing: exclude provider_data (up to 72KB JSON) and description from listing queries via listing_select/1 - logo/header: select only rendering fields, skip BLOB data column Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
2f3b7e7b21
commit
e041f5d8f0
@ -7,9 +7,10 @@ defmodule Berrypod.Application do
|
||||
|
||||
@impl true
|
||||
def start(_type, _args) do
|
||||
# Create ETS table here so the supervisor process owns it (lives forever).
|
||||
# The Task below only warms it with data from the DB.
|
||||
# Create ETS tables here so the supervisor process owns them (live forever).
|
||||
# The GenServers/Tasks below only warm them with data from the DB.
|
||||
Berrypod.Redirects.create_table()
|
||||
Berrypod.Settings.SettingsCache.create_table()
|
||||
Berrypod.ActivityLog.ObanTelemetryHandler.attach()
|
||||
|
||||
children = [
|
||||
|
||||
@ -135,7 +135,8 @@ defmodule Berrypod.Media do
|
||||
from i in ImageSchema,
|
||||
where: i.image_type == "logo",
|
||||
order_by: [desc: i.inserted_at],
|
||||
limit: 1
|
||||
limit: 1,
|
||||
select: struct(i, [:id, :image_type, :is_svg, :svg_content, :source_width])
|
||||
)
|
||||
|
||||
SettingsCache.put_cached(:logo, logo)
|
||||
@ -165,7 +166,8 @@ defmodule Berrypod.Media do
|
||||
from i in ImageSchema,
|
||||
where: i.image_type == "header",
|
||||
order_by: [desc: i.inserted_at],
|
||||
limit: 1
|
||||
limit: 1,
|
||||
select: struct(i, [:id, :image_type, :source_width])
|
||||
)
|
||||
|
||||
SettingsCache.put_cached(:header, header)
|
||||
|
||||
@ -146,6 +146,14 @@ defmodule Berrypod.Products do
|
||||
[images: {pi_query, image: image_preload_query()}]
|
||||
end
|
||||
|
||||
# Skip provider_data (up to 72KB JSON) and description from listing queries —
|
||||
# neither is used on product cards. Same idea as image_preload_query().
|
||||
@listing_fields Product.__schema__(:fields) -- [:provider_data, :description]
|
||||
|
||||
defp listing_select(query) do
|
||||
from(p in query, select: struct(p, ^@listing_fields))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets a single visible, active product by slug with full preloads (for detail page).
|
||||
"""
|
||||
@ -179,6 +187,7 @@ defmodule Berrypod.Products do
|
||||
|> apply_sort(opts[:sort])
|
||||
|> maybe_limit(opts[:limit])
|
||||
|> maybe_exclude(opts[:exclude])
|
||||
|> listing_select()
|
||||
|> Repo.all()
|
||||
|> Repo.preload(listing_preloads())
|
||||
end
|
||||
@ -195,6 +204,7 @@ defmodule Berrypod.Products do
|
||||
|> apply_visible_filters(opts)
|
||||
|> apply_sort(opts[:sort])
|
||||
|> maybe_exclude(opts[:exclude])
|
||||
|> listing_select()
|
||||
|> Berrypod.Pagination.paginate(page: opts[:page], per_page: opts[:per_page] || 24)
|
||||
|
||||
%{pagination | items: Repo.preload(pagination.items, listing_preloads())}
|
||||
|
||||
@ -67,16 +67,9 @@ defmodule Berrypod.Redirects do
|
||||
{:ok, %{to_path: to_path, status_code: status_code, id: id}}
|
||||
|
||||
[] ->
|
||||
case Repo.one(from r in Redirect, where: r.from_path == ^path) do
|
||||
nil ->
|
||||
# Cache is warmed on startup with all redirects and kept in sync on
|
||||
# create/update/delete, so a miss here means no redirect exists.
|
||||
:not_found
|
||||
|
||||
redirect ->
|
||||
put_cache(redirect.from_path, redirect.to_path, redirect.status_code, redirect.id)
|
||||
|
||||
{:ok,
|
||||
%{to_path: redirect.to_path, status_code: redirect.status_code, id: redirect.id}}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@ -21,6 +21,24 @@ defmodule Berrypod.Settings.SettingsCache do
|
||||
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates the ETS table. Called from application.ex so the supervisor
|
||||
process owns the table — it survives GenServer crashes.
|
||||
"""
|
||||
def create_table do
|
||||
if :ets.whereis(@table_name) == :undefined do
|
||||
:ets.new(@table_name, [
|
||||
:set,
|
||||
:public,
|
||||
:named_table,
|
||||
read_concurrency: true,
|
||||
write_concurrency: false
|
||||
])
|
||||
end
|
||||
|
||||
@table_name
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets a cached setting by key.
|
||||
|
||||
@ -131,14 +149,8 @@ defmodule Berrypod.Settings.SettingsCache do
|
||||
|
||||
@impl true
|
||||
def init(_opts) do
|
||||
:ets.new(@table_name, [
|
||||
:set,
|
||||
:public,
|
||||
:named_table,
|
||||
read_concurrency: true,
|
||||
write_concurrency: false
|
||||
])
|
||||
|
||||
# Table is created in application.ex so it survives GenServer restarts
|
||||
create_table()
|
||||
{:ok, %{}, {:continue, :warm}}
|
||||
end
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user