본문으로 건너뛰기
5분 AI.
GITHUB TRENDING ELIXIR DAILY #7 딥다이브 · 2026-10-23 분석

voyager 딥다이브
— 실행 중인 BEAM의 속을 화면과 AI로 들여다보기

관측 앱·MCP 서버

빠른 판단

무엇
Erlang·Elixir 노드의 프로세스·감독 트리·메모리·ETS를 UI와 MCP로 탐색하는 BEAM 관측 도구
이럴 때
서비스 지연·메모리 문제를 조사하며 실행 중인 노드 상태를 사람이 보고 AI 도구에도 같은 관측값을 줄 때
입력
접근 권한이 있는 BEAM 노드 이름·cookie와 관측 요청
결과
프로세스·메시지함·감독 트리·ETS·메모리 상태
Voyager는 Erlang·Elixir 프로그램이 실행되는 BEAM 노드에 연결해 프로세스, 메모리, 감독 트리, ETS 테이블을 조사하는 데스크톱 도구입니다. Elixir·Phoenix LiveView로 만든 화면을 Tauri로 감싸고, 같은 조회 기능을 MCP로 코딩 에이전트에 제공합니다. 이 글에서는 커밋 39bdbae를 따라 연결 상태 관리, 원격 조회 비용, Erlang 도우미 모듈 주입, 비동기 화면 갱신을 배웁니다. (software-mansion/voyager · ⭐106 · 포크 7 · 주 언어 Elixir · 2026-10-23 GitHub 기준)
목차
  1. 프로젝트 한줄 요약
  2. 왜 주목받는가
  3. 기술 스택 전체 지도
  4. 아키텍처 심화 분석
  5. 디렉토리 구조 해부
  6. 학습 포인트 (기술별)
  7. 하드웨어 / 시스템 요구사항
  8. 직접 해볼 수 있는 실습 과제
  9. 관련 기술 심화 학습 로드맵
  10. 핵심 키워드 사전
  11. 참고 링크

1프로젝트 한줄 요약

실행 중인 프로그램의 내부 상태를 읽기 쉬운 형태로 바꾸는 관찰 도구

웹 서비스의 응답이 느려졌다고 가정해 봅시다. 소스 코드만 읽으면 어떤 함수가 있을지는 알 수 있지만, 지금 어떤 프로세스가 메모리를 많이 쓰는지, 누구의 메시지함이 밀리는지는 알 수 없습니다. Voyager는 코드에 적힌 설계와 실제로 실행 중인 시스템 사이의 간격을 보여 주는 도구입니다.

노드 이름과 cookie로 연결하면 노드 정보, 감독 트리, 프로세스 목록·상세, ETS 목록·내용을 탐색할 수 있습니다. 화면의 진입점은 lib/voyager_web/router.ex이고, 원격 연결은 Voyager.NodeSession이 관리합니다. 프레임워크 전용 플러그인이 아니라 Erlang distribution을 사용하므로 README는 Erlang·Elixir·Gleam 시스템을 대상으로 설명합니다.

용어부터
BEAM · 노드 · 프로세스
BEAM은 Erlang과 Elixir 코드가 실행되는 가상 머신입니다. 노드는 네트워크에서 이름으로 구분되는 BEAM 실행 인스턴스이고, 프로세스는 그 안에서 메시지를 주고받으며 독립적으로 일하는 가벼운 실행 단위입니다. 여기서 프로세스 수 1만 개는 운영체제 프로그램 1만 개를 띄운다는 뜻이 아닙니다.
한 문장 비유

소스 코드가 공장 설계도라면, Voyager는 가동 중인 공장의 계기판입니다

설계도는 기계가 어떻게 연결되어야 하는지 설명합니다. 계기판은 지금 어느 기계에 일이 몰렸는지 보여 줍니다.

감독 트리는 조직도, 메시지 큐는 각 작업자의 대기함, ETS는 여러 작업자가 함께 읽는 메모리 보관함에 가깝습니다.

1-1. 사용자가 보는 기능과 코드의 대응

사용자 질문화면·도구읽을 소스
노드가 얼마나 바쁜가?노드 정보 / node_infolib/voyager/services/node_info.ex
누가 누구를 감독하는가?감독 트리lib/voyager/services/supervision_tree/walker.ex
어느 프로세스가 큰가?프로세스 / process_listlib/voyager/services/process_list.ex
이 프로세스 안에는 무엇이 있나?프로세스 상세 / process_infolib/voyager/services/process_info.ex
메모리 테이블에는 무엇이 있나?ETS / ETS 관련 MCP 도구lib/voyager/services/ets/fetch.ex
이 글의 학습 목표
작은 분산 시스템을 완성하는 방법을 배웁니다

언어 문법을 아는 데서 한 걸음 더 나아가, 연결·실패·시간 제한·응답 크기·화면 갱신을 함께 설계하는 법을 살펴봅니다. Elixir를 처음 접한다면 10절의 사전을 옆에 두고 읽어도 좋습니다.

2왜 주목받는가

트렌딩 순위와, 코드에서 읽히는 매력을 구분해서 보기

2026-10-23 선정 기준은 GitHub Trending Elixir Daily #7입니다. 같은 날짜의 GitHub 수치는 스타 106개, 포크 7개이고 최신 릴리스는 2026-10-20의 v0.2.0입니다. 순위나 누적 스타만으로 유입 경로·당일 증가량·흥행 원인을 확정할 수는 없습니다. 아래의 주목 요인은 README의 제품 방향과 구현을 바탕으로 한 해석입니다.

2-1. 화면 사용자와 코딩 에이전트가 같은 런타임을 봅니다

MCP 도구는 소스 검색 결과 대신 현재 연결된 노드의 실제 값을 돌려줍니다. lib/voyager/mcp/server.ex에는 6개 도구가 등록되어 있고, process_list는 화면에서도 쓰는 Voyager.Services.ProcessList.top/7을 호출합니다. 사람의 화면과 AI의 도구가 같은 조회 서비스를 공유한다는 점이 이 저장소의 중요한 제품 설계입니다.

예를 들어 AI가 메모리 문제를 조사할 때, 먼저 process_list로 후보를 좁히고 process_info로 한 프로세스를 더 자세히 볼 수 있습니다. 다만 도구가 관측값을 전달한다고 해서 AI의 원인 분석까지 맞는 것은 아닙니다. 순간값을 보고 추정한 원인은 여러 시점의 값과 코드 흐름으로 다시 검증해야 합니다.

2-2. 대상 앱에 의존성을 추가하지 않는 연결 방식

README의 “대상 노드에 설정이 필요 없다”는 설명은 대상 앱에 Voyager 패키지를 추가할 필요가 없다는 뜻으로 읽어야 합니다. 실제로는 분산 노드 이름, 올바른 cookie, 네트워크 접근, OTP 27 이상, 원격 컴파일러가 필요합니다. 연결 시 도우미 코드를 메모리에 올리므로 대상 런타임에 아무 변화도 없는 관찰은 아닙니다.

2-3. 익숙한 도구와 비교하면 무엇이 다른가?

도구주요 접근 방식Voyager를 공부할 이유
Erlang ObserverOTP의 그래픽 관찰 도구. 감독 트리·프로세스·ETS와 추적 기능을 제공비슷한 런타임 정보를 LiveView·Tauri·MCP라는 구성으로 다시 제품화한 방법을 비교할 수 있음
Phoenix LiveDashboardPhoenix 앱에 연결하는 웹 대시보드. 분산 노드 사이의 조회도 지원대상 앱에 대시보드를 추가하는 대신, 별도 데스크톱 앱에서 연결하는 흐름을 배울 수 있음
IEx / 원격 셸명령으로 런타임을 자유롭게 조사. 사용자가 조회·가공 코드를 작성반복 명령을 입력 폼, 제한된 서비스 API, 표·그래프로 바꾸는 과정을 볼 수 있음
Voyager분산 연결 또는 SSH 터널 + 로컬 화면 + 내장 MCP한 번의 관측 기능을 사람과 에이전트 양쪽에 제공하는 구조가 핵심

