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 normalnie

FLAME zarządza poolami runnerów. Każdy pool to grupa tymczasowych maszyn, na których działa kopia aplikacji. Gdy przychodzi zadanie, FLAME:

  1. Sprawdza, czy w poolu jest wolny runner
  2. Jeśli tak - wykonuje funkcję na nim
  3. Jeśli nie i nie osiągnięto limitu - stawia nowy runner (nowa maszyna z Docker image)
  4. Runner łączy się z klastrem BEAM w ~3 sekundy
  5. Funkcja się wykonuje, wynik wraca do callera
  6. Runner czeka na kolejne zadanie
  7. Po idle_shutdown_after milisekund 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ę lokalnie

Zmienne 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 runnerze

FLAME.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
end

To 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.LocalBackend

W 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:

BackendStatusInfrastrukturaCold start
FLAME.FlyBackendOficjalnyFly.io Machines~3s
FlameK8sBackendCommunity (Hex)Kubernetes~10-30s (zależy od klastra)
FLAME.LocalBackendWbudowanyLokalnie (dev/test)0s
CustomBehavior do implementacjiDowolna chmuraZależ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

LimitAWS LambdaFLAME
Timeout15 minutBrak (runner żyje dowolnie długo)
RAM10 GBTyle ile ma maszyna
Dysk10 GB (/tmp)Pełny dysk maszyny
Rozmiar paczki250 MB (deployment)Brak (Docker image)
Połączenia siecioweOgraniczoneBez ograniczeń
Stan między wywołaniamiBrak (stateless)Tak (runner utrzymuje stan)
Dostęp do bazyPrzez VPC + connection poolerNatywny (ta sama aplikacja)
WebSocketNieTak (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ładnikAWS LambdaFLAME (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 / konfiguracja20-40h jednorazowo1-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")
end

Przetwarzanie 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ęć...")}
end

Zwróć 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)
end

Koszt 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
end

Lambda 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)
end

Runner 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

WymiarFLAMEKubernetes HPAAWS Lambda
Zmiana w kodzie1 linia (FLAME.call)Zero (ale YAML)Przepisanie na handler
Infrastruktura dodatkowaZeroK8s cluster, registry, monitoringSQS, S3, API GW, IAM
Cold start~3s (Fly.io)30-60s (nowy pod)1-5s (zależy od języka)
Scale to zeroTakTak (KEDA)Tak
Limit czasuBrakBrak15 minut
Stan między wywołaniamiTak (runner żyje)Tak (pod żyje)Nie
Dostęp do bazyNatywnyNatywnyVPC + pooler
Vendor lock-inNiski (zmiana backendu)Średni (K8s API)Wysoki (AWS SDK)
Koszt DevOpsMinimalnyWysokiŚredni
YAML/Terraform0 plików5-15 plików3-8 plików
Czas wdrożenia1-2 godziny1-3 tygodnie2-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):

ElementKoszt
Serwer aplikacyjny (8 CPU, 16 GB)1 200 PLN/mies.
Problem: raporty blokują systemUtracona produktywność: ~20 000 PLN/rok
Problem: importy spowalniają UIFrustracja zespołu: niepoliczalna

Z FLAME (serwer + elastyczne runnery):

ElementKoszt
Serwer aplikacyjny (4 CPU, 8 GB)600 PLN/mies.
FLAME runnery (scale-to-zero, avg 2-3h/dzień)200-400 PLN/mies.
Razem800-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ązanieKoszt infra/mies.Czas wdrożeniaDevOps
Większy serwer (16 CPU, 32 GB)2 400 PLN1 dzieńZero
Kubernetes + HPA8 000-25 000 PLN2-4 tygodnieDedykowana osoba
AWS Lambda + SQS + S31 500-3 000 PLN1-2 tygodnieŚredni
FLAME800-1 000 PLN1-2 dniZero

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"}
  ]
end

2. 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)
end

4. 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, FlameK8sBackend

5. Deploy

mix deps.get
fly deploy  # lub kubectl apply, lub docker push

W 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.