LabHub

블로그

모던 Elixir & Phoenix 2026 — Elixir 1.18 / Phoenix 1.8 / LiveView 1.0 / Oban / Broadway / Bumblebee / Nx / Livebook 심층 가이드

한국어English日本語

프롤로그 — 30년 묵은 VM이 2026년에 다시 뜨는 이유

Elixir는 새로운 언어가 아니다. 2012년 José Valim이 Erlang/OTP 위에 사람이 쓸 만한 문법을 얹어 시작했다. 그리고 그 Erlang은 1986년 에릭슨에서 전화 교환기를 위해 태어났다 — 즉 BEAM(Erlang VM)은 사실상 마흔 살이다.

2026년, 그 마흔 살짜리 VM이 다시 뜨고 있다. 이유는 한 줄로 말할 수 있다.

"동시성·분산·내고장성을 처음부터 박아둔 VM은 BEAM 하나뿐이고, 멀티코어/실시간/AI 추론 시대가 결국 그쪽 문법을 강제한다."

이 글은 2026년의 Elixir 스택을 처음부터 끝까지 — VM의 액터 모델부터 LiveView, Oban, Broadway, Membrane, Bumblebee, Tidewave, 그리고 한국·일본의 실제 채택 사례까지 — 한 호흡으로 정리한다.


1장 · 왜 BEAM인가 — 액터·소프트 리얼타임·내고장성

먼저 한 가지를 분명히 하자. Elixir가 매력적인 이유의 절반은 언어 그 자체가 아니라 그 아래의 VM이다.

BEAM의 세 가지 핵심.

  1. 경량 프로세스(액터) — 한 노드 안에 수백만 개의 프로세스가 동시에 산다. 각 프로세스는 메모리(기본 ~2KB 힙)와 메일박스를 가진 격리된 단위다. OS 스레드가 아니다.
  2. 선점형 스케줄링 — 스케줄러가 일정 reduction 수마다 프로세스를 강제로 스왑한다. 한 프로세스가 무한 루프를 돌아도 다른 프로세스가 굶지 않는다 — "소프트 리얼타임".
  3. let it crash + supervisor 트리 — 한 프로세스가 죽으면 격리된 단위 안에서만 죽고, 위에 있는 supervisor가 미리 정해진 전략(one_for_one, rest_for_one 등)으로 재시작한다. 방어 코드 대신 회복 코드.

그림으로 보면 이렇다.

                  [Application Supervisor]
                          |
            +-------------+-------------+
            |             |             |
     [DB Pool Sup]   [Web Sup]    [Worker Sup]
            |             |             |
       [Pool Worker]  [Endpoint]   [Job Worker x N]
                          |
                   [LiveView Conn x M]

이 트리 한 장이 Elixir 앱의 운영을 거의 다 설명한다. 한 LiveView 연결이 죽어도 그 연결만 죽고, 다른 사용자는 영향이 없다. DB 풀이 흔들리면 Pool Sup이 재시작한다. 워커 하나가 OOM이 나도 Worker Sup이 새로 띄운다.

기억할 한 줄: "BEAM은 죽지 않는 VM이 아니라, 죽어도 괜찮은 VM이다."


2장 · Elixir 1.18 (2024년 12월) — set-theoretic types 프리뷰

2024년 12월 19일 릴리스된 Elixir 1.18은 그 자체로도 충분히 큰 사건이다.

2.1 set-theoretic types — 점진적 정적 타입의 첫 발

오랫동안 Elixir 사용자가 가장 자주 받은 질문은 "왜 정적 타입이 없냐"였다. 답은 두 가지였다 — (1) Dialyzer 있다 (2) BEAM의 동시성과 hot code reload는 동적 타입과 잘 맞는다. 하지만 그 두 답이 충분치 않다는 걸 코어 팀이 받아들였다.

1.18은 그 첫 번째 답이다. set-theoretic types를 함수 시그니처와 패턴 매칭에 적용하기 시작했다. union(A or B)·intersection(A and B)·negation(not A)을 1급 시민으로 다루는 타입 시스템이다.

# 컴파일러가 자동으로 추론한다 — 어노테이션이 없어도
defmodule Numbers do
  def add(x, y) when is_integer(x) and is_integer(y), do: x + y
  def add(x, y) when is_float(x) and is_float(y), do: x + y
end

# 잘못된 호출은 컴파일 시점에 잡힌다
Numbers.add(1, 2.0)
# warning: incompatible types — integer 와 float 의 union 에 모두 속하지 않음