비교 근거는 Observer 공식 문서와 LiveDashboard 공식 문서입니다. Observer에도 감독 트리와 ETS가 있고, LiveDashboard도 분산 노드를 지원합니다. 따라서 Voyager의 장점은 기존에 없던 BEAM 정보를 발명했다는 데보다, 연결과 탐색·MCP를 한 제품에 묶은 데 있습니다.

기능 범위를 정확히
보이는 메뉴가 모두 완성된 기능은 아닙니다

lib/voyager_web/router.ex는 추적, 소켓, 포트, 차트, 메모리 할당기 화면을 ComingSoon.*Live에 연결합니다. 반면 Erlang 도우미에는 trace_start/3 같은 함수가 이미 있습니다. 내부 구현의 존재와 사용 가능한 제품 기능은 구분해야 합니다.

3기술 스택 전체 지도

선언한 버전 범위와 실제 잠금 버전을 함께 읽기

의존성 파일의 ~>·^는 허용 범위이고 잠금 파일은 해결된 구체 버전입니다. 아래 값은 모두 커밋 39bdbae 기준이며, 지금 배포된 최신 패키지 버전을 뜻하지 않습니다. 개발 환경을 재현하려면 설명 문장보다 설정 파일과 잠금 파일을 함께 읽어야 합니다.

3-1. 백엔드: Phoenix가 화면과 도메인 기능을 함께 담당합니다

기술선언 → 잠금 버전역할·출처
Elixir / ErlangElixir ~> 1.20
도구 고정: 1.20.2-otp-29 / OTP 29.0.2
mix.exs, .tool-versions. Voyager 자체를 개발하는 환경
Phoenix~> 1.8.7 → 1.8.15라우터·웹 엔드포인트. mix.exs, mix.lock
Phoenix LiveView~> 1.2.8 → 1.2.12서버의 상태를 바탕으로 화면을 갱신. 같은 두 파일
Bandit~> 1.12 → 1.13.1Phoenix와 별도 MCP 엔드포인트의 HTTP 서버
Ecto SQL / SQLite~> 3.13 → 3.14.0
ecto_sqlite3 >= 0.0.0 → 0.25.0
로컬 설정·접속 이력 저장. lib/voyager/repo.ex는 SQLite3 어댑터
Anubis MCP~> 2.0 → 2.1.0MCP 서버, 도구 스키마, Streamable HTTP 전송
Cloak / Cloak Ecto~> 1.1 → 1.1.4
~> 1.3 → 1.3.0
로컬 DB에 저장하는 비밀값 암호화
Req~> 0.7 → 0.7.5HTTP 요청. 텔레메트리 내보내기 등에 사용
OTP 기본 구성:logger, :runtime_tools, :sshmix.exs의 extra_applications. 원격 조사와 SSH에 활용
버전 읽기
개발 OTP 29와 대상 OTP 27+는 서로 다른 조건입니다
.tool-versions는 Voyager를 만드는 쪽의 도구를 고정합니다. lib/voyager/agent.ex의 @min_otp 27은 관찰할 대상 노드의 최소 조건입니다. 빌드 머신과 원격 서버가 반드시 같은 OTP 버전이어야 한다는 뜻은 아닙니다.

3-2. 프론트엔드: LiveView와 필요한 부분의 JavaScript

기술실제 설정값쓰임새
LiveView + HEEx화면 대부분이 lib/voyager_web/live/**/*.ex의 ~H 템플릿서버 측 상태·이벤트 처리와 HTML 렌더링
Cytoscape / dagrecytoscape 3.34.4
cytoscape-dagre ^4.0.1 → 4.0.1
dagre ^0.8.5
감독 트리의 그래프 표시·배치. assets/package.json, 잠금 파일
DaisyUI^5.7.50 → 5.7.50UI 컴포넌트·테마. assets/css/app.css와 npm 의존성
Tailwind CSSCLI 4.1.12config/config.exs의 실제 CSS 도구 버전
esbuild실행 도구 0.25.4
Mix 래퍼 잠금 0.10.0
JS 번들링. config/config.exs, mix.lock
TypeScript^7.0.2 → 7.0.2JS 타입 검사 도구. 앱의 훅 구현 파일은 .js
Node.js.tool-versions: 26.4.0
npm engines.node: 24.16.0
assets/package.json과 e2e/package.json은 모두 24.16.0을 지정해 도구 고정값과 다름

React나 Vue를 사용하는 앱으로 읽으면 흐름을 놓치기 쉽습니다. assets/js/app.js는 LiveSocket('/live', ...)을 연결하고 훅을 등록합니다. 감독 트리처럼 브라우저에서 직접 다루기 편한 부분만 SupervisionTree 훅과 Cytoscape가 담당합니다. Node.js 버전 불일치는 설정에서 확인되는 사실이며, 어느 조합이 실제로 오류를 내는지는 별도 검증이 필요합니다.

3-3. 데스크톱·빌드·검증 도구

영역버전·설정소스 위치
Rust / TauriRust 1.96.0, edition 2024, Tauri 크레이트 범위 2.tool-versions, rel/app/src-tauri/Cargo.toml
Tauri CLI2.8.0 정확히 고정docs/development.md, .github/workflows/build-apps.yml
ElixirKitRust 쪽 Git revision 16d19dd3fe7a…rel/app/src-tauri/Cargo.toml. Rust와 Elixir 프로세스 사이의 연결
Mix 릴리스mix release voyager와 Tauri 패키징mix.exs의 releases/0, rel/app/tauri.sh
단위·통합 검사ExUnit, Mox 잠금 1.3.2, Credo, Dialyzer, Rust 검사.github/workflows/ci-check.yaml
브라우저 검사Playwright ^1.60.0 → 1.60.0
Chromium·Firefox, 포트 4001
e2e/package.json, e2e/playwright.config.ts

mix.exs의 assets.build는 정적 파일 정리, 포맷, 폰트 복사, 컴파일, Tailwind·esbuild 실행을 묶습니다. assets.deploy는 여기에 phx.digest를 더합니다. mix precommit도 단순 검사 이름과 달리 포맷·의존성 잠금 정리처럼 파일을 바꾸는 작업을 포함합니다. 명령 이름만 보고 “읽기 전용 검사”라고 판단하지 않는 습관을 배울 수 있습니다.

구성 비유

Phoenix는 관찰실의 업무 담당자, LiveView는 계속 갱신되는 전광판, Tauri는 데스크톱 창과 메뉴를 붙이는 외피입니다. Rust가 모든 조회 로직을 처리하거나 JavaScript가 대상 노드에 직접 접속하는 구조는 아닙니다.

4아키텍처 심화 분석

연결부터 결과 표시까지, 네 가지 설계 패턴으로 읽기

4-0. 시스템 전체 지도

