persist mockup variants on fly volume across deploys
All checks were successful
deploy / deploy (push) Successful in 3m46s
All checks were successful
deploy / deploy (push) Successful in 3m46s
Source mockup WebPs are copied from the release to /data/mockups/ on startup, and variants are generated there. This eliminates the 182-job storm on every deploy that was saturating the CPU and causing SQLite locking. After the first successful run, subsequent deploys find all variants intact. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
8159a312ae
commit
5e70c07b60
@ -19,8 +19,9 @@ config :logger, level: :info
|
|||||||
# Structured JSON logs for production (machine-parseable by fly logs, journalctl, Loki, etc.)
|
# Structured JSON logs for production (machine-parseable by fly logs, journalctl, Loki, etc.)
|
||||||
config :logger, :default_handler, formatter: {LoggerJSON.Formatters.Basic, []}
|
config :logger, :default_handler, formatter: {LoggerJSON.Formatters.Basic, []}
|
||||||
|
|
||||||
# Persistent image cache on the Fly volume (survives deploys)
|
# Persistent image cache and mockup variants on the Fly volume (survives deploys)
|
||||||
config :berrypod, :image_cache_dir, "/data/image_cache"
|
config :berrypod, :image_cache_dir, "/data/image_cache"
|
||||||
|
config :berrypod, :mockup_dir, "/data/mockups"
|
||||||
|
|
||||||
# Runtime production configuration, including reading
|
# Runtime production configuration, including reading
|
||||||
# of environment variables, is done on config/runtime.exs.
|
# of environment variables, is done on config/runtime.exs.
|
||||||
|
|||||||
@ -19,7 +19,13 @@ defmodule Berrypod.Images.VariantCache do
|
|||||||
alias Berrypod.Sync.ImageDownloadWorker
|
alias Berrypod.Sync.ImageDownloadWorker
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
|
|
||||||
defp mockup_dir, do: Application.app_dir(:berrypod, "priv/static/mockups")
|
# Source mockups bundled in the release
|
||||||
|
defp release_mockup_dir, do: Application.app_dir(:berrypod, "priv/static/mockups")
|
||||||
|
|
||||||
|
# Where mockup variants live — persistent volume in prod, release dir in dev
|
||||||
|
defp mockup_dir do
|
||||||
|
Application.get_env(:berrypod, :mockup_dir) || release_mockup_dir()
|
||||||
|
end
|
||||||
|
|
||||||
def start_link(opts) do
|
def start_link(opts) do
|
||||||
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
|
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
|
||||||
@ -84,9 +90,25 @@ defmodule Berrypod.Images.VariantCache do
|
|||||||
end
|
end
|
||||||
|
|
||||||
defp ensure_mockup_variants do
|
defp ensure_mockup_variants do
|
||||||
if File.dir?(mockup_dir()) do
|
output = mockup_dir()
|
||||||
|
release = release_mockup_dir()
|
||||||
|
|
||||||
|
# When using a persistent volume, copy source WebPs from the release
|
||||||
|
# so variants can be generated there (and survive deploys).
|
||||||
|
if output != release and File.dir?(release) do
|
||||||
|
File.mkdir_p!(output)
|
||||||
|
|
||||||
|
Path.wildcard(Path.join(release, "*.webp"))
|
||||||
|
|> Enum.reject(&is_variant?/1)
|
||||||
|
|> Enum.each(fn src ->
|
||||||
|
dest = Path.join(output, Path.basename(src))
|
||||||
|
unless File.exists?(dest), do: File.cp!(src, dest)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
if File.dir?(output) do
|
||||||
sources =
|
sources =
|
||||||
Path.wildcard(Path.join(mockup_dir(), "*.webp"))
|
Path.wildcard(Path.join(output, "*.webp"))
|
||||||
|> Enum.reject(&is_variant?/1)
|
|> Enum.reject(&is_variant?/1)
|
||||||
|
|
||||||
missing = Enum.reject(sources, &mockup_variants_exist?/1)
|
missing = Enum.reject(sources, &mockup_variants_exist?/1)
|
||||||
|
|||||||
@ -16,7 +16,7 @@ defmodule BerrypodWeb.Endpoint do
|
|||||||
websocket: [connect_info: [session: @session_options]],
|
websocket: [connect_info: [session: @session_options]],
|
||||||
longpoll: [connect_info: [session: @session_options]]
|
longpoll: [connect_info: [session: @session_options]]
|
||||||
|
|
||||||
# In prod, image variants live on the persistent volume (/data/image_cache)
|
# In prod, image variants and mockups live on the persistent volume
|
||||||
# rather than inside the ephemeral release directory.
|
# rather than inside the ephemeral release directory.
|
||||||
if image_cache_dir = Application.compile_env(:berrypod, :image_cache_dir) do
|
if image_cache_dir = Application.compile_env(:berrypod, :image_cache_dir) do
|
||||||
plug Plug.Static,
|
plug Plug.Static,
|
||||||
@ -25,6 +25,13 @@ defmodule BerrypodWeb.Endpoint do
|
|||||||
cache_control_for_etags: "public, max-age=31536000, immutable"
|
cache_control_for_etags: "public, max-age=31536000, immutable"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if mockup_dir = Application.compile_env(:berrypod, :mockup_dir) do
|
||||||
|
plug Plug.Static,
|
||||||
|
at: "/mockups",
|
||||||
|
from: mockup_dir,
|
||||||
|
cache_control_for_etags: "public, max-age=31536000, immutable"
|
||||||
|
end
|
||||||
|
|
||||||
# Serve at "/" the static files from "priv/static" directory.
|
# Serve at "/" the static files from "priv/static" directory.
|
||||||
# gzip only in prod — avoids stale .gz files from mix assets.deploy
|
# gzip only in prod — avoids stale .gz files from mix assets.deploy
|
||||||
# shadowing freshly-built dev assets.
|
# shadowing freshly-built dev assets.
|
||||||
|
|||||||
@ -35,6 +35,7 @@ defmodule BerrypodWeb.Plugs.BrokenUrlTracker do
|
|||||||
String.starts_with?(path, "/assets/") or
|
String.starts_with?(path, "/assets/") or
|
||||||
String.starts_with?(path, "/images/") or
|
String.starts_with?(path, "/images/") or
|
||||||
String.starts_with?(path, "/image_cache/") or
|
String.starts_with?(path, "/image_cache/") or
|
||||||
|
String.starts_with?(path, "/mockups/") or
|
||||||
String.starts_with?(path, "/favicon")
|
String.starts_with?(path, "/favicon")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user