지금은 아주 한정된 범위(시그니처·패턴 매칭·일부 가드)지만, 로드맵상 1.19~1.20에서 본격적인 타입 어노테이션 문법이 들어온다.

2.2 mix 작업 개선

1.18은 mix 쪽도 손을 봤다. mix test --partitioned가 안정화됐고, mix format이 prettier처럼 import 정렬까지 다룬다. mix deps.tree가 SAT 솔버 진단을 출력해 의존성 충돌의 원인을 알려준다.

2.3 Elixir 버전 정책

Elixir는 1.x를 길게 가져간다. 2.0이 나올 가능성은 거의 없다. 대신 1.x가 매년 12월쯤 마이너 릴리스를 한다 — 1.15(2023), 1.16(2024 초), 1.17(2024 중), 1.18(2024 말), 그리고 1.19가 2025년 12월 예정이다.


3장 · Phoenix 1.8 (2024년 12월) — Scope API, daisyUI 기본

Phoenix는 Elixir의 웹 프레임워크다. Rails 비유로 시작하지만 — 컨트롤러·뷰·라우터의 구조는 비슷하다 — 핵심은 다르다. Phoenix는 LiveView를 기본으로 깔고, Plug 미들웨어 위에 올라가고, PubSub으로 노드 간 메시지를 라우팅한다.

3.1 Scope API — 인증 컨텍스트를 함수 시그니처에 박는다

1.8의 가장 큰 변화는 Scope다. 기존에는 인증된 사용자를 conn.assigns.current_user에서 꺼냈고, 이게 컨트롤러 안에서만 통했다. Scope는 그 사용자(와 그 사용자가 속한 organization, tenant 등)를 첫 인자로 명시적으로 받는다.

defmodule MyApp.Posts do
  alias MyApp.Accounts.Scope

  # 스코프를 첫 인자로 — 인증 컨텍스트가 시그니처에 박힌다
  def list_posts(%Scope{user: user, org: org}) do
    Post
    |> where(org_id: ^org.id)
    |> where(author_id: ^user.id)
    |> Repo.all()
  end

  def create_post(%Scope{} = scope, attrs) do
    %Post{}
    |> Post.changeset(attrs)
    |> Ecto.Changeset.put_change(:org_id, scope.org.id)
    |> Repo.insert()
  end
end

이게 왜 중요한가? 컨트롤러뿐 아니라 LiveView, Oban 잡, 백그라운드 작업, GenServer까지 같은 인증 컨텍스트를 일관되게 받는다. 기존에는 LiveView가 mount 시점에 user를 따로 assign 해야 했고, Oban 잡은 args에 user_id를 넣어 다시 fetch 해야 했다.

3.2 daisyUI + Tailwind v4 기본

1.8부터 mix phx.new의 기본 UI 스택이 Tailwind v4 + daisyUI다. Heroicons도 기본. 별다른 설정 없이 즉시 모던한 UI가 떠진다.

# 1.8 기본 템플릿
<.button class="btn btn-primary" phx-click="save">저장</.button>
<.input field={@form[:title]} class="input input-bordered" />

이전엔 Tailwind만 깔고 컴포넌트는 직접 만들어야 했다. 1.8은 daisyUI 컴포넌트 클래스(btn, card, modal, alert 등)를 바로 쓴다.

3.3 새 프로젝트 만들기

mix archive.install hex phx_new
mix phx.new my_app --live   # LiveView 포함
cd my_app
mix ecto.create
mix phx.server

이 네 줄로 LiveView·Tailwind v4·daisyUI·Ecto·Postgres가 다 깔린 프로젝트가 4000번 포트에서 떠 있다.


4장 · LiveView 1.0 — 5년의 베타 끝에 정식 출시

LiveView는 Elixir의 시그니처 기술이다. 2018년 ElixirConf에서 Chris McCord가 데모를 한 이후, 5년 넘게 0.x 베타를 거쳐 2024년 11월 1.0이 정식 출시됐다.

4.1 LiveView가 푸는 문제

기존 SPA(React, Vue) 스택의 비용은 두 가지다.

  1. API 두 번 만들기 — REST/GraphQL 백엔드 + 그걸 호출하는 프런트.
  2. 상태 동기화 — 서버 상태와 클라이언트 상태가 따로. 그 차이가 모든 버그의 근원.

