FLAME - jak skalować aplikację Elixir jedną funkcją, bez Kubernetesa i bez AWS Lambda
Piątek, 22:00. Klient uruchamia kampanię marketingową. Ruch na stronie rośnie 10x w ciągu godziny. System generuje raporty PDF dla każdego zamówienia. Jeden serwer nie nadąża - kolejka PDF-ów rośnie, czas generowania z 2 sekund skacze do 45, użytkownicy widzą spinner.
Tradycyjne rozwiązania:
Kubernetes: Konfiguracja HPA (Horizontal Pod Autoscaler), Deployment, Service, Ingress, health checks, readiness probes. Nowe pody startują w 30-60 sekund. Potrzebujesz DevOpsa, który to ogarnia. Koszt: 8 000-25 000 PLN/mies. za klaster + człowiek, który go utrzymuje.
AWS Lambda: Przepisz generowanie PDF na osobną funkcję. Skonfiguruj SQS jako kolejkę, S3 jako storage pośredni, API Gateway jako trigger, IAM role, CloudWatch logi. Lambda ma limit 15 minut na wykonanie i 10 GB RAM. Twój PDF z 500-stronicowym raportem się w to nie zmieści. Cold start: 1-5 sekund. Od sierpnia 2025 AWS pobiera opłaty za fazę INIT - koszty cold startów wzrosły 22x.
FLAME: Dodaj 3 linie kodu. Zero nowej infrastruktury. Zero przepisywania. Jedna funkcja:
FLAME.call(MyApp.PdfRunner, fn ->
PdfGenerator.generate(order)
end)Nowa maszyna z aplikacją startuje w ~3 sekundy. Generuje PDF. Wynik wraca do głównej aplikacji. Maszyna obsługuje kolejne zadania, a gdy kolejka się opróżni - wyłącza się sama. Koszt: tyle, ile faktycznie zużyjesz.
Czym jest FLAME
FLAME - Fleeting Lambda Application for Modular Execution - to biblioteka stworzona przez Chrisa McCorda (twórcę Phoenix) w ramach projektu phoenixframework na GitHubie. Nie jest to framework, nie jest to platforma - to wzorzec skalowania, który można podsumować jednym zdaniem:
Twoja istniejąca aplikacja jest funkcją.
Zamiast przepisywać kod na AWS Lambda, Google Cloud Functions czy Azure Functions - zamiast wydzielać logikę do osobnego serwisu, konfigurować kolejki, storage i triggery - opakowujesz istniejący kod w FLAME.call() i BEAM automatycznie stawia tymczasową kopię aplikacji, wykonuje na niej operację i zwraca wynik.
Żadnego nowego kodu. Żadnej nowej infrastruktury. Żadnego vendor lock-in.
Jak to działa
Architektura
Aplikacja Phoenix (parent)
│
├── FLAME.Pool :pdf_runner (min: 0, max: 10)
│ │
│ ├── FLAME.call() → Runner 1 (kopia aplikacji)
│ ├── FLAME.call() → Runner 2 (kopia aplikacji)
│ └── ...idle → Runner wyłącza się po 30s
│
├── FLAME.Pool :ml_inference (min: 1, max: 5)
│ │
│ └── FLAME.call() → Runner z GPU
│
└── Reszta aplikacji działa normalnieFLAME zarządza poolami runnerów. Każdy pool to grupa tymczasowych maszyn, na których działa kopia aplikacji. Gdy przychodzi zadanie, FLAME:
- Sprawdza, czy w poolu jest wolny runner
- Jeśli tak - wykonuje funkcję na nim
- Jeśli nie i nie osiągnięto limitu - stawia nowy runner (nowa maszyna z Docker image)
- Runner łączy się z klastrem BEAM w ~3 sekundy
- Funkcja się wykonuje, wynik wraca do callera
- Runner czeka na kolejne zadanie
- Po
idle_shutdown_aftermilisekund bezczynności - runner się wyłącza
Kluczowa różnica: runner to pełna kopia aplikacji. Ma dostęp do bazy danych, PubSub, Oban, konfiguracji, wszystkich modułów. Nie musisz przekazywać kontekstu, nie musisz serializować danych do JSON-a, nie musisz budować API między komponentami.
FLAME.call - synchroniczne wywołanie
# Generowanie PDF - ciężka operacja CPU
result = FLAME.call(MyApp.PdfRunner, fn ->
# Ten kod wykonuje się na ZDALNYM runnerze
# Ale ma dostęp do wszystkiego - Repo, PubSub, config
order = MyApp.Orders.get!(order_id)
pdf = MyApp.PdfGenerator.generate_invoice(order)
MyApp.Storage.upload(pdf, "invoices/#{order.id}.pdf")
{:ok, pdf_url}
end)
# result = {:ok, "https://storage.example.com/invoices/123.pdf"}
# Zwrócone synchronicznie, jakby kod wykonał się lokalnieZmienne zamknięte w closure (order_id) są automatycznie przekazywane do runnera. Wynik wraca przez Erlang distribution - natywny mechanizm komunikacji między węzłami BEAM. Zero serializacji JSON, zero HTTP, zero kolejek.
FLAME.cast - fire and forget
# Generowanie thumbnailów - nie czekamy na wynik
FLAME.cast(MyApp.ImageRunner, fn ->
MyApp.Images.generate_thumbnails(upload_id, [
{200, 200}, # miniatura
{800, 600}, # podgląd
{1920, 1080} # full HD
])
end)
# Kod wraca natychmiast
# Thumbnaily generują się w tle na runnerzeFLAME.place_child - długo żyjące procesy
# Uruchom proces transkodowania wideo na runnerze
{:ok, pid} = FLAME.place_child(MyApp.VideoRunner, {
MyApp.VideoTranscoder,
video_id: video.id, format: :h265
})
# pid to normalny PID procesu BEAM
# Możesz wysyłać mu wiadomości, monitorować, linkować
# Działa na runnerze, ale komunikacja jest transparentna
send(pid, {:progress_update, self()})place_child jest odpowiednikiem DynamicSupervisor.start_child, ale proces uruchamia się na zdalnym runnerze. Supervisor na runnerze nadzoruje ten proces. Jeśli runner padnie - parent o tym wie (Erlang node monitoring). Jeśli parent padnie - runner może kontynuować pracę lub się zamknąć (konfigurowalne).
Konfiguracja poola
# lib/my_app/application.ex
defmodule MyApp.Application do
use Application
def start(_type, _args) do
children = [
MyApp.Repo,
MyAppWeb.Endpoint,
# Pool do generowania PDF-ów
{FLAME.Pool,
name: MyApp.PdfRunner,
min: 0, # Scale to zero - zero maszyn gdy brak pracy
max: 10, # Maksymalnie 10 runnerów
max_concurrency: 5, # 5 PDF-ów jednocześnie per runner
idle_shutdown_after: 30_000 # Wyłącz po 30s bezczynności
},
# Pool do ML inference - zawsze co najmniej 1 runner gotowy
{FLAME.Pool,
name: MyApp.MlRunner,
min: 1, # Zawsze 1 ciepły runner
max: 5,
max_concurrency: 2, # ML jest ciężki - max 2 na runner
idle_shutdown_after: 120_000 # Dłuższy idle - model się ładuje wolno
}
]
Supervisor.start_link(children, strategy: :one_for_one)
end
endTo jest cała konfiguracja. Żadnego Kubernetesa, żadnych YAML-ów, żadnych Terraform modułów. Pool jest częścią supervision tree aplikacji - zarządzany tak samo jak Repo czy Endpoint.
Konfiguracja backendu
# config/prod.exs
# Fly.io (oficjalny backend)
config :flame, :backend, FLAME.FlyBackend
config :flame, FLAME.FlyBackend,
token: System.fetch_env!("FLY_API_TOKEN")
# Kubernetes (community backend)
config :flame, :backend, FlameK8sBackend
# Development - lokalne wykonanie, bez zdalnych runnerów
# config/dev.exs
config :flame, :backend, FLAME.LocalBackendW dev FLAME.call wykonuje funkcję lokalnie - w tym samym procesie. Zero różnicy w kodzie między dev a produkcją. Nie potrzebujesz Dockera, Fly.io ani Kubernetesa, żeby rozwijać aplikację.
Backendy - nie tylko Fly.io
FLAME ma pluggowalne backendy. Oficjalny backend to Fly.io, ale FLAME nie jest przywiązany do żadnego dostawcy:
| Backend | Status | Infrastruktura | Cold start |
|---|---|---|---|
FLAME.FlyBackend | Oficjalny | Fly.io Machines | ~3s |
FlameK8sBackend | Community (Hex) | Kubernetes | ~10-30s (zależy od klastra) |
FLAME.LocalBackend | Wbudowany | Lokalnie (dev/test) | 0s |
| Custom | Behavior do implementacji | Dowolna chmura | Zależy od API |
Backend Kubernetes (flame_k8s_backend) odpytuje API Kubernetesa, wyciąga informacje z bieżącego Poda (image, env vars, resource limits) i tworzy tymczasowe Pody-runnery z tą samą konfiguracją. Działa na dowolnym klastrze K8s - AWS EKS, GCP GKE, Azure AKS, bare metal.
Napisanie własnego backendu to implementacja behaviour FLAME.Backend - cold/remote start, terminacja, health check. Backend Fly.io ma mniej niż 200 linii kodu z dokumentacją. Jeśli masz API do provisioningu maszyn wirtualnych - możesz mieć własny backend w dzień.
FLAME vs serverless - punkt po punkcie
Problem 1: Przepisywanie kodu
AWS Lambda: Logika biznesowa musi być wydzielona do osobnej funkcji. Potrzebujesz handler'a, serializacji wejścia/wyjścia, warstw (layers) dla zależności. Kod, który działa w Twoim monolicie, nie działa w Lambda bez zmian.
# AWS Lambda - musisz przepisać kod
import json
import boto3
def handler(event, context):
# Deserializuj wejście z SQS/API Gateway
body = json.loads(event['body'])
order_id = body['order_id']
# Nie masz dostępu do swojego ORM-a, configu, serwisów
# Musisz połączyć się z bazą bezpośrednio
# Musisz pobrać plik z S3
# Musisz wysłać wynik do innej kolejki SQS
return {
'statusCode': 200,
'body': json.dumps({'pdf_url': url})
}FLAME: Zero przepisywania. Opakowujesz istniejący kod:
# FLAME - istniejący kod bez zmian
FLAME.call(MyApp.PdfRunner, fn ->
# Ten sam kod, który działał w monolicie
# Pełny dostęp do Repo, Config, PubSub, Oban
order = MyApp.Orders.get!(order_id)
MyApp.PdfGenerator.generate_invoice(order)
end)Problem 2: Cold starty
AWS Lambda: Cold start 1-5 sekund (Java, .NET). Od sierpnia 2025 AWS nalicza opłaty za fazę INIT - koszt cold startów wzrósł z ~$0.80 do $17.80 per milion invokacji (22x). Provisioned concurrency eliminuje cold starty, ale kosztuje 24/7, niezależnie od użycia.
FLAME: Runner startuje w ~3 sekundy na Fly.io. Ale:
min: 1- zawsze 1 ciepły runner, zero cold startów- Runner obsługuje wiele wywołań (max_concurrency) - cold start jest amortyzowany
- Runner nie wyłącza się po jednym wywołaniu - idle timeout daje czas na kolejne żądania
Lambda: 1 invokacja = 1 cold start (chyba że trafisz w ciepłą instancję). FLAME: 1 runner = setki wywołań.
Problem 3: Limity
| Limit | AWS Lambda | FLAME |
|---|---|---|
| Timeout | 15 minut | Brak (runner żyje dowolnie długo) |
| RAM | 10 GB | Tyle ile ma maszyna |
| Dysk | 10 GB (/tmp) | Pełny dysk maszyny |
| Rozmiar paczki | 250 MB (deployment) | Brak (Docker image) |
| Połączenia sieciowe | Ograniczone | Bez ograniczeń |
| Stan między wywołaniami | Brak (stateless) | Tak (runner utrzymuje stan) |
| Dostęp do bazy | Przez VPC + connection pooler | Natywny (ta sama aplikacja) |
| WebSocket | Nie | Tak (BEAM distribution) |
Twój raport generuje się 20 minut? Lambda go ubije po 15. FLAME nie ma limitu - runner pracuje, dopóki nie skończy.
Problem 4: Vendor lock-in
AWS Lambda: Twój kod używa event, context, AWS SDK, SQS, S3, API Gateway, IAM, CloudWatch, X-Ray. Migracja do GCP Cloud Functions = przepisanie wszystkiego.
FLAME: Twój kod to normalny Elixir. FLAME.call() to jedyna zależność. Zmiana backendu (Fly.io → Kubernetes → custom) to zmiana jednej linii w configu. Logika biznesowa nie wie, że jest skalowana.
Problem 5: Koszty
Policzmy dla scenariusza: 50 000 generowań PDF dziennie, średni czas 3 sekundy, 512 MB RAM.
| Składnik | AWS Lambda | FLAME (Fly.io) |
|---|---|---|
| Compute | ~$45/mies. (invokacje + czas) | ~$30/mies. (shared-cpu-1x, scale-to-zero) |
| Cold start tax (od 08.2025) | ~$27/mies. | $0 (amortyzowany przez pooling) |
| SQS (kolejka) | ~$5/mies. | $0 (nie potrzebna) |
| S3 (pośredni storage) | ~$3/mies. | $0 (dane przez BEAM distribution) |
| API Gateway | ~$18/mies. | $0 (nie potrzebny) |
| CloudWatch (logi) | ~$10/mies. | $0 (logi w aplikacji) |
| DevOps / konfiguracja | 20-40h jednorazowo | 1-2h jednorazowo |
| Razem miesięcznie | ~$108/mies. | ~$30/mies. |
Lambda nie jest droga za compute. Jest droga za wszystko dookoła - kolejki, storage, gateway, logi, monitoring. To jest serverless tax - cena za architekturę, nie za obliczenia.
FLAME eliminuje serverless tax, bo nie potrzebuje dodatkowej infrastruktury. Dane przepływają przez Erlang distribution. Logi są w aplikacji. Monitoring to ten sam Telemetry, który już masz.
Praktyczne scenariusze
Generowanie PDF-ów i raportów
Najczęstszy case. Raport kwartalny dla zarządu zjada CPU na 30 sekund. 20 użytkowników generuje raporty jednocześnie. FLAME odciąża główny serwer:
def generate_report(conn, %{"type" => type, "range" => range}) do
# Ciężkie obliczenie na runnerze, główny serwer wolny
{:ok, report} = FLAME.call(MyApp.ReportRunner, fn ->
MyApp.Reports.generate(type, range)
end)
send_download(conn, report, filename: "raport_#{type}.pdf")
endPrzetwarzanie obrazów i wideo
Resize, thumbnaily, transkodowanie. Operacje CPU-intensive, które nie powinny blokować web requestów:
def handle_event("upload_complete", %{"id" => upload_id}, socket) do
# Fire and forget - thumbnaily generują się w tle
FLAME.cast(MyApp.ImageRunner, fn ->
upload = MyApp.Uploads.get!(upload_id)
thumbnails = MyApp.Images.generate_thumbnails(upload.path, [
{:thumb, 200, 200},
{:preview, 800, 600},
{:hd, 1920, 1080}
])
MyApp.Uploads.attach_thumbnails(upload, thumbnails)
# PubSub działa - LiveView użytkownika dostanie update
Phoenix.PubSub.broadcast(MyApp.PubSub,
"uploads:#{upload.user_id}",
{:thumbnails_ready, upload_id}
)
end)
{:noreply, put_flash(socket, :info, "Przetwarzanie zdjęć...")}
endZwróć uwagę: Phoenix.PubSub.broadcast działa z runnera, bo runner jest częścią klastra BEAM. LiveView użytkownika dostaje powiadomienie natychmiast po wygenerowaniu thumbnailów - bez pollingu, bez webhooków, bez kolejek.
ML inference - elastyczne GPU
FLAME + Nx + Bumblebee to stack do uruchamiania modeli ML bez dedykowanego serwera GPU:
# Pool z GPU runnerami - min 0 (scale to zero)
{FLAME.Pool,
name: MyApp.MlRunner,
min: 0,
max: 3,
max_concurrency: 4,
idle_shutdown_after: 300_000 # GPU drogie - wyłącz po 5 min
}def classify_document(document) do
FLAME.call(MyApp.MlRunner, fn ->
# Model załadowany na runnerze z GPU
Nx.Serving.batched_run(MyApp.DocumentClassifier, document.text)
end)
endKoszt GPU na Fly.io: ~$2.50/h za A100. Bez FLAME musisz utrzymywać GPU 24/7 ($1 800/mies.) albo budować własną infrastrukturę kolejkowania. Z FLAME - GPU działa tylko gdy jest potrzebne.
Import danych - elastyczne przetwarzanie
Kontrahent wysyła CSV z 500 000 wierszy. FLAME + Broadway:
defmodule MyApp.CsvImporter do
def import(file_path) do
# Ciężki import na runnerze, główny serwer wolny
FLAME.call(MyApp.ImportRunner, fn ->
file_path
|> File.stream!()
|> CSV.decode!(headers: true)
|> Stream.chunk_every(1000)
|> Enum.each(fn batch ->
rows = Enum.map(batch, &transform_row/1)
MyApp.Repo.insert_all("products", rows,
on_conflict: :replace_all,
conflict_target: :external_id
)
end)
end, timeout: 600_000) # 10 minut na import
end
endLambda by to ubił po 15 minutach. FLAME nie ma limitu.
Warunkowa konfiguracja runnera
Runnery nie potrzebują pełnej aplikacji. Nie muszą serwować web requestów, nie muszą uruchamiać schedulerów Oban. FLAME pozwala warunkować konfiguracj*:
# lib/my_app/application.ex
def start(_type, _args) do
children =
if FLAME.Parent.get() do
# Jesteśmy runnerem - minimalna konfiguracja
[
MyApp.Repo, # Potrzebujemy bazy
# BEZ Phoenix.Endpoint - nie serwujemy HTTP
# BEZ Oban - nie przetwarzamy jobów
# BEZ schedulerów - nie uruchamiamy cronjobów
]
else
# Jesteśmy parentem - pełna aplikacja
[
MyApp.Repo,
MyAppWeb.Endpoint,
{Oban, Application.fetch_env!(:my_app, Oban)},
{FLAME.Pool, name: MyApp.PdfRunner, min: 0, max: 10, ...},
{FLAME.Pool, name: MyApp.ImageRunner, min: 0, max: 5, ...}
]
end
Supervisor.start_link(children, strategy: :one_for_one)
endRunner z minimalną konfiguracją startuje szybciej i zużywa mniej RAM-u. Baza danych jest potrzebna, bo logika biznesowa z niej korzysta. Ale Phoenix.Endpoint? Oban? Nie - runner nie serwuje HTTP i nie przetwarza jobów w tle. Robi jedną rzecz i robi ją szybko.
FLAME vs Kubernetes HPA vs AWS Lambda
| Wymiar | FLAME | Kubernetes HPA | AWS Lambda |
|---|---|---|---|
| Zmiana w kodzie | 1 linia (FLAME.call) | Zero (ale YAML) | Przepisanie na handler |
| Infrastruktura dodatkowa | Zero | K8s cluster, registry, monitoring | SQS, S3, API GW, IAM |
| Cold start | ~3s (Fly.io) | 30-60s (nowy pod) | 1-5s (zależy od języka) |
| Scale to zero | Tak | Tak (KEDA) | Tak |
| Limit czasu | Brak | Brak | 15 minut |
| Stan między wywołaniami | Tak (runner żyje) | Tak (pod żyje) | Nie |
| Dostęp do bazy | Natywny | Natywny | VPC + pooler |
| Vendor lock-in | Niski (zmiana backendu) | Średni (K8s API) | Wysoki (AWS SDK) |
| Koszt DevOps | Minimalny | Wysoki | Średni |
| YAML/Terraform | 0 plików | 5-15 plików | 3-8 plików |
| Czas wdrożenia | 1-2 godziny | 1-3 tygodnie | 2-5 dni |
Kubernetes jest potężny, ale kosztuje - w infrastrukturze, ludziach i czasie. Lambda jest prosta, ale ograniczona i tworzy vendor lock-in. FLAME daje elastyczność obu bez kosztów żadnego.
Kiedy FLAME nie jest odpowiedzią
Uczciwie - FLAME nie rozwiązuje każdego problemu skalowania:
Skalowanie web requestów - jeśli Twój bottleneck to ilość HTTP requestów, FLAME nie pomoże. Potrzebujesz więcej instancji Phoenixa za load balancerem, nie więcej runnerów FLAME. FLAME skaluje compute, nie throughput HTTP.
Procesy long-running z dużym stanem - GenServer z 10 GB stanu w pamięci nie przeniesie się łatwo na runnera. FLAME najlepiej działa z operacjami, które zaczynają od zera (pobierz dane → przetworz → zwróć wynik).
Heterogeniczny stack - jeśli Twój backend jest w Javie/Go/Pythonie, FLAME nie ma sensu. FLAME wykorzystuje Erlang distribution - natywną komunikację między węzłami BEAM. Inny runtime = inny wzorzec.
Wymagania regulacyjne - niektóre branże wymagają, żeby dane nie opuszczały określonej strefy. FLAME na Fly.io pozwala wybrać region, ale musisz to skonfigurować.
Ile to kosztuje - kalkulator
Scenariusz: firma średniej wielkości
System ERP z modułami: zamówienia, faktury, raporty, import danych. 50 użytkowników, ~5 000 operacji ciężkich dziennie (PDF, importy, raporty).
Bez FLAME (jeden serwer):
| Element | Koszt |
|---|---|
| Serwer aplikacyjny (8 CPU, 16 GB) | 1 200 PLN/mies. |
| Problem: raporty blokują system | Utracona produktywność: ~20 000 PLN/rok |
| Problem: importy spowalniają UI | Frustracja zespołu: niepoliczalna |
Z FLAME (serwer + elastyczne runnery):
| Element | Koszt |
|---|---|
| Serwer aplikacyjny (4 CPU, 8 GB) | 600 PLN/mies. |
| FLAME runnery (scale-to-zero, avg 2-3h/dzień) | 200-400 PLN/mies. |
| Razem | 800-1 000 PLN/mies. |
| Raporty nie blokują systemu | ✓ |
| Importy nie spowalniają UI | ✓ |
Mniejszy główny serwer (bo ciężkie operacje idą na runnery) + elastyczne runnery = tańsza infrastruktura i lepsze doświadczenie użytkownika.
Porównanie z alternatywami
| Rozwiązanie | Koszt infra/mies. | Czas wdrożenia | DevOps |
|---|---|---|---|
| Większy serwer (16 CPU, 32 GB) | 2 400 PLN | 1 dzień | Zero |
| Kubernetes + HPA | 8 000-25 000 PLN | 2-4 tygodnie | Dedykowana osoba |
| AWS Lambda + SQS + S3 | 1 500-3 000 PLN | 1-2 tygodnie | Średni |
| FLAME | 800-1 000 PLN | 1-2 dni | Zero |
Większy serwer to najprostsze rozwiązanie, ale nie skaluje się i nadal blokuje system przy peak load. Kubernetes rozwiązuje problem, ale kosztuje więcej niż sam system. Lambda wymaga przepisania kodu i generuje vendor lock-in. FLAME daje elastyczność Kubernetesa za cenę niższą niż większy serwer.
Jak zacząć - 15 minut do pierwszego FLAME.call
1. Dodaj zależność
# mix.exs
defp deps do
[
{:flame, "~> 0.5"}
]
end2. Dodaj pool do supervision tree
# lib/my_app/application.ex
children = [
# ...istniejące children...
{FLAME.Pool,
name: MyApp.HeavyWorkRunner,
min: 0,
max: 5,
max_concurrency: 3,
idle_shutdown_after: 30_000}
]3. Opakuj ciężki kod
# Przed:
def generate_report(params) do
MyApp.Reports.build(params) # 30 sekund CPU na głównym serwerze
end
# Po:
def generate_report(params) do
FLAME.call(MyApp.HeavyWorkRunner, fn ->
MyApp.Reports.build(params) # 30 sekund CPU na runnerze
end)
end4. Skonfiguruj backend
# config/dev.exs - lokalne wykonanie
config :flame, :backend, FLAME.LocalBackend
# config/prod.exs - Fly.io
config :flame, :backend, FLAME.FlyBackend
# lub Kubernetes
# config :flame, :backend, FlameK8sBackend5. Deploy
mix deps.get
fly deploy # lub kubectl apply, lub docker pushW dev wszystko działa lokalnie - FLAME.call wykonuje funkcję w tym samym procesie. Na produkcji - na elastycznych runnerach. Zero różnicy w kodzie.
Dlaczego to jest możliwe tylko na BEAM
FLAME nie jest portywalny do dowolnego języka. Działa, bo BEAM ma trzy unikalne cechy:
Erlang distribution - węzły BEAM łączą się w klaster i komunikują się natywnie. Proces na węźle A wysyła wiadomość do procesu na węźle B dokładnie tak samo jak do procesu lokalnego. Runner jest po prostu kolejnym węzłem w klastrze.
Closures serializują się natywnie - gdy piszesz FLAME.call(pool, fn -> ... end), ta anonimowa funkcja jest serializowana przez Erlang term format i wysyłana do runnera. Zmienne zamknięte w closure jadą razem. W Javie czy Go serializacja closures to koszmar - w BEAM to operacja wbudowana.
Supervision trees - runner jest supervised. Jeśli padnie - parent wie o tym natychmiast (node monitor). Jeśli parent padnie - runner może zakończyć bieżącą pracę i się wyłączyć. Graceful shutdown, health check, failover - wszystko jest częścią OTP, nie dodatkową infrastrukturą.
Istnieje proof-of-concept FLAME dla JavaScriptu, ale wymaga serializacji argumentów do JSON, osobnego pliku z kodem funkcji i zewnętrznego zarządzania poolem. To nie jest ten sam wzorzec - to Lambda z lepszą ergonomią.
Twoja aplikacja Phoenix spowalnia przy ciężkich operacjach - raportach, importach, przetwarzaniu plików? Porozmawiajmy - pokażemy, jak FLAME odciąży Twój serwer bez dodatkowej infrastruktury i bez przepisywania kodu.