[사용자 컴퓨터] Tauri / Rust ── ElixirKit ── Voyager.Application │ │ │ WebView ├── Repo / SQLite: 설정·접속 이력 ▼ ├── Vault: 저장할 비밀값 암호화 Phoenix Endpoint ├── NodeSession: 활성 연결 1개 │ └── RateLimiter: 조회 허용량 LiveView 화면 ▲ │ │ ├──── 조회 서비스 ────────────────┤ │ │ JS 훅 / Cytoscape MCP 도구 6개 감독 트리 표시 ▲ │ Bandit 127.0.0.1:4040/mcp ▲ MCP 클라이언트 조회 서비스 ── Erpc / Agent ── Erlang distribution │ 직접 연결 또는 SSH 터널 ▼ [대상 서버: OTP 27+ BEAM 노드] OTP API: system_info / process_info / supervisor / ets + 메모리에 로드한 voyager_agent_<해시 12자리> ├── 프로세스 순회·상위 K개 선택 ├── ETS 페이지 조회·큰 값 잘라내기 └── 접속한 Voyager 노드 감시·코드 정리

이 그림에서 SQLite는 대상 서비스의 운영 DB가 아닙니다. Voyager 자신의 설정과 접속 정보를 보관합니다. 대상 노드의 ETS도 별개입니다. 화면을 위한 원격 조회 결과와 앱 설정의 영속 저장을 구분하면 데이터 흐름이 단순해집니다. 관찰 도구의 설계에서는 무엇을 읽을지뿐 아니라, 읽는 동안 대상 시스템에 얼마의 일을 시킬지도 결정해야 합니다.

4-1. 패턴 ① 상태 소유자 한 곳 + 교체 가능한 연결 전략

lib/voyager/application.ex의 start/2는 Vault, Repo, PubSub, Task.Supervisor, NodeSession, RateLimiter, 웹 엔드포인트, MCP 등을 :one_for_one 감독자 아래 시작합니다. 한 자식의 실패에 대해 그 자식을 재시작하는 기본 전략입니다. 이것만으로 모든 런타임 문제가 자동 해결되지는 않습니다.

lib/voyager/node_session.ex의 Voyager.NodeSession은 GenServer이며 활성 연결을 하나만 소유합니다. 이미 연결되어 있으면 {:error, :already_connected}를 반환합니다. 이 때문에 화면과 MCP는 각각 독립적인 대상 노드를 갖는 대신 동일한 현재 연결을 사용합니다.

# lib/voyager/node_session/connector.ex — 핵심 계약 발췌
@callback connect(node_name :: String.t(), cookie :: String.t(), opts :: keyword()) ::
            {:ok, node(), meta()} | {:error, term()}
@callback disconnect(node(), meta()) :: :ok
@callback subscriptions() :: [String.t()]
@callback teardown?(msg :: term(), meta()) :: boolean()

이것이 Elixir의 behaviour입니다. 연결 방식이 지켜야 할 함수 모양을 정의합니다. lib/voyager/node_session/connectors/distribution.ex는 직접 연결을, lib/voyager/node_session/connectors/ssh.ex는 SSH 연결을 구현합니다. NodeSession은 공통 계약만 사용하고, SSH 연결 핸들·로컬 포트 같은 차이는 세션의 meta에 담습니다.

연결 성공의 의미도 엄격합니다. connect_and_install/4는 전송 연결 뒤 Agent.install/1까지 성공해야 세션을 확정합니다. 설치가 실패하면 connector.disconnect/2로 되돌립니다. 이는 “소켓이 열렸다”와 “관찰할 준비가 끝났다”를 별개 단계로 다루는 작은 트랜잭션과 비슷합니다.

미연결 └─ connect_via/4 ├─ connector.connect/3 실패 ──────────── 미연결 └─ 전송 연결 성공 ├─ Agent.install/1 실패 → 연결 정리 → 미연결 └─ 설치 성공 → Session 저장 → node_connected 방송 │ 수동 해제 / nodedown / 터널 장애 ▼ 세션 제거·관련 상태 초기화

handle_info/2는 :nodedown과 연결 전략의 teardown?/2 결과를 처리합니다. 이벤트는 Phoenix.PubSub의 node_session 토픽으로 전달됩니다. 화면이 네트워크 상태를 제각각 추측하지 않고, 상태 소유자가 알리는 변화를 구독하는 구조입니다.

SSH의 상세 경로는 lib/voyager/services/remote_node_connector.ex의 connect/6·discover_dist_port/4에서 볼 수 있습니다. SSH로 EPMD에 접근해 대상 distribution 포트를 알아내고, 그 포트를 터널로 연결합니다. rel/app/vm.args.eex의 -proto_dist dual_tcp -epmd_module Elixir.Voyager.ProxyEpmd가 이 경로를 부팅 시 활성화합니다.

4-2. 패턴 ② 데이터를 가져오기 전에 원격에서 줄이기

프로세스가 P개인데 상위 K개만 보고 싶다면, 모든 상세 데이터를 로컬로 보내는 설계는 전송량이 큽니다. lib/voyager/services/process_list.ex의 top/7은 먼저 속성 허용 목록을 검사하고, priv/voyager_agent.erl의 proc_top/5를 원격 호출합니다. 메시지 본문·프로세스 사전·백트레이스처럼 커질 수 있는 속성은 목록 조회에서 거절합니다.

Erlang 쪽의 scan/5·fold/9·insert/7는 프로세스를 순회하며 gb_trees에 후보 K개만 유지합니다. 큰 값 우선이면 현재 후보 중 가장 작은 값을 교체합니다. 비교 트리 연산만 보면 시간은 대략 O(P log K), 후보 보관은 O(K)입니다. 실제 비용에는 속성 조회·검색 문자열 처리·프로세스 변동도 더해집니다.

# 동작을 이해하기 위한 의사 코드 — 구현 언어 문법과 무관
후보 = 비어 있는 정렬 트리
각 프로세스에 대해:
  허용된 고정 크기 속성을 읽는다
  검색 조건과 맞지 않으면 건너뛴다
  후보가 K개 미만이면 넣는다
  새 값이 현재 최하위보다 좋으면 최하위를 바꾼다
후보만 정렬해 반환한다

여기서 반환 25개가 조회 25번이라는 뜻은 아닙니다. 도우미는 전체 프로세스 표를 순회하고, 그중 일부만 보냅니다. iterator/0는 :erlang.processes_iterator/0를 우선 사용하며 없으면 목록 방식으로 돌아갑니다. 프로세스가 순회 도중 종료되면 process_info 결과를 확인해 건너뜁니다. 원자적인 전체 스냅숏은 아닙니다.

도우미 모듈은 어떻게 대상 노드에 도착하나요?

  1. lib/voyager/agent.ex의 install/1이 대상 OTP 버전을 확인합니다.
  2. 소스의 SHA-256 앞 12자리를 붙인 voyager_agent_… 모듈이 이미 로드됐는지 검사합니다.
  3. lib/voyager/services/code_injector.ex의 load/3가 로컬에서 :epp.parse_file/2로 전처리합니다.
  4. remote_compile/2가 대상의 :compile.forms/2를 호출해 BEAM 바이너리를 만듭니다.
  5. remote_load/4가 대상의 :code.load_binary/3로 메모리에 올립니다. 이어서 Agent.install/1의 등록 단계가 실행됩니다.

컴파일 제한 시간은 15,000ms, 로드 제한 시간은 5,000ms입니다. 원격에서 컴파일하므로 대상에 compiler가 필요합니다. 소스별 이름을 쓰면 서로 다른 Voyager 버전이 같은 노드에 접속해도 상대의 모듈을 덮어쓰지 않습니다. register/1은 접속한 Voyager 노드를 감시하고 마지막 노드가 사라지면 종료·코드 정리 경로로 들어갑니다.