LiveView는 이 둘을 없앤다. 상태는 서버에만 있다. UI는 서버에서 렌더된 HTML이 WebSocket으로 푸시된다. 사용자 이벤트(click, submit, keypress)는 WebSocket으로 서버로 가고, 서버는 상태를 바꾸고 변경된 DOM 일부만 다시 push 한다.

defmodule MyAppWeb.CounterLive do
  use MyAppWeb, :live_view

  def mount(_params, _session, socket) do
    {:ok, assign(socket, count: 0)}
  end

  def handle_event("inc", _params, socket) do
    {:noreply, assign(socket, count: socket.assigns.count + 1)}
  end

  def render(assigns) do
    ~H"""
    <div>
      <h1>카운트: <%= @count %></h1>
      <button phx-click="inc" class="btn btn-primary">+</button>
    </div>
    """
  end
end

이게 끝이다. 100ms 안에 서버 왕복이 일어나고, 변경된 텍스트 노드만 patch 된다. JS는 한 줄도 안 썼다.

4.2 1.0의 핵심 변화

4.3 LiveView가 잘 안 맞는 경우

LiveView가 만능은 아니다.

이런 경우엔 LiveView Hooks로 일부만 클라이언트 JS로 빼거나, 차라리 React로 가는 게 맞다.


5장 · Ecto — DB 레이어의 표준

Ecto는 Elixir의 ORM이 아니라 DB 쿼리·검증·마이그레이션 라이브러리다. ActiveRecord 비교는 표면적이고, 본질은 다르다.

Ecto의 네 가지 모듈.

  1. Repo — DB 풀과 실행 인터페이스. Repo.all, Repo.insert, Repo.transaction.
  2. Schema — 테이블과 Elixir 구조체의 매핑. ActiveRecord와 달리 모델이 아니라 매핑만이다.
  3. Changeset — 입력 검증·변환의 파이프라인. 이게 Ecto의 진짜 무기다.
  4. Query — 컴파일 타임에 검사되는 DSL. SQL 그대로보다 안전.

5.1 Changeset — 진짜 무기

Changeset은 "이 입력을 받아서, 이런 검증을 거쳐, 이런 모양으로 DB에 넣는다"를 한 파이프라인으로 표현한다.

def changeset(post, attrs) do
  post
  |> cast(attrs, [:title, :body, :published_at])
  |> validate_required([:title, :body])
  |> validate_length(:title, min: 3, max: 200)
  |> validate_format(:title, ~r/^[^<>]+$/)
  |> unique_constraint(:slug)
  |> put_change(:slug, slugify(attrs["title"]))
end

이게 단순히 폼 검증이 아니다. 모든 DB 쓰기가 changeset을 거친다. 그래서 비즈니스 규칙 위반은 컴파일 타임이 아니라 데이터 레이어에서 한 곳에서 차단된다.

5.2 Multi — 트랜잭션 빌더

복잡한 트랜잭션은 Ecto.Multi로.

Multi.new()
|> Multi.insert(:post, Post.changeset(%Post{}, attrs))
|> Multi.update(:user, User.bump_post_count(user))
|> Multi.insert(:notification, fn %{post: post} ->
  Notification.new_post(post)
end)
|> Repo.transaction()

각 단계는 이전 단계의 결과를 받을 수 있고, 어디서 실패해도 전체 롤백된다.

5.3 Ecto 3 — 표준 자리잡음

2024~2026 사이 Ecto는 안정기다. 3.x가 사실상 표준이고, 큰 변화 없이 점진적 개선만 일어나고 있다. Postgres 배열·jsonb·LISTEN/NOTIFY, MySQL 8, SQLite — 다 잘 지원한다.


6장 · Oban — 잡 처리, 그리고 Pro 999달러 논란

Oban은 Elixir 진영의 표준 잡 큐다. Postgres 기반이라는 게 핵심이다 — Sidekiq처럼 Redis를 따로 쓰지 않고, 이미 있는 Postgres에 잡 테이블 하나를 더 둔다.

6.1 왜 Postgres 기반인가

# 잡 정의
defmodule MyApp.Workers.EmailWorker do
  use Oban.Worker, queue: :emails, max_attempts: 5

  @impl Oban.Worker
  def perform(%Oban.Job{args: %{"user_id" => user_id, "kind" => kind}}) do
    user = Repo.get!(User, user_id)
    MyApp.Mailer.deliver(user, kind)
    :ok
  end
end

