cap image variant concurrency to CPU core count
All checks were successful
deploy / deploy (push) Successful in 1m5s
All checks were successful
deploy / deploy (push) Successful in 1m5s
Replaces unbounded Task.async parallelism with Task.async_stream capped at System.schedulers_online(). On shared-cpu-1x this prevents CPU saturation and SQLite locking; on beefier machines it still saturates all cores. Also releases the DB connection before starting libvips processing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
9a27723b52
commit
e66d51a08b
@ -71,21 +71,25 @@ defmodule Berrypod.Images.Optimizer do
|
||||
Called by Oban worker.
|
||||
"""
|
||||
def process_for_image(image_id) do
|
||||
case Repo.get(ImageSchema, image_id) do
|
||||
# Load the image row and release the DB connection immediately,
|
||||
# so libvips processing doesn't block other queries.
|
||||
image = Repo.get(ImageSchema, image_id)
|
||||
|
||||
case image do
|
||||
nil ->
|
||||
{:error, :not_found}
|
||||
|
||||
%{data: nil} ->
|
||||
{:error, :no_data}
|
||||
|
||||
%{is_svg: true} = image ->
|
||||
%{is_svg: true} ->
|
||||
Repo.update!(ImageSchema.changeset(image, %{variants_status: "complete"}))
|
||||
{:ok, :svg_skipped}
|
||||
|
||||
%{data: data} when byte_size(data) < @min_image_bytes ->
|
||||
{:error, :too_small}
|
||||
|
||||
%{data: data, source_width: width} = image ->
|
||||
%{data: data, source_width: width} ->
|
||||
File.mkdir_p!(cache_dir())
|
||||
|
||||
# Write source WebP to disk so it can be served by Plug.Static
|
||||
@ -95,14 +99,19 @@ defmodule Berrypod.Images.Optimizer do
|
||||
with {:ok, vips_image} <- Image.from_binary(data) do
|
||||
widths = applicable_widths(width)
|
||||
|
||||
tasks = [
|
||||
Task.async(fn -> generate_thumbnail(vips_image, image_id) end)
|
||||
| for w <- widths, fmt <- @pregenerated_formats do
|
||||
Task.async(fn -> generate_variant(vips_image, image_id, w, fmt) end)
|
||||
all_tasks =
|
||||
[fn -> generate_thumbnail(vips_image, image_id) end] ++
|
||||
for w <- widths, fmt <- @pregenerated_formats do
|
||||
fn -> generate_variant(vips_image, image_id, w, fmt) end
|
||||
end
|
||||
]
|
||||
|
||||
Task.await_many(tasks, :timer.seconds(120))
|
||||
# Cap concurrency to the number of CPU cores — keeps small
|
||||
# machines from choking while still saturating bigger ones.
|
||||
Task.async_stream(all_tasks, & &1.(),
|
||||
max_concurrency: System.schedulers_online(),
|
||||
timeout: :timer.seconds(120)
|
||||
)
|
||||
|> Stream.run()
|
||||
|
||||
Repo.update!(ImageSchema.changeset(image, %{variants_status: "complete"}))
|
||||
{:ok, widths}
|
||||
@ -182,25 +191,17 @@ defmodule Berrypod.Images.Optimizer do
|
||||
{:ok, vips_image} <- Image.from_binary(webp_data) do
|
||||
widths = applicable_widths(source_width)
|
||||
|
||||
tasks = [
|
||||
Task.async(fn ->
|
||||
generate_variant_to_dir(
|
||||
vips_image,
|
||||
output_basename,
|
||||
output_dir,
|
||||
"thumb",
|
||||
:jpg,
|
||||
@thumb_size
|
||||
)
|
||||
end)
|
||||
| for w <- widths, fmt <- @pregenerated_formats do
|
||||
Task.async(fn ->
|
||||
generate_variant_to_dir(vips_image, output_basename, output_dir, w, fmt, w)
|
||||
end)
|
||||
all_tasks =
|
||||
[fn -> generate_variant_to_dir(vips_image, output_basename, output_dir, "thumb", :jpg, @thumb_size) end] ++
|
||||
for w <- widths, fmt <- @pregenerated_formats do
|
||||
fn -> generate_variant_to_dir(vips_image, output_basename, output_dir, w, fmt, w) end
|
||||
end
|
||||
]
|
||||
|
||||
Task.await_many(tasks, :timer.seconds(120))
|
||||
Task.async_stream(all_tasks, & &1.(),
|
||||
max_concurrency: System.schedulers_online(),
|
||||
timeout: :timer.seconds(120)
|
||||
)
|
||||
|> Stream.run()
|
||||
{:ok, source_width}
|
||||
end
|
||||
rescue
|
||||
|
||||
Loading…
Reference in New Issue
Block a user