용어
term budget과 힙 상한은 다른 제한입니다
term budget은 중첩된 Erlang 값을 얼마나 펼쳐 응답에 넣을지 제한합니다. 기본값 5,000은 단순한 바이트 수가 아닙니다. with_bounded_heap/1은 작업 프로세스에 max_heap_size: 10_000_000 word를 설정합니다. 64비트의 8바이트 word로 환산하면 약 76MiB이지만 전체 앱 메모리 한도나 정확한 응답 크기 보장은 아닙니다.

ETS도 select_chunk/6·select_spec/7·lookup/7으로 나누어 읽습니다. 잘린 값에는 $voyager_truncated 표시가 남고 다음 조회에는 continuation을 사용합니다. 다만 메시지함 조회는 런타임 API 특성상 잘라내기 전에 큰 값을 복사할 수 있습니다. 반환값 제한, 힙 제한, 시간 제한이 각각 다른 위험을 줄인다는 점을 기억해야 합니다.

4-3. 패턴 ③ 비동기 조회와 화면 상태를 분리하기

lib/voyager_web/live/processes_live.ex는 폼·정렬·현재 표시 페이지를 소유합니다. lib/voyager_web/live/processes_live/query.ex의 page/3는 검증된 입력을 조회 서비스 호출로 바꿉니다. lib/voyager_web/live/processes_live/fetcher.ex는 start/1·fetch/2·start_fetch/2로 조회 수명주기를 관리합니다.

조회 상한과 표시 페이지 크기도 다릅니다. GUI의 기본 조회 상한은 100개이고 표시 페이지는 25개입니다. GUI 조회 상한은 25~5,000의 정해진 선택지, 시간 제한은 1,000~30,000ms입니다. 표의 다음 페이지는 받아 둔 결과를 잘라 보여 주며, 전체 원격 프로세스의 다음 25개를 별도로 가져오는 기능은 아닙니다.

감독 트리는 lib/voyager/services/supervision_tree/fetch.ex의 start/1에서 Task.Supervisor.async_nolink/2로 탐색을 시작합니다. 작업 실패를 화면 프로세스에 전파하지 않는 선택입니다. 브라우저 쪽 assets/js/hooks/supervision_tree/graph.js의 applyPayload는 전체 결과와 변경분을 구분해 적용합니다. 서버는 조사·상태를, Cytoscape는 배치·선택·확대 같은 시각적 상호작용을 담당합니다.

4-4. 패턴 ④ 얇은 입구 여러 개 + 공통 서비스와 사용량 제어

lib/voyager/mcp/tools/process_list.ex의 execute/2는 스키마로 받은 문자열을 미리 만든 허용 목록에 대응시키고, Remote.reply/2로 결과를 반환합니다. MCP는 기본 25개, 최대 100개이며 GUI와 상한이 다릅니다. UI 전용 상태를 서비스에 섞지 않아 MCP가 기존 서비스를 재사용할 수 있습니다.

# lib/voyager/services/rate_limiter.ex — 기본 설정 발췌
@default_config %{
  high_capacity: 5,
  high_refill: 2,
  low_capacity: 2,
  low_refill: 1,
  low_starvation_threshold: 2,
  refill_interval_ms: 1_000
}

RateLimiter.run/2는 수동 GUI·MCP 요청을 :high, 자동 갱신을 :low로 나눕니다. 1초마다 높은 우선순위 토큰 2개, 낮은 우선순위 토큰 1개를 각 용량 한도까지 채웁니다. 높은 우선순위의 잔여 토큰이 2개 미만이면 낮은 우선순위 요청을 거절합니다. 이 토큰 버킷은 요청 허용 여부를 제어합니다. 이미 시작한 RPC의 실행 시간을 제한하지는 않습니다.

비유

관찰할 때마다 대상 서버의 직원에게 질문하는 셈입니다. 결과를 작게 받는 것은 답변 분량을 줄이는 일이고, 토큰 버킷은 질문을 던지는 횟수를 조절하는 일입니다. 두 가지를 함께 해야 계기판이 공장 일을 지나치게 방해하지 않습니다.

lib/voyager/mcp.ex는 MCP 구성 요소들을 :one_for_all로 묶습니다. 서버·동적 감독자·엔드포인트 관리자가 함께 동작해야 하므로 앱 전체의 :one_for_one과 다른 전략을 선택한 것입니다. EndpointManager는 새 포트를 먼저 열고, commit_endpoint/3에서 설정 저장에 성공한 뒤 이전 리스너를 닫습니다. 실패 시 기존 서비스를 남기는 순서 자체가 학습할 만한 설계입니다.

MCP는 기본 설정에서 127.0.0.1:4040에 바인딩하며 lib/voyager/mcp/router.ex가 Host·Origin을 검사합니다. Origin이 없으면 허용하고, 있으면 loopback 호스트인지 확인합니다. 별도 인증은 없습니다. 따라서 로컬 경계를 전제로 한 기능이며, 이를 외부 공개 API처럼 그대로 노출해서는 안 됩니다. 구현 근거: MCP 소스.

5디렉토리 구조 해부

파일 이름을 따라가며 책임의 경계를 찾기
voyager/ ├── mix.exs / mix.lock Elixir 앱·의존성·빌드 작업 ├── .tool-versions 개발 도구 버전 ├── config/ 환경별 설정·빌드 도구 버전 ├── lib/ │ ├── voyager/ │ │ ├── application.ex OTP 감독 트리 │ │ ├── node_session.ex 현재 연결 상태 │ │ ├── node_session/ 연결 계약·직접 연결·SSH 전략 │ │ ├── agent.ex / erpc.ex 도우미 호출·RPC 오류 변환 │ │ ├── services/ 노드·프로세스·ETS·SSH 조회 │ │ ├── schemas/ 저장할 Ecto 데이터 모양 │ │ ├── queries/ DB 읽기 │ │ ├── actions/ DB 쓰기 │ │ ├── mcp/ 서버·도구·로컬 HTTP 관리 │ │ └── telemetry/ 사용 이벤트·내보내기 │ └── voyager_web/ │ ├── router.ex 화면 경로와 연결 조건 │ ├── live/ 화면별 상태·이벤트·HEEx │ ├── form_schemas/ 화면 입력 검증 │ ├── components/ 공용 UI │ └── hooks/ LiveView 생명주기 훅 ├── assets/ │ ├── js/hooks/ 브라우저 동작·감독 트리 │ ├── css/ 스타일·테마·아이콘 │ └── package.json npm 의존성 ├── priv/ │ ├── voyager_agent.erl 대상 노드에 올릴 Erlang 도우미 │ ├── repo/migrations/ 로컬 DB 변경 이력 │ └── static/ 정적 파일 ├── src/dual_tcp_dist.erl distribution 전송 모듈 ├── rel/app/ Mix 릴리스·Tauri 패키징 │ └── src-tauri/ Rust 앱·macOS Swift 트레이 ├── test/ ExUnit·원격 조회 검사 ├── e2e/ Playwright 시나리오 ├── dev/ 개발 실행·데모 노드·검사 스크립트 ├── docs/ 연결·개발·Linux 사용 안내 └── .github/workflows/ 검사·패키징·릴리스·버전 갱신

services라는 이름만 보면 모든 업무가 한곳에 몰린 것처럼 보일 수 있습니다. 실제로는 lib/voyager/services/node_info/, lib/voyager/services/ets/, lib/voyager/services/supervision_tree/처럼 기능별 하위 모듈이 있습니다. 폴더를 외우기보다 화면 입력, 업무 처리, 원격 호출, 결과 표현의 경계를 따라 읽는 편이 효과적입니다.