# enqueue — 비즈니스 로직과 같은 트랜잭션
Multi.new()
|> Multi.insert(:user, User.changeset(%User{}, attrs))
|> Oban.insert(:email_job, fn %{user: user} ->
  EmailWorker.new(%{user_id: user.id, kind: "welcome"})
end)
|> Repo.transaction()

6.2 Oban Pro 999달러 논란

Oban 본체는 무료/오픈소스다. 하지만 운영에 자주 필요한 기능들 — Cron, Batch Jobs(N개가 다 끝나면 콜백), Workflow, Smart Engines — 은 Pro에 있다. 2024년 중반 Pro의 단일 라이선스 가격이 연 999달러로 인상되며 커뮤니티가 시끄러웠다.

찬반은 갈렸다.

결과적으로 커뮤니티는 Pro로 가는 곳, OSS 본체만으로 직접 짜는 곳, 그리고 Quantum + 직접 GenServer 조합으로 가는 곳으로 갈렸다. 2026년 시점에서 새 프로젝트는 대부분 그냥 Pro로 간다 — 1년에 999달러는 시니어 한 명의 며칠 값.


7장 · Broadway — Kafka·RabbitMQ·AMQP·SQS 파이프라인

Oban이 "DB 잡 큐"라면 Broadway는 "데이터 파이프라인". 외부 메시지 시스템(Kafka, RabbitMQ, AMQP, AWS SQS, Google PubSub)에서 메시지를 끌어와, 배치 처리하고, 백프레셔를 관리하는 추상이다.

7.1 Broadway가 푸는 문제

스트리밍 데이터 처리에는 보통 다음이 필요하다.

  1. 컨슈머 그룹 — 여러 노드가 동시에 같은 토픽을 나눠 읽는다.
  2. 배치 처리 — 1건씩이 아니라 100건/1000건 단위로 묶어 처리.
  3. 백프레셔 — 다운스트림이 느리면 업스트림에서 안 받는다.
  4. 재시도/dead letter — 실패한 메시지의 격리.

Broadway는 이걸 BEAM의 GenStage 위에 다 얹은 표준이다.

defmodule MyApp.KafkaPipeline do
  use Broadway

  def start_link(_opts) do
    Broadway.start_link(__MODULE__,
      name: __MODULE__,
      producer: [
        module: {BroadwayKafka.Producer, [
          hosts: [localhost: 9092],
          group_id: "my_group",
          topics: ["events"]
        ]},
        concurrency: 1
      ],
      processors: [
        default: [concurrency: 10]
      ],
      batchers: [
        default: [batch_size: 100, batch_timeout: 1_000]
      ]
    )
  end

  @impl true
  def handle_message(_, message, _) do
    # 메시지 1건 변환
    message
  end

  @impl true
  def handle_batch(:default, messages, _, _) do
    # 100건 한 번에 DB INSERT
    Enum.map(messages, &Message.update_data(&1, transformed(&1)))
  end
end

이게 다다. Kafka·RabbitMQ·SQS·PubSub은 producer 모듈만 갈아끼우면 된다.

7.2 사례 — 이벤트 ETL

쇼핑몰에서 주문 이벤트(초당 수천 건)를 Kafka에서 읽어, 풍부화(enrichment)하고, ClickHouse에 적재하는 파이프라인. Broadway로 짜면 한 BEAM 노드에서 producer 1, processor 64, batcher 8로 초당 만 건 가뿐히 처리한다. 노드 추가하면 컨슈머 그룹이 알아서 파티션을 재분배한다.


8장 · Membrane — 오디오/비디오 파이프라인

Membrane은 Software Mansion에서 만든 오디오/비디오 처리 프레임워크다. WebRTC SFU(Jitsi/LiveKit 같은 것)부터 라이브 스트리밍 트랜스코딩, RTSP 카메라 통합, HLS 분절 생성까지 한다.

8.1 왜 BEAM이 미디어 처리에 맞는가

물론 코덱(H.264/VP9/Opus) 자체의 인코딩·디코딩은 C/Rust(Native Implemented Function, NIF)로 빠진다. Membrane은 그 파이프라인을 조립하고, 라이프사이클을 관리하고, 흐름을 제어한다.

8.2 간단 파이프라인

