https://github.com/phoenixframework/flame
https://github.com/phoenixframework/flame
Last synced: 3 months ago
JSON representation
- Host: GitHub
- URL: https://github.com/phoenixframework/flame
- Owner: phoenixframework
- License: mit
- Created: 2023-10-30T17:21:14.000Z (over 1 year ago)
- Default Branch: main
- Last Pushed: 2024-04-22T19:33:16.000Z (about 1 year ago)
- Last Synced: 2024-05-08T20:36:15.391Z (about 1 year ago)
- Language: Elixir
- Size: 167 KB
- Stars: 679
- Watchers: 15
- Forks: 25
- Open Issues: 9
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE.md
Awesome Lists containing this project
README
[](https://github.com/phoenixframework/flame/actions/workflows/elixir.yml) [](https://hex.pm/packages/flame) [](https://hexdocs.pm/flame)
Imagine if we could auto scale simply by wrapping any existing app code in a function and have that block of code run in a temporary copy of the app.
Enter the FLAME pattern.
> FLAME - Fleeting Lambda Application for Modular Execution
With FLAME, you treat your *entire application* as a lambda, where modular parts can be executed on short-lived infrastructure.
Check the screencast to see it in action:
[](https://www.youtube.com/watch?v=l1xt_rkWdic)
## Setup
First add `:flame` as a dependency:
```elixir
defp deps do
[
# For Erlang/OTP 26 and earlier, you also need Jason
# {:jason, ">= 0.0.0"},
{:flame, "~> 0.5"}
]
end
```Then start a FLAME pool in your supervision tree, typically on `application.ex`. The example below uses [Fly.io](https://fly.io/)'s backend:
```elixir
children = [
{FLAME.Pool,
name: MyApp.SamplePool,
backend: FLAME.FlyBackend,
min: 0,
max: 10,
max_concurrency: 5,
idle_shutdown_after: 30_000,
log: :debug}
]
```Now you can wrap any block of code in a `FLAME.call` and it will find or boot a copy of the app, execute the work there, and return the results:
```elixir
def generate_thumbnails(%Video{} = vid, interval) do
FLAME.call(MyApp.FFMpegRunner, fn ->
# I'm runner on a short-lived, temporary server
tmp_dir = Path.join(System.tmp_dir!(), Ecto.UUID.generate())
File.mkdir!(tmp_dir)
System.cmd("ffmpeg", ~w(-i #{vid.url} -vf fps=1/#{interval} #{tmp_dir}/%02d.png))
urls = VideoStore.put_thumbnails(vid, Path.wildcard(tmp_dir <> "/*.png"))
Repo.insert_all(Thumbnail, Enum.map(urls, &%{video_id: vid.id, url: &1}))
end)
end
```Here we wrapped up our CPU expensive `ffmpeg` operation in a `FLAME.call/2`. FLAME accepts a function and any variables that the function closes over. In this example, the `%Video{}` struct and `interval` are passed along automatically. The work happens in a temporary copy of the app. We can do any work inside the FLAME call because we are running the *entire application*, database connection(s) and all.
`FLAME` provides the following interfaces for elastically scaled operations:
* `FLAME.call/3` - used for synchronous calls
* `FLAME.cast/3` - used for async casts where you don't need to wait on the results
* `FLAME.place_child/3` – used for placing a child spec somewhere to run, in place of `DynamicSupervisor.start_child`, `Task.Supervisor.start_child`, etcThe `FLAME.Pool` handles elastically scaling runners up and down, as well as remote monitoring of resources. Check the moduledoc for example usage.