Software Mansion의 쿠바 고네트(Kuba Gonet)가 Popcorn 1.0을 공개했다. Elixir를 브라우저에서 실행하는 라이브러리 Popcorn은 이번 버전부터 AtomVM 대신 OTP/BEAM을 사용해 OTP 호환성을 넓히고, 안정적인 첫 정식 버전을 선보였다.
Popcorn은 처음에 임베디드 환경을 겨냥해 OTP/BEAM의 일부를 구현한 AtomVM을 브라우저 런타임으로 채택했다. 하지만 수정하지 않은 Elixir 코드를 실행하려면 표준 라이브러리에 패치를 계속 유지해야 했고, Popcorn 기능 개발과 포크 관리가 점점 어려워졌다. AtomVM을 OTP/BEAM과 완전히 호환되게 만드는 일도 현실적으로 어렵다고 판단했다.
고네트는 2025년 말 팀에 합류한 마테우시 푸르가(Mateusz Furga)가 OTP/BEAM의 WebAssembly(Wasm) 컴파일을 실험한 뒤, 이를 바탕으로 작업을 이어갔다. 처음 만든 시제품은 시작에 약 10초가 걸리고 파일 크기도 컸지만, VM은 안정적이었고 OTP의 C 코드를 광범위하게 수정할 필요도 없었다. 부팅 시간은 이후 개선됐다.
크기와 속도
Popcorn은 현재 730KB 크기의 Wasm 바이너리를 제공하며, 모든 자산은 Brotli로 압축된다. VM 부팅에는 약 200ms가 걸리고, 따뜻한 부팅(warm boot)은 140ms다. 표준 라이브러리 파일은 약 2.5MB가 추가되지만, 사용하지 않는 모듈을 제거하는 트리 셰이킹으로 크기를 줄일 수 있다.
비교 대상으로 제시된 AtomVM은 압축 후 약 250KB이며 80ms에 시작한다. BEAM은 AtomVM보다 약 720KB 더 크고, AtomVM이 3.2배 빠르게 시작한다. 반면 측정한 작업에서 OTP는 3.4배에서 1,800배 더 빠르게 실행됐다. 컴파일된 코드 실행은 두 VM이 비슷한 수준이었지만, 부팅 시간은 BEAM이 약 240ms, AtomVM이 80ms였다. 인터프리터 코드에서는 BEAM이 모듈을 평가하고 실행하는 데 70ms가 걸린 반면 AtomVM은 240ms가 걸렸다.
암호화 라이브러리를 포함한 빌드와 제외한 빌드가 있다. SSL, 공개·개인 키 확인, 보안 해시 생성처럼 암호화 기능이 필요하면 라이브러리를 포함한 버전을 써야 한다. OTP/BEAM의 crypto 앱을 제공하려면 BEAM이 사용하는 OpenSSL을 Wasm 바이너리로 컴파일해야 한다. 암호학적으로 안전한 난수나 관련 알고리즘이 필요하지 않은 앱은 더 가벼운 버전을 선택할 수 있다.
브라우저에서 달라진 점
Popcorn은 VM 충돌을 브라우저 창과 분리하기 위해 쓰던 iframe 대신 Web Workers를 사용한다. 제작자에 따르면 이 방식은 여러 VM 인스턴스를 실행할 수 있고, 문제가 생긴 VM으로부터 메인 창을 격리한다. 이전에는 메인 창과 출처(origin)가 같으면 iframe이 격리되지 않는 문제가 있었다.
OTP/BEAM으로 옮기면서 기존 AtomVM 버전에 부족했던 비트스트링, 정규식, 전체 ETS, 프로토콜, system/process_info 관련 기능과 일부 NIF도 사용할 수 있게 됐다. 다만 브라우저의 소켓·DNS·파일 시스템 제약과 제거된 NIF에 의존하는 일부 모듈은 여전히 고려해야 한다.
브라우저에는 일반적인 네트워크 스택이 없어 TCP 소켓을 기대하는 Req의 기본 Finch 어댑터를 그대로 쓸 수 없다. Popcorn은 브라우저의 fetch() API를 이용하는 Req 어댑터 Popcorn.Fetch를 만들었고, 이를 기본 어댑터로 설정했다. 사용자가 다른 어댑터를 지정할 수도 있다. fetch()에는 CORS, 리디렉션, 설정 가능한 헤더와 관련한 제한이 있다.
새 API에서는 call()과 cast()가 이름에 맞게 각각 handle_call과 handle_cast로 메시지를 전달한다. 임의의 메시지를 보내는 send()도 추가됐다. JavaScript에서 이 함수는 프로세스 이름이나 Elixir에서 JS.run()의 반환값으로 얻은 PID 핸들을 대상으로 받을 수 있으며, 메시지가 대상 프로세스의 메일박스에 들어가면 Promise를 완료한다. 유효하지 않은 PID를 무시하는 OTP/BEAM의 send()와는 동작이 조금 다르다.
표준 라이브러리는 앱의 .beam 파일을 .tar로 묶고 다시 .tar.br로 압축해 배포한다. 이전 방식은 크게 수정한 표준 라이브러리 beam 파일을 .avm에 담는 방식이었다. Popcorn은 예상되는 오류를 Result 형식의 반환값으로 처리하고, API를 잘못 사용한 경우에는 예외를 던지는 방식으로 JavaScript 오류 처리도 정리했다.
사용과 이전
Popcorn 1.0은 릴리스 페이지에서 확인할 수 있다. 제작자는 문서에서 API와 사용법을 안내하며, 이전 버전에서의 마이그레이션 절차는 마이그레이션 문서와 릴리스 노트에 설명돼 있다. 기존 버전과 사용 방식은 대체로 비슷하고, 호환성을 깨는 변경은 기계적으로 처리할 수 있는 수준이라고 고네트는 밝혔다. Elixir Language Tour의 마이그레이션 변경도 참고할 수 있다.
고네트는 Elixir Language Tour와 Popcorn 랜딩 페이지에서 새 런타임을 사용하고 있으며, 최근 Local LiveView도 OTP/BEAM으로 전환했다고 설명했다. 앞으로는 예제와 크기·속도 최적화를 마무리하고, PopDoc(대화형 문서를 위한 ExDoc 플러그인)과 Local LiveView, 트리 셰이킹을 개선할 계획이다. 새 BEAM 버전 지원도 이전보다 빨라질 것으로 기대한다고 덧붙였다.
OTP/BEAM 최적화에 관한 글에서 고네트는 부팅 시간 개선 과정에서 OTP/BEAM의 버그 하나와 문서화되지 않은 기능 하나를 발견했다고 밝혔다. 초기 부팅이 오래 걸렸던 원인과 관련해 Popcorn의 stdin 구현은 읽을 때마다 Atomics를 이용해 10ms 동안 대기했으며, 약 1,300번 읽는 과정에서 시간이 누적됐다. 패치 전체 목록도 공개돼 있다.
댓글 (0)
로그인하면 이 기사에 내 생각을 남길 수 있어요