defmodule MyApp.AudioPipeline do
  use Membrane.Pipeline

  @impl true
  def handle_init(_ctx, opts) do
    structure = [
      child(:file_src, %Membrane.File.Source{location: opts.input}),
      child(:mp3_decoder, Membrane.MP3.MAD.Decoder),
      child(:opus_encoder, %Membrane.Opus.Encoder{}),
      child(:rtp, Membrane.RTP.PayloadFormatResolver),
      child(:udp_sink, %Membrane.UDP.Sink{destination: opts.dest})
    ]

    {[spec: structure], %{}}
  end
end

MP3 파일을 디코드해서 Opus로 인코드하고 RTP로 패킷화해 UDP로 보낸다. 노드는 다 별도 프로세스고, 백프레셔가 자동으로 흐른다.

8.3 실제 사례


9장 · Igniter — 코드 변환 도구

Igniter는 비교적 새 프로젝트(2024년)다. Elixir 코드를 안전하게 자동 수정하는 도구로, 라이브러리 설치/업그레이드 시 보일러플레이트를 알아서 박아준다.

9.1 무엇을 푸는가

기존 mix deps.get은 라이브러리만 다운로드한다. 그 라이브러리를 쓰려면 config/config.exs에 설정 넣고, application.ex에 supervisor child를 넣고, router에 plug를 추가하고 — 이걸 다 README 보고 손으로 했다.

Igniter는 이걸 자동화한다. 라이브러리가 Igniter Recipe를 제공하면, mix igniter.install <lib>이 AST 레벨로 안전하게 모든 파일을 수정한다.

mix igniter.install ash_authentication
# config/config.exs 자동 수정
# lib/my_app/application.ex 에 supervisor child 자동 추가
# lib/my_app_web/router.ex 에 라우트 자동 추가

9.2 직접 쓰는 경우

Igniter는 단순 라이브러리 설치뿐 아니라 마이그레이션 도구로도 쓰인다 — 예를 들어 LiveView 0.20에서 1.0으로 갈 때 deprecated API 자동 변환. Phoenix 1.7 → 1.8 마이그레이션 일부도 Igniter recipe로 제공된다.


10장 · Bumblebee — Elixir에서 HF Transformers

여기서부터가 "Elixir도 AI 한다"의 이야기다.

Bumblebee는 Hugging Face Transformers의 Elixir 포팅이다. 같은 모델 아티팩트(safetensors), 같은 토크나이저 설정을 그대로 읽어서 Elixir에서 추론한다.

10.1 BERT 한 줄 추론

{:ok, model} = Bumblebee.load_model({:hf, "bert-base-uncased"})
{:ok, tokenizer} = Bumblebee.load_tokenizer({:hf, "bert-base-uncased"})

serving = Bumblebee.Text.fill_mask(model, tokenizer)
Nx.Serving.run(serving, "The capital of [MASK] is Paris.")
# => [%{token: "France", score: 0.97}, ...]

이게 다다. CPU에서 잘 돈다. GPU 가속을 원하면 백엔드를 EXLA(XLA, JAX의 그것)로 바꾼다.

10.2 백엔드 — Nx → EXLA / Torchx

Bumblebee 자체는 Nx 위에 산다. Nx는 NumPy-like 텐서 API다. 실제 연산은 백엔드가 한다.

# config/config.exs
config :nx, default_backend: EXLA.Backend
config :nx, default_defn_options: [compiler: EXLA]

10.3 Stable Diffusion, Whisper, Llama

Bumblebee가 지원하는 모델군은 꾸준히 늘었다.

10.4 Phoenix와 묶기 — Nx.Serving

Bumblebee의 추론은 Nx.Serving이라는 추상 안에서 돈다. 이게 결정적이다 — Phoenix 앱의 supervisor 트리에 그냥 child로 박으면, 자동으로 배치/큐잉/타임아웃을 처리한다.

# application.ex
children = [
  MyAppWeb.Endpoint,
  {Nx.Serving,
   serving: build_whisper_serving(),
   name: WhisperServing,
   batch_size: 8,
   batch_timeout: 200}
]

# 어디서든 호출
Nx.Serving.batched_run(WhisperServing, audio_input)

10개 동시 요청이 와도 batch_size: 8이 알아서 8개씩 묶어 GPU로 보낸다. GPU 활용률이 올라간다.


11장 · Nx + Axon + Explorer — BEAM 데이터 사이언스

Bumblebee 한 줄 추론을 봤으니, 그 아래의 스택을 보자.

11.1 Nx — NumPy for BEAM

Nx는 텐서 라이브러리. NumPy/PyTorch와 비슷한 API.