5-1. 처음 읽는 사람에게 권하는 세 경로

관심사읽는 순서확인할 질문
연결lib/voyager_web/router.ex → lib/voyager_web/live/connect_live.ex → lib/voyager/node_session.ex → lib/voyager/agent.ex어느 시점부터 연결 성공으로 보는가?
목록lib/voyager_web/live/processes_live.ex → lib/voyager_web/live/processes_live/query.ex → lib/voyager/services/process_list.ex → priv/voyager_agent.erl정렬·조회 제한·화면 페이지는 어디서 적용되는가?
AI 도구lib/voyager/mcp/server.ex → lib/voyager/mcp/tools/process_list.ex → lib/voyager/mcp/tools/remote.exGUI와 공유하는 계층과 MCP에만 있는 검증은 무엇인가?

웹 화면은 lib/voyager_web/, 도메인·서비스는 lib/voyager/ 아래에 있습니다. lib/voyager_web/hooks/는 Elixir 서버 훅이고 assets/js/hooks/는 브라우저 훅입니다. 이름은 같아도 실행 위치와 수명주기가 다르므로 함께 묶어 생각하지 않는 것이 좋습니다.

5-2. 코드 규모는 어떻게 읽어야 하나요?

Git 추적 파일의 물리적 줄을 세면 lib/의 Elixir 구현은 165개 파일·23,260줄, test/의 Elixir 파일은 108개·15,587줄입니다. 주석·공백·HEEx 문자열을 포함하므로 순수 실행 코드량이나 테스트 커버리지 수치가 아닙니다. 언어별 전체 집계와 범위는 11절에 정리했습니다.

읽기 요령
큰 파일을 처음부터 끝까지 읽지 않아도 됩니다

먼저 공개 함수의 입력·반환값과 호출자를 찾고, 오류가 돌아가는 방향을 확인하세요. 그다음 해당 함수의 테스트를 읽으면 의도와 경계 조건을 빠르게 파악할 수 있습니다.

6학습 포인트 (기술별)

문법에서 끝내지 않고 설계 선택까지 이해하기

6-1. Elixir: 패턴 매칭으로 성공과 실패를 연결합니다

lib/voyager/services/code_injector.ex의 load/3는 with로 전처리와 컴파일의 성공을 연결합니다. {:ok, 값}이 이어질 때 다음 단계로 가고, 다른 모양이면 그 결과를 반환합니다. 예외만으로 실패를 전달하는 코드에 익숙하다면, 반환값 자체에 성공·실패를 드러내는 방식부터 익혀 보세요.

# lib/voyager/services/code_injector.ex — 실제 흐름 발췌
def load(node, path, macros \\ []) do
  with {:ok, forms} <- preprocess(path, macros),
       {:ok, module, binary} <- remote_compile(node, forms) do
    remote_load(node, module, Path.basename(path), binary)
  end
end

반대로 lib/voyager/erpc.ex의 safe_call/5는 원격 예외·종료·throw를 잡아 오류 튜플로 바꿉니다. 실습 아이디어: 읽기 실패, 컴파일 실패, 연결 끊김 세 경우가 어느 함수에서 어떤 튜플로 바뀌는지 표를 만드세요. 함수의 정상 경로와 오류 경로를 함께 따라가면, 문법이 실제 설계 도구가 되는 지점을 볼 수 있습니다.

6-2. OTP: 상태를 모으되, 비싼 일을 모두 직렬화하지 않습니다

NodeSession은 “지금 어디에 연결됐나”라는 공유 상태를 소유합니다. RateLimiter는 토큰 상태를 소유하지만, run/3는 토큰 획득 후 fun.()을 호출자 쪽에서 실행합니다. 긴 원격 조회 전체를 RateLimiter의 GenServer 안에서 수행하는 구조가 아닙니다. 상태를 보호하는 짧은 구간과 실제 작업을 구분하는 예입니다.

실습 아이디어: NodeSession.connect_via/4의 무한 대기 옵션과 CodeInjector의 15초·5초 제한이 각각 어느 단계의 대기를 제어하는지 표시하세요. “GenServer를 쓴다”만으로 응답성이 보장되는지, 내부 함수가 어떤 제한을 갖는지까지 질문해 보세요.

6-3. LiveView·Ecto: 입력 검증과 화면 상태를 분리합니다

lib/voyager_web/form_schemas/process_list_controls.ex는 DB 테이블이 아닌 embedded_schema로 검색어·상한·시간 제한·열 선택을 정의합니다. changeset/2가 검증하고 apply/2는 잘못된 필드의 변경을 빼서 이전 유효 값을 보존합니다. 숫자를 지우고 다시 입력하는 중에도 표가 불필요하게 무너지지 않게 하는 설계입니다.

실습 아이디어: 기존 상한이 100일 때 검색어는 유효하고 시간 제한은 잘못된 입력이라고 가정하세요. 어떤 변경이 적용되고 어떤 값은 남는지 valid_part/1까지 따라가세요. 이어서 Fetcher의 dirty?·fetched_with가 현재 폼과 마지막 결과 사이의 차이를 어떻게 표현하는지 읽어 보세요.

6-4. Erlang: 원격에서 실행할 코드는 호환성도 설계해야 합니다

도우미는 Elixir 모듈이 아니라 priv/voyager_agent.erl입니다. -module(?AGENT).의 매크로를 로컬 전처리에서 치환한 뒤 대상 노드에서 컴파일합니다. CodeInjector 문서가 ?OTP_RELEASE 조건부 사용을 경고하는 이유는 전처리 시점에 보이는 버전이 대상이 아니라 Voyager 쪽 OTP이기 때문입니다.

실습 아이디어: iterator/0의 function_exported/3 검사와 목록 대체 경로를 비교하세요. 배포 시점의 버전 숫자를 믿는 방법과, 실행 시점에 기능의 존재를 검사하는 방법의 장단점을 설명해 보세요. 모듈 이름에 소스 해시를 넣는 방식은 코드 버전과 원격 설치 상태를 함께 식별하는 아이디어로 연결됩니다.

6-5. ETS: 검색 조건은 데이터로 파싱하고, 무제한으로 읽지 않습니다

lib/voyager/services/ets/search.ex는 키 일치·접두사·필드 일치 조건을 match spec으로 바꿉니다. 키 완전 일치는 Fetch.lookup/7로 보내 키 조회 경로를 사용합니다. 고급 문자열 입력은 lib/voyager/services/ets/match_spec.ex의 parse/1이 :erl_scan.string/1·:erl_parse.parse_term/1으로 리터럴 term만 읽습니다. 임의 코드를 평가하지 않습니다.

# Erlang match spec: {키, 값}에서 값이 10보다 큰 행의 키를 선택
[{{'$1', '$2'}, [{'>', '$2', 10}], ['$1']}]

이 예의 머리 패턴은 2원소 튜플이고, 두 번째 목록은 조건, 마지막 목록은 반환할 값입니다. 파서에는 4,096바이트 상한과 atom 여유 10,000개 검사가 있습니다. erl_scan이 새 atom을 만들 수 있기 때문에 “eval하지 않는다”만으로 모든 자원 위험이 사라지지는 않습니다. 실습 아이디어: 함수 호출 문자열과 단순 term 문자열을 왜 다르게 취급하는지 관련 테스트에서 확인하세요.

용어
continuation: 다음 페이지를 읽기 위한 재개 정보
ETS 페이지 조회 결과에는 다음 읽기 위치를 나타내는 값이 올 수 있습니다. MCP 쪽은 이를 불투명한 cursor로 표현하고 같은 테이블·검색 조건인지 확인합니다. 이것은 긴 테이블 전체를 하나의 고정 스냅숏으로 저장한 결과가 아닙니다. 조회 사이에 테이블은 바뀔 수 있습니다.

