Skip to content
Published on

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

Authors

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

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

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

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

  • Elixir 1.18(2024년 12월) — set-theoretic types의 첫 프리뷰. 점진적 정적 타입의 길이 열렸다.
  • Phoenix 1.8(2024년 12월) — Scope API로 인증 컨텍스트를 함수 시그니처에 박았고, daisyUI/Tailwind v4가 기본이 됐다.
  • LiveView 1.0(2024년 11월) — 5년의 베타 끝에 정식 1.0. 서버 렌더 HTML과 실시간 양방향 통신이 한 추상 안에 들어왔다.
  • Bumblebee + Nx + Axon — Hugging Face Transformers를 Elixir에서 그대로 돌리고, GPU 가속까지 받는다.
  • Tidewave(2025년 9월) — José Valim이 공개한 Elixir용 AI 추론 플랫폼. BEAM 위에서 LLM 서빙을 한다.

이 글은 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의 핵심 변화

  • Streams — 대량 리스트의 효율적 갱신. 추가/삭제/이동을 서버에서 명시하고, 클라이언트는 DOM 키 기반으로만 처리한다. 무한 스크롤 뉴스피드를 메모리 폭발 없이 만든다.
  • Async assignsassign_async(:user, fn -> ... end)로 비동기 데이터 로딩이 1급 시민이 됐다. Suspense 비슷한 UX.
  • Function components + slots — React의 children prop과 같은 슬롯 패턴이 안정화됐다.
  • JS Commands — 단순 토글/숨김/포커스는 서버 왕복 없이 JS.toggle("#menu") 같은 클라이언트 사이드 명령으로 처리.

4.3 LiveView가 잘 안 맞는 경우

LiveView가 만능은 아니다.

  • 오프라인 우선 PWA — WebSocket 끊기면 못 쓴다.
  • 극단적 인터랙티브 UI — 60fps 드래그/스크럽처럼 ms 단위 응답이 필요한 곳은 클라이언트 사이드가 더 낫다.
  • CDN 끝단 렌더링 — 서버 상태가 필요하므로 edge에 뿌리기 어렵다.

이런 경우엔 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 기반인가

  • 트랜잭션 한 번에 비즈니스 데이터 + 잡 enqueue가 같이 커밋된다. Sidekiq는 "Redis에 큐 넣고 DB에 데이터 저장" 사이에 실패하면 데이터 따로/잡 따로가 된다. Oban은 둘 다 같은 트랜잭션.
  • 운영 도구가 SQL이다. 잡 상태를 쿼리로 직접 본다. 별도 어드민 UI가 필수가 아니다.
  • 백업·복제·HA가 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달러로 인상되며 커뮤니티가 시끄러웠다.

찬반은 갈렸다.

  • 찬성 — Oban 메인테이너 한 명이 풀타임으로 라이브러리에 매달려야 BEAM 진영에 이 정도 품질의 잡 큐가 유지된다. 999달러는 시니어 시간당 1~2시간 값.
  • 반대 — Sidekiq Pro도 219달러/연이다. 999달러는 너무 비싸고, Pro에 있는 기능 중 일부는 오픈소스로 가야 했다.

결과적으로 커뮤니티는 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 실제 사례

  • Jellyfish — Membrane이 만든 WebRTC SFU 서버. Zoom 같은 다자간 회의를 BEAM 위에서 돌린다.
  • Veedo — Membrane 기반 라이브 스트리밍 플랫폼.

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다. 실제 연산은 백엔드가 한다.

  • EXLA — Google XLA를 통한 CPU/GPU/TPU. 가장 빠른 게 많다.
  • Torchx — LibTorch(파이토치 코어)를 통한 CPU/GPU. PyTorch 모델 호환성이 좋다.
# config/config.exs
config :nx, default_backend: EXLA.Backend
config :nx, default_defn_options: [compiler: EXLA]

10.3 Stable Diffusion, Whisper, Llama

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

  • Stable Diffusion XL — 텍스트 → 이미지.
  • Whisper — 음성 → 텍스트. 라이브 자막에 자주 쓴다.
  • Llama 3, Mistral, Gemma — 7B~70B LLM. 양자화로 노트북에서도 돈다.
  • CLIP — 이미지/텍스트 임베딩.

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이 잘 맞는 곳

  • 데이터 탐색 — DB 쿼리, Explorer, Vega-Lite 차트.
  • ML 추론 실험 — Bumblebee 모델을 노트북에서 띄워보고, 하이퍼파라미터 조정.
  • 운영 노트북 — "프로덕션 DB에 이런 쿼리 한 번 돌려야 함"을 검토 가능한 형태로.
  • 튜토리얼/교재.livemd는 실행 가능한 글이다.

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 강점.

선택 기준은 단순하다.

  • 단일 노드에서 짧은 응답을 최대 처리량으로 — vLLM.
  • 다중 세션·장시간 대화·복잡 툴 호출·고가용성 — Tidewave.

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 한국

  • 토스(Toss) — 핀테크 메인 스택은 Kotlin/JVM이지만, 일부 실시간 시스템(알림, 채팅, 일부 결제 워크플로)에 Elixir/Phoenix가 들어가 있다고 알려져 있다.
  • BabyDragon(베이비드래곤) — 한국 모바일 게임 회사. Elixir/Phoenix를 게임 백엔드와 매칭 서버에 광범위하게 쓴다.
  • Banksalad — 가계부 앱. 초기에 일부 백엔드를 Elixir로 했다는 자료가 있다.
  • 개인 개발자/스타트업 — 실시간 채팅·협업·게임 백엔드를 단일 BEAM 노드로 처리하는 작은 팀들이 꾸준히 있다.

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

15.2 일본

  • ドリコム(Drecom) — 모바일 게임 회사. 게임 서버 일부에 Elixir/Phoenix.
  • DMM — 일부 실시간 시스템.
  • 任天堂(Nintendo) — 공식 발표는 적지만, 일부 온라인 서비스의 백엔드에 BEAM 계열이 들어가 있다고 알려져 있다(Erlang 포함).
  • Cookpad — Ruby 본진이지만 일부 백엔드 실험에 Elixir를 본 자료가 있다.
  • 사이버에이전트(CyberAgent) — 광고/AdTech의 일부 실시간 부분.

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

15.3 전 세계

  • Discord — Erlang/Elixir 메시지 라우팅. 한 채널에 수백만 동시 접속을 BEAM 클러스터로 처리.
  • WhatsApp — Erlang. 메타 인수 전까지 작은 팀이 수억 사용자를 운영한 전설.
  • Pinterest — 일부 알림/SMS 시스템.
  • Heroku — Routing layer를 한때 Erlang으로.
  • Bleacher Report — 트래픽 스파이크 대응을 Elixir 하나로.

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