import Nx.Defn

defn softmax(t) do
  Nx.exp(t) / Nx.sum(Nx.exp(t))
end

Nx.tensor([1.0, 2.0, 3.0]) |> softmax()
# => Nx.Tensor<f32[3] [0.09, 0.24, 0.67]>

defn숫자 전용 함수다. 일반 Elixir 함수가 아니라 JIT 컴파일된다 — XLA로 GPU/TPU에서 돌고, EXLA로 컴파일된다.

11.2 Axon — 딥러닝 라이브러리

Axon은 Nx 위의 신경망 라이브러리. Keras 스타일의 함수형 API.

model =
  Axon.input("input", shape: {nil, 784})
  |> Axon.dense(128, activation: :relu)
  |> Axon.dropout(rate: 0.2)
  |> Axon.dense(10, activation: :softmax)

# 학습
model
|> Axon.Loop.trainer(:categorical_cross_entropy, :adam)
|> Axon.Loop.metric(:accuracy)
|> Axon.Loop.run(train_data, %{}, epochs: 10)

작은 모델은 BEAM 노드에서 직접 학습한다. 큰 모델은 PyTorch로 학습하고 Bumblebee로 서빙하는 게 일반적이다.

11.3 Explorer — DataFrames, Polars 백엔드

Explorer는 Pandas/Polars의 Elixir 버전. 내부적으로 Polars(Rust)를 NIF로 부른다. 그래서 성능이 좋다.

require Explorer.DataFrame, as: DF

df = DF.from_csv!("sales.csv")

df
|> DF.filter(col("amount") > 100)
|> DF.group_by("region")
|> DF.summarise(total: sum(col("amount")), n: count(col("id")))
|> DF.sort_by(desc: "total")

이걸 Livebook 안에서 시각화하면 Jupyter 비슷한 데이터 분석 환경이 된다.


12장 · Livebook — Elixir 노트북

Livebook은 José Valim이 만든 Elixir용 Jupyter다. 단, Jupyter보다 두 가지가 다르다.

  1. 협업이 1급 — 한 노트북을 여러 사람이 동시에 편집한다. Google Docs 비슷.
  2. 버전 관리가 깨끗 — 노트북이 .livemd(markdown)다. git diff가 사람이 읽을 수 있다.
# Mac/Linux 한 줄 설치
mix escript.install hex livebook
livebook server
# http://localhost:8080

12.1 Livebook이 잘 맞는 곳

12.2 Smart Cells

Smart Cell은 UI 폼으로 코드를 만드는 셀이다. 차트, DB 쿼리, 신경망 시각화 등이 폼 입력만으로 코드 셀이 생성된다.

[Smart Cell: SQL Query]
  Connection: prod_replica
  Query: SELECT * FROM users WHERE created_at > $1
  Variables: cutoff_date = ~D[2026-01-01]

→ 셀 아래에 자동으로 Ecto/Postgrex 코드 생성

13장 · Tidewave (2025년 9월, José Valim) — Elixir AI 추론 플랫폼

2025년 9월, José Valim과 Dashbit 팀이 Tidewave를 공개했다. "BEAM 위의 LLM 서빙 플랫폼"이다.

13.1 무엇을 푸는가

LLM 서빙에는 다음이 필요하다.

  1. 동시 추론 요청 수백~수천 개를 GPU에 잘 묶기 — vLLM이 푸는 문제.
  2. 스트리밍 응답 — 토큰 단위로 클라이언트에 push.
  3. 세션/상태 관리 — 멀티턴 대화, 컨텍스트.
  4. 장애 격리 — 한 사용자의 잘못된 요청이 다른 사용자에게 영향 없도록.
  5. 분산 — 여러 GPU 노드에 라우팅.

이 5가지 중 (3)(4)(5)는 BEAM이 30년 전부터 잘하던 것이다. 나머지 (1)(2)도 Nx.Serving + Phoenix 채널 위에 자연스럽게 얹힌다.

13.2 아키텍처

Tidewave는 대략 이렇게 생겼다.

              [Phoenix Endpoint]
                      |
              +-------+-------+
              |               |
        [HTTP /chat]    [WS /stream]
              |               |
              v               v
        [Session Sup]   [Stream Channel]
              |               |
              v               v
        [Session GenServer x N]
              |
              v
    +---------+---------+
    |                   |
[Nx.Serving]      [Tool Runtime]
    |                   |
    v                   v