6-6. MCP·Rust: 제품 경계마다 계약이 필요합니다

MCP의 schema는 허용 입력을 정의하고, lib/voyager/mcp/tools/remote.ex는 현재 연결 확인·사용량 제어·응답 변환을 묶습니다. Remote의 비공개 함수 jsonable/1에는 비 UTF-8 바이너리, 구조체, charlist 등을 JSON으로 전달하기 위한 처리가 있습니다. BEAM의 모든 값이 JSON의 기본 타입과 그대로 대응하는 것은 아니라는 점을 보여 줍니다.

Rust 쪽 rel/app/src-tauri/src/lib.rs의 run()은 ElixirKit 통신을 열고 Elixir 프로세스를 시작한 뒤 ready 메시지를 기다려 창을 만듭니다. 단순히 프로세스를 생성했다고 UI 준비가 끝난 것으로 처리하지 않습니다. 실습 아이디어: 서버 준비 전, 준비 완료, 서버 종료 때 창과 앱이 어떻게 반응하는지 호출 순서를 그려 보세요.

6-7. 테스트: 성공 사례보다 경계 조건에서 설계를 배웁니다

테스트 파일읽을 사례배울 내용
test/voyager/agent_test.exsOTP 27 미만 거절, 두 번째 연결 중 진행 중인 호출 유지호환성·동시 접속의 경계
test/voyager/services/process_list_test.exs지원하지 않는 속성·정렬을 원격 호출 전에 거절비용을 지불하기 전 검증하기
test/voyager/services/rate_limiter_test.exs토큰 소진, 보충, 낮은 우선순위 거절시간과 상태 변화의 계약
test/voyager/mcp/router_test.exs유사한 Host, 외부 Origin, IPv6 loopback네트워크 경계를 구체적인 입력으로 검사하기
test/voyager/services/ets/match_spec_test.exs함수 호출·변수·여러 절·긴 입력 거절문자열 입력의 허용 범위

테스트 파일은 해당 사례를 자동 검사하도록 작성한 코드입니다. 파일의 존재만으로 모든 실행 환경에서의 통과나 모든 장애에 대한 안전성을 보장할 수는 없습니다.

7하드웨어 / 시스템 요구사항

앱을 사용하는 컴퓨터, 빌드하는 컴퓨터, 관찰 대상 서버를 나누기
구분확인되는 조건초보자가 알아둘 점
배포 앱README: macOS Apple Silicon·Intel, Linux x64 AppImageWindows 배포는 안내되지 않음. Tauri를 쓴다고 모든 OS 배포가 완성된 것은 아님
macOSrel/app/src-tauri/tauri.conf.json: 최소 시스템 13.0CPU 아키텍처에 맞는 빌드를 선택
대상 BEAM 노드OTP 27+, 분산 이름·cookie, compiler 애플리케이션GUI만 설치한다고 이름 없는 노드에 바로 접속할 수는 없음
직접 연결EPMD와 distribution 포트에 접근 가능EPMD 기본 4369만 열려 있어도 실제 distribution 연결은 실패할 수 있음
SSH 연결SSH 로그인, 중계 호스트에서 노드 접근 가능, 대상 EPMDSSH 기본 22, EPMD 기본 4369. cookie도 별도로 필요
개발 환경Elixir 1.20.2-otp-29, Erlang 29.0.2, Node.js 26.4.0, Rust 1.96.0.tool-versions 기준. npm engines 불일치는 3절 참고
Linux 빌드WebKitGTK 4.1·AppIndicator·librsvg·patchelf 관련 개발 패키지CI 설치 목록 기준. 배포판별 패키지 이름은 별도 확인 필요
CPU·RAM·디스크README·개발 안내에 수치형 공식 최소 사양 없음Rust·BEAM·브라우저를 함께 빌드·실행할 여유를 고려. 고정 성능을 단정할 근거는 없음
GPU전용 GPU·CUDA 의존성이 명시되어 있지 않음내장 MCP가 모델을 로컬 추론하는 기능은 아님. 모델 실행 조건은 MCP 클라이언트 쪽 문제

7-1. 포트 번호가 여러 개인 이유

웹 개발 기본 포트는 config/runtime.exs의 4000이고, E2E는 4001입니다. 데스크톱 앱은 Rust 코드에서 4005를 시작점으로 사용 가능한 포트를 찾습니다. MCP는 별도 서버로 기본 4040입니다. 따라서 “Voyager의 포트”를 하나로 외우기보다 어떤 연결의 포트인지 구분해야 합니다.

7-2. 패키징 대상과 실제 배포 안내는 다릅니다

.github/workflows/build-apps.yml에는 macOS arm64·x64와 Linux arm64·x64의 빌드 행렬이 있습니다. README의 설치 안내는 Linux x64만 명시합니다. CI에 arm64 빌드 항목이 있다는 사실만으로 해당 릴리스에서 내려받을 배포 파일이 있다고 보장할 수는 없습니다. 설치 시에는 공식 다운로드 안내와 릴리스 자산을 확인하세요.

관찰 비용
대상 노드의 부하는 남아 있습니다

상위 목록·ETS 조회·프로세스 상태 읽기는 CPU와 메모리를 씁니다. 상한과 시간 제한이 있어도 무부하를 보장하지 않습니다. 처음에는 별도 로컬 노드에서 작은 조회 상한과 수동 갱신으로 동작을 익히는 편이 좋습니다.

8직접 해볼 수 있는 실습 과제

읽기부터 로컬 관찰까지, 다섯 단계로 난도를 높이기

1번은 소스만 읽어도 완료할 수 있습니다. 실행이 필요한 과제는 개인 실습용 BEAM 노드와 민감하지 않은 데이터로 진행하세요. 앱 설치·빌드 절차는 개발 안내를 참고합니다. 화면에 나타난 값이 어느 함수에서 만들어졌는지 설명할 수 있어야 실습을 완료한 것입니다.

과제 1 · 입문 · 소스 읽기

프로세스 목록의 조회 흐름 그리기

ProcessesLive의 refresh_now 이벤트부터 Fetcher.fetch/2, Query.page/3, ProcessList.top/7, Agent.call/4, proc_top/5까지 화살표를 그리세요.

완료 기준: 입력 검증 위치, 원격 정렬 위치, 결과 개수 제한 위치, 화면 페이지 분할 위치를 각각 짚을 수 있어야 합니다.

확인 질문: 표시 페이지를 25에서 50으로 바꾸는 일과 조회 상한을 100에서 500으로 바꾸는 일은 대상 노드의 비용이 어떻게 다른가요?

과제 2 · 입문 · 별도 로컬 노드

메시지함에 세 개의 메시지를 넣고 찾기

OTP 27+의 IEx를 아래처럼 별도 터미널에서 시작하고, Voyager의 직접 연결 폼에 같은 노드 이름과 cookie를 입력하세요. 이 cookie는 이 실습에서만 사용하는 예시 값입니다.

# 터미널: 개인 실습용 노드 시작
iex --name study@127.0.0.1 --cookie study-only-cookie

# IEx: 메시지를 소비하지 않고 대기하는 프로세스
pid = spawn(fn ->
  receive do
    :stop -> :ok
  end
end)
Enum.each(1..3, fn n -> send(pid, {:study_message, n}) end)
Process.info(pid, :message_queue_len)
# 기대: {:message_queue_len, 3}

