I’m trying to get some code to run in Flyio Flame machines.
I have a phoenix app (although, I think the question applies more generally to Elixir and Flame).
There is a live file upload module that takes a few files, and using an image library resizes, converts and saves them to disk. The path to the images is saved in a Postgres db. Very simplistic, this is a learning tool, not a production app.
Resizing and saving the files is not CPU intensive, but it can use up a lot of memory, so it would help to do each operation on a new machine. (I’m aware other kinds optimizations are possible, but the purpose of this is to try Flame).
This blog post (Rethinking Serverless with FLAME · The Fly Blog) seems to state that it’s enough to wrap a chunk of code in a FLAME.call
with a FLAME.Pool
and those chunks should run on separate machines.
That’s it!
FLAME.call
accepts the name of a runner pool, and a function. It then finds or boots a new copy of our entire application and runs the function there.
I have this implementation and I’m not seeing and Flame machines start up. Am I doing something wrong? Did I misunderstand the post, and do I in fact need the entire GenServer
and FLAME.place_child
functionality?
The FLAME.Pool
in /lib/phoenix_albums/application.ex
@impl true
def start(_type, _args) do
flame_parent = FLAME.Parent.get()
children =
[
PhoenixAlbumsWeb.Telemetry,
PhoenixAlbums.Repo,
{DNSCluster, query: Application.get_env(:phoenix_albums, :dns_cluster_query) || :ignore},
{Phoenix.PubSub, name: PhoenixAlbums.PubSub},
# Start the Finch HTTP client for sending emails
{Finch, name: PhoenixAlbums.Finch},
# Start a worker by calling: PhoenixAlbums.Worker.start_link(arg)
# {PhoenixAlbums.Worker, arg},
# Start to serve requests, typically the last entry
{FLAME.Pool,
name: PhoenixAlbums.ImageProcessor, min: 0, max: 10, max_concurrency: 1, log: :debug},
!flame_parent && PhoenixAlbumsWeb.Endpoint
]
|> Enum.filter(& &1)
# See https://hexdocs.pm/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: PhoenixAlbums.Supervisor]
Supervisor.start_link(children, opts)
end
My upload handler in the live
fodler:
@impl Phoenix.LiveView
def handle_event("save", params, socket) do
unless File.exists?(@folder) do
File.mkdir!(@folder)
end
uploaded_files =
consume_uploaded_entries(socket, :avatar, fn %{path: path}, entry ->
uuid = UUID.generate()
image_folder = "#{@folder}/#{uuid}"
unless File.exists?(image_folder) do
File.mkdir!(image_folder)
end
FLAME.call(
PhoenixAlbums.ImageProcessor,
fn -> resize_image(:thumbnail, path, 150, image_folder) end
)
FLAME.call(PhoenixAlbums.ImageProcessor, fn ->
resize_image(:medium, path, 800, image_folder)
end)
FLAME.call(PhoenixAlbums.ImageProcessor, fn ->
save_original(path, image_folder)
end)
image = Map.merge(params, %{"user_id" => socket.assigns.user_id, "url" => image_folder})
case Album.create_image(image) do
{:ok, image} ->
socket
|> put_flash(:info, "Image created successfully.")
{:ok, ~p"/images/#{image}"}
end
end)
{:noreply, update(socket, :uploaded_files, &(&1 ++ uploaded_files))}
end
Here I just wrapped resize_image
and save_original
functions, with the expectation, that these would be run on separate machines. The code runs on fly just the same as without the wrappers, and I’m not sure why.