[GPU Workers]   [DB / HTTP / RAG]

각 사용자 세션이 별도 GenServer다. 한 세션이 꼬여도 다른 세션에 영향 없다. Nx.Serving이 GPU 배치를 관리한다. 툴 호출은 별도 supervisor 트리.

13.3 vLLM 대비

Tidewave는 vLLM과 정면 경쟁한다기보단 다른 곳을 푼다. vLLM은 GPU 효율 1등. Tidewave는 GPU 효율은 vLLM에 못 미치지만, 운영성·세션 관리·장애 격리·분산이 BEAM 강점.

선택 기준은 단순하다.


14장 · BEAM 운영 — Observer, recompile, hot reload

Elixir 앱을 운영해 본 사람만 아는 즐거움이 몇 가지 있다.

14.1 Observer — 라이브 노드 들여다보기

iex --sname dev --remsh my_app@hostname
iex> :observer.start()

GUI가 뜬다. 프로세스 트리, 메시지 큐 길이, 메모리 사용량, CPU 스케줄러 상태 — 다 라이브로 본다. 한 프로세스의 메일박스가 비정상적으로 길면 거기서 병목을 찾는다.

14.2 IEx 원격 셸

iex --sname remote@host --cookie SECRET --remsh prod@host

프로덕션 노드에 원격 IEx 셸을 띄운다. 거기서 Repo.get(User, 1), Phoenix.PubSub.broadcast(...) 다 된다. 운영 디버깅의 끝판왕.

14.3 Hot code reload

iex> r MyApp.SomeModule

운영 중인 노드에 모듈을 재컴파일/재로드한다. 전체 재시작 없이 한 함수만 바꿔도 즉시 반영. 단, 프로덕션에서 이걸 쓰는 건 신중히. 보통은 새 릴리스를 배포해 rolling restart 한다.


15장 · 한국·일본의 Elixir 채택

Elixir는 거대 기업의 메인 스택은 아니지만, 특정 영역에서 강하다.

15.1 한국

한국에서 Elixir의 보급은 더디다. 1순위 이유는 채용 풀이 작다는 것. JVM/Kotlin/Node 풀에 비해 한국어 자료도 적다. 하지만 한 번 적용한 팀은 잘 이탈하지 않는다.

15.2 일본

일본은 Erlang 전통이 일찍부터 있었다(NTT·Ericsson 일본 영향). 그래서 Elixir로의 이행이 한국보다 자연스럽다. 일본어 자료(Qiita, Zenn)도 한국어보다 풍부.

15.3 전 세계


16장 · 누가 Elixir를 골라야 하나

마지막 장. 결정 매트릭스.

Elixir/Phoenix가 잘 맞는 경우

  1. 실시간 동시성이 핵심 — 채팅, 게임, 라이브 스트림, 협업 도구, 트레이딩.
  2. 장기간 연결을 많이 유지해야 — WebSocket 수만 개, IoT 디바이스 수만 대.
  3. 소프트 리얼타임·내고장성이 요구사항 — 통신, 결제 알림, 미디어.
  4. 작은 팀이 풀스택으로 빠르게 — LiveView 한 추상으로 백+프 동시 처리.
  5. 메시지 파이프라인 — Broadway로 Kafka/SQS/RabbitMQ ETL.

Elixir가 잘 안 맞는 경우

  1. CPU-bound 무거운 단일 계산 — JS 엔진/네이티브 코드보다 BEAM이 느리다. NIF으로 빠지긴 하지만 단순한 건 Go/Rust가 낫다.
  2. 거대한 ML 학습 메인 스택 — PyTorch 생태계가 압도적. Elixir는 서빙·MLOps 보조에 가깝다.
  3. 풍부한 라이브러리 생태계가 핵심 — npm/PyPI 같은 양은 없다. Hex 패키지 수는 1만 단위.
  4. 채용/팀 구성이 1순위 제약 — 한국에서 Elixir 시니어를 단기간에 5명 뽑기는 어렵다.

한 줄 가이드

"동시 연결 1만 개 이상, 또는 실시간이 핵심, 또는 운영 안정성이 1순위라면 Elixir를 고민할 가치가 있다. CRUD 위주 SaaS라면 Phoenix는 매력적이지만 Rails/Django/Next로도 충분히 잘 된다."


17장 · 참고 / References

댓글

아직 댓글이 없습니다.

로그인하면 댓글을 쓸 수 있습니다