# 관찰을 마치면 선택적 receive가 :stop을 받아 종료
send(pid, :stop)

완료 기준: 종료 전에 프로세스 목록을 message_queue_len 기준으로 정렬하고 PID와 길이 3을 확인합니다. 다른 프로세스도 있을 수 있으니 순위 1위라는 가정은 하지 마세요.

배울 것: 메시지를 보냈다고 자동으로 소비되는 것은 아닙니다. receive 패턴과 맞지 않는 메시지는 큐에 남습니다.

과제 3 · 중급 · ETS 페이지 조회

테이블의 전체 크기와 한 번에 읽는 양을 구분하기

같은 실습 노드에서 작은 공개 테이블을 만든 뒤 ETS 화면에서 찾습니다. 값은 간단한 정수·문자열만 사용합니다.

# IEx: 실습용 테이블과 데이터 100개
table = :ets.new(:voyager_study, [:named_table, :set, :public])
Enum.each(1..100, fn n -> :ets.insert(table, {n, "item-#{n}"}) end)
:ets.info(table, :size)
# 기대: 100

# 페이지·검색 관찰 후 정리
:ets.delete(table)

완료 기준: 작은 페이지 상한으로 일부만 읽고 다음 페이지를 요청해 봅니다. 키 완전 일치와 전체 선택의 경로를 Ets.Search.chunk/8·Ets.Fetch에서 비교하세요.

확인 질문: :set 조회 결과를 삽입 순서로 예상하면 왜 곤란할까요? 페이지 사이에 데이터가 바뀌면 무엇을 단정할 수 없을까요?

과제 4 · 중급 · MCP 계약 읽기

화면에서 본 상위 프로세스를 도구 호출과 비교하기

MCP 클라이언트를 로컬 엔드포인트 http://127.0.0.1:4040/mcp에 연결합니다. 아래는 도구 이름과 인수의 예이며, 초기화·세션 처리를 포함한 전체 HTTP 요청은 아닙니다.

{
  "name": "process_list",
  "arguments": {
    "limit": 10,
    "sort_by": "memory",
    "direction": "desc",
    "attrs": ["memory", "message_queue_len", "registered_name"]
  }
}

완료 기준: GUI와 같은 노드를 보는지 확인하고 total_scanned와 반환 행 수의 차이를 설명하세요. 두 관측 시각이 다르므로 값이 완전히 같을 필요는 없습니다.

추가 질문: GUI에서는 조회 상한 500을 선택할 수 있는데 MCP process_list에는 왜 500을 보낼 수 없을까요? 도구 스키마에서 답을 찾으세요.

과제 5 · 고급 · 상태 전이·실패 설계

연결 도중 실패와 연결 후 실패를 나눠 추적하기

먼저 실행 없이 NodeSession.connect_and_install/4과 Agent.install/1을 읽고, 원격 컴파일 실패 때 전송 연결이 어떻게 정리되는지 그립니다.

그다음 개인 실습 노드 연결 후 그 노드만 정상 종료해, :nodedown이 세션 상태와 화면에 어떻게 전달되는지 관찰합니다. 조사 대상이 사라졌는데 이전 결과가 최신값처럼 남지 않는지 보세요.

완료 기준: 설치 실패, 정상 해제, 노드 종료, 도우미 :undef의 네 경로를 입력 이벤트·세션 변화·알림으로 나눈 표를 만듭니다.

더 생각하기: 새 MCP 포트 열기에 실패했을 때 기존 포트를 유지하는 EndpointManager의 순서를 다른 서비스 설정 변경에도 적용할 수 있을까요?

9관련 기술 심화 학습 로드맵

6주 동안 작은 관찰 도구를 이해할 수 있는 기반 쌓기

Elixir가 처음이라면 거대한 감독 트리 탐색부터 시작할 필요는 없습니다. 반환값을 읽는 연습에서 출발해 상태, 원격 호출, 화면, 도구 계약 순서로 넓혀 가세요. 매주 기능을 많이 구현하기보다 설명 가능한 작은 결과물을 하나씩 남기는 계획입니다.

주차배울 내용Voyager에서 읽을 곳완료 산출물
1주차Elixir 값·튜플·패턴 매칭·파이프·withlib/voyager/services/code_injector.ex, lib/voyager/erpc.ex성공·실패 반환값 5개를 한국어로 풀이한 노트
2주차프로세스·메시지·GenServer·감독 전략lib/voyager/application.ex, lib/voyager/node_session.ex, lib/voyager/mcp.ex연결 상태 전이도와 두 감독 전략의 선택 이유
3주차분산 이름·cookie·EPMD·RPC·원격 코드 로드lib/voyager/agent.ex, lib/voyager/services/remote_node_connector.ex, 연결 문서직접 연결과 SSH 연결의 포트 흐름 비교표
4주차LiveView·Ecto 입력 검증·비동기 결과·JS 훅lib/voyager_web/live/processes_live/, lib/voyager_web/form_schemas/, assets/js/hooks/폼 변경부터 표 갱신까지의 이벤트 순서도
5주차ETS·match spec·상위 K개 알고리즘·조회 예산priv/voyager_agent.erl, lib/voyager/services/ets/행 수·term 크기·시간·호출 빈도 제한을 나눈 표
6주차MCP 스키마·Tauri 준비 신호·CI·배포 버전lib/voyager/mcp/tools/, rel/app/src-tauri/, .github/workflows/새 조회 도구를 추가한다면 바뀔 계층과 검증 항목 설계서

9-1. 관심 분야에 따라 더 깊게 들어가기

스스로 설명해 보기
한 기능의 계약을 네 문장으로 말할 수 있으면 다음 단계로 갑니다

무엇을 입력하는가? 어느 프로세스·노드에서 실행되는가? 얼마나 읽고 무엇을 반환하는가? 실패하면 누가 정리하는가? 이 네 질문을 process_list와 ETS 조회에 각각 답해 보세요.

10핵심 키워드 사전

처음 보는 단어를 코드 속 역할과 함께 기억하기
용어쉬운 설명
BEAMErlang·Elixir 프로그램이 실행되는 가상 머신. 많은 경량 프로세스를 관리합니다.
OTP프로세스·서버·감독·배포 등을 위한 Erlang 생태계의 런타임·라이브러리·설계 기반입니다.
node분산 환경에서 이름으로 구분되는 BEAM 실행 인스턴스입니다.
PIDBEAM 프로세스의 식별자입니다. 서로 다른 노드의 PID를 단순한 숫자로 혼동하면 안 됩니다.
mailbox프로세스에 도착해 아직 소비되지 않은 메시지가 대기하는 공간입니다.
message_queue_len대기 중인 메시지 개수. 그 자체가 메시지 바이트 수나 장애의 확정 증거는 아닙니다.
reductionsBEAM이 실행 작업량을 세는 단위입니다. 누적값을 CPU 사용률 퍼센트로 읽으면 안 됩니다.
scheduler실행 가능한 BEAM 프로세스에 CPU 실행 기회를 나누는 런타임 구성 요소입니다.
GenServer상태를 가진 서버 프로세스를 만들기 위한 OTP의 표준 패턴입니다.
Supervisor자식 프로세스의 종료를 감시하고 정해진 정책으로 재시작하는 프로세스입니다.
supervision tree어떤 감독자가 어떤 작업자를 관리하는지 나타낸 계층입니다. 일반적인 함수 호출 그래프와 다릅니다.
behaviour모듈이 구현해야 할 함수 계약입니다. Voyager의 직접 연결·SSH 전략이 같은 계약을 따릅니다.
PubSub이벤트를 특정 토픽에 알리면 구독자가 받는 방식입니다. 연결 변화 알림에 쓰입니다.
Erlang distribution이름이 있는 BEAM 노드들이 연결해 메시지와 원격 호출을 주고받는 기반입니다.
cookie분산 노드 연결의 인증에 쓰는 공유 비밀값입니다. 데이터 전송 암호화 자체를 뜻하지 않습니다.
EPMD노드 이름으로 실제 distribution 포트를 알아내도록 돕는 서비스입니다. 기본 포트는 4369입니다.
RPC / erpc다른 노드에서 함수를 실행하고 결과를 받는 방식과 Erlang의 관련 API입니다.
ETSBEAM 안의 메모리 테이블입니다. 대상 서버의 SQL DB나 Voyager의 SQLite와 구분합니다.
match specETS에서 어떤 데이터를 고르고 무엇을 반환할지 패턴·조건·결과로 표현하는 규칙입니다.
continuation / cursor이어서 읽을 위치를 전달하는 값입니다. 시간 전체를 고정한 스냅숏이라는 뜻은 아닙니다.
term숫자·리스트·튜플·맵 등 Erlang이 다루는 값 전반을 가리킵니다.
atom이름 자체가 값인 상수입니다. 동적으로 무제한 생성하면 VM 자원이 고갈될 수 있습니다.
HEExPhoenix의 HTML 템플릿 문법입니다. Elixir 파일의 ~H 안에도 쓰입니다.
LiveView서버에서 관리하는 상태와 이벤트를 바탕으로 화면을 갱신하는 Phoenix 기술입니다.
JS hookLiveView 화면의 특정 요소와 브라우저 JavaScript 동작을 연결하는 수명주기 콜백입니다.
Ecto changeset입력값의 변환·검증·변경 내용을 모은 값입니다. DB 저장 없이 폼 검증에도 사용합니다.
token bucket토큰을 주기적으로 보충하고 요청마다 소비해 호출 빈도를 조절하는 방식입니다.
MCPAI 애플리케이션이 외부 도구·데이터와 연결되는 표준입니다. Voyager는 서버 역할을 합니다.
Streamable HTTPMCP 메시지를 HTTP로 주고받는 전송 방식입니다. Voyager의 로컬 MCP 엔드포인트가 사용합니다.
Tauri / WebView웹 UI를 운영체제의 웹 표시 엔진으로 띄우고 Rust로 데스크톱 기능을 연결하는 구성입니다.
source-available소스는 공개되어 있지만 사용·재배포 조건은 별도 라이선스를 따라야 하는 배포 형태입니다.

11참고 링크

원문으로 이어 읽기와, 분석 시점의 상태 확인

11-1. 프로젝트와 핵심 소스

11-2. 관련 기술의 공식 문서

현재 상태 (2026-10-23 확인)

항목값읽는 기준
저장소software-mansion/voyager공식 GitHub
선정 순위GitHub Trending Elixir Daily #72026-10-23 선정 시점. 현재 트렌딩 페이지는 이후 달라질 수 있음
스타 / 포크106 / 72026-10-23 GitHub 기준
주 언어ElixirGitHub 언어 분류
생성일2026-04-19GitHub 저장소 기준
최신 릴리스v0.2.0 · 2026-10-20분석 시점의 릴리스. 개발 소스의 버전 문자열은 0.2.0-dev
최근 push2026-10-23GitHub 기준
분석 커밋39bdbae · 2026-10-23전체 해시 39bdbae78564cbc82dc68293d814aa201f09b18c
최근 커밋 제목Chore: Bump Elixir/OTP and add a weekly bump workflow (#317)커밋 39bdbae의 원문 제목
코드 규모Elixir .ex + .exs 합계 39,701줄구현·테스트·설정·개발 스크립트 포함. 아래 언어별 집계 참고
라이선스 분류GitHub: NOASSERTION
실제 문서: Voyager User License
표준 라이선스 식별이 확정되지 않았다는 분류. 라이선스 문서가 없다는 뜻은 아님
전체 커밋 수전체 Git 이력 기준 집계 필요단일 커밋만으로 전체 개발량을 추정할 수 없음

11-3. 언어별 물리적 줄 수

커밋 39bdbae의 Git 추적 파일 중 해당 확장자의 텍스트를 줄 단위로 집계했습니다. 공백·주석·테스트·설정 및 해당 확장자의 벤더 파일을 포함하고, 이미지·폰트·잠금 파일은 아래 표의 집계 대상이 아닙니다. HEEx는 Elixir 문자열 안에도 있으므로 HTML 템플릿이 174줄뿐이라는 뜻으로 해석하면 안 됩니다.

언어·확장자파일 수줄 수
Elixir .ex18124,221
Elixir 스크립트 .exs10815,480
Erlang .erl91,454
HEEx .heex1174
JavaScript .js142,383
TypeScript .ts121,672
CSS .css11568
Rust .rs5585
Swift .swift2481
셸 .sh16780

범위를 좁히면 lib/의 .ex는 23,260줄, test/의 .ex·.exs는 15,587줄입니다. 테스트 쪽 물리적 줄 수는 구현 쪽의 약 67%이지만, 이는 커버리지나 품질 점수가 아닙니다. Rust와 Swift가 상대적으로 작다는 점은 이 앱의 업무 로직이 주로 Elixir 쪽에 있다는 구조와도 맞습니다.

쓰기 전에 알아둘 한계
관측 범위·버전 차이·권한과 라이선스를 함께 확인하세요

기능·성능: 추적·차트 등은 화면에서 준비 중입니다. 도우미에 관련 함수가 있어도 완성된 UI나 MCP 도구를 의미하지 않습니다. 조회는 대상 자원을 사용하고 순간값은 원인 분석의 증거 일부입니다. 실행 성능과 설치·테스트 결과는 사용 환경에 따라 달라집니다.

설정 차이: Node.js는 도구 고정값과 npm engines가 다릅니다. 커밋 제목은 “weekly”지만 .github/workflows/bump-elixir-otp.yml의 cron은 0 6 1,15 * *로 매월 1일·15일 06:00 UTC에 실행됩니다. Linux arm64 빌드 행렬과 README의 배포 안내 범위도 다릅니다.

관찰 권한: 분산 연결은 읽기 전용 계정 권한 모델이 아닙니다. 연결하면 코드 컴파일·로드가 일어나며, 프로세스 상태·메시지·ETS에는 비밀값이 있을 수 있습니다. MCP는 별도 인증 없이 loopback 경계를 사용하므로 접속 가능한 로컬 클라이언트와 전달할 데이터의 범위를 확인해야 합니다.

로컬 저장과 통신: cookie 저장에는 로컬 AES-GCM 암호화가 사용됩니다. VaultKey는 환경변수 키 또는 ~/.voyager/vault.key를 사용하므로 DB와 키 관리가 함께 필요합니다. 로컬 앱이라는 사실이 외부 통신 없음까지 뜻하지는 않습니다. 프로덕션 설정은 텔레메트리 export를 사용하며 업데이트 확인도 구현되어 있습니다. 실제 전송 설정·사용자 선택은 따로 확인하세요.

사용 조건: LICENSE.md는 OSI 기준 오픈소스가 아닌 소스 공개형임을 명시합니다. 개인·직원 10명 이하 영리 조직·비영리 조직의 무료 사용 조건과 그 밖의 Company License 조건이 있습니다. 직원 수에는 시간제 직원과 계약자도 포함합니다. 파생 제품 판매·호스팅 제공 등에 제한이 있으므로, 업무 적용·재배포 전에는 라이선스 원문을 확인해야 합니다.