모두가 Electron을 쓰라고 했습니다. 한 번 만들어 어디서나 실행하고, 빠르게 움직이라고요. 저희는 반대로 했습니다. Apple에서는 SwiftUI, Android에서는 Jetpack Compose, Windows에서는 C++/WinRT, 픽셀이 시계에 맞춰 나가야 하는 곳에서는 순수 Metal과 Vulkan입니다. 제품 하나를 위해 네이티브 클라이언트 네 개를 만들며 배운 것을 소개합니다.
쉬운 길과 옳은 길
2024년에 원격 데스크톱 앱을 처음부터 만들고 있다고 하면 첫 질문은 늘 같습니다. "그냥 Electron 쓰면 안 돼요?"
타당한 질문입니다. Electron은 VS Code, Slack, Discord, Figma의 데스크톱 래퍼 등 세상에서 가장 성공한 앱 몇몇을 움직입니다. 한 번 작성해 어디에나 배포할 수 있습니다. 웹 개발자가 하룻밤에 데스크톱 개발자가 됩니다. 출시 속도의 이점은 실재합니다.
저희는 숫자를 보고 트레이드오프를 따져 본 뒤 가장 어려운 길을 택했습니다. 모든 플랫폼에서 완전한 네이티브입니다. iOS와 macOS에서는 SwiftUI, Android에서는 Jetpack Compose, Windows에서는 C++/WinRT, Apple의 렌더링은 Metal, 나머지는 Vulkan, 중심에는 자체 제작한 Go 서버가 있습니다.
고생을 즐겨서가 아닙니다. 어떤 날은 그런 기분이 들긴 하지만요. 이런 종류의 앱에서는 네이티브가 사치가 아니라 필수이기 때문입니다.
Electron의 대가는 실재합니다
Electron이 실제로 무엇인지부터 짚겠습니다. 데스크톱 앱인 척하는 Chromium 브라우저 창입니다. 비판이 아니라 아키텍처 설명입니다. 많은 애플리케이션에는 그걸로 충분합니다.
하지만 원격 데스크톱이라면요? 다른 컴퓨터의 픽셀을 스트리밍해 실시간으로 렌더링하면서, 모든 입력 이벤트를 잡아 다시 보내야 합니다. 지연은 있으면 좋은 게 아니라 제품 그 자체입니다.
두 방식을 모두 프로토타입으로 만들어 측정한 결과입니다.
- 메모리 — 네이티브 클라이언트 정상 상태: 약 45MB. Electron 프로토타입: 약 380MB. 프레임을 하나도 디코딩하기 전, 유휴 상태에서 이미 8.4배 차이입니다.
- 콜드 스타트 — 네이티브 macOS 앱은 200ms 미만으로 콜드 스타트합니다. Electron 프로토타입은 2~3초가 걸렸습니다. 하루에 스무 번 여는 도구라면 이 차이가 쌓여 실제 불편이 됩니다.
- 배터리 — 배터리로 동작하는 Mac에서 Electron 빌드는 유휴 연결 중에 대략 3배의 전력을 썼습니다. Chromium은 싸게 쉬지 못합니다.
380MB는 Electron이 부풀어서가 아닙니다. V8, GPU 프로세스, 유틸리티 프로세스, 브라우저 엔진 전체까지, Chromium이 제 할 일을 하는 겁니다. 인상적인 엔지니어링입니다. 다만 다른 문제를 풀기 위해 설계된 엔지니어링일 뿐입니다.
기능보다 프레임이 중요합니다
원격 데스크톱은 프레임 하나하나가 중요한 몇 안 되는 앱 분야입니다. 프레임이 빠지면 사용자는 느낍니다. 입력 지연이 16ms 늘면 디자이너는 부드러운 곡선을 그릴 수 없습니다. 32ms 늘면 게이머는 게임을 할 수 없습니다.
네이티브 렌더링에서는 GPU와 직접 대화합니다. Apple 플랫폼에서는 VideoToolbox로 하드웨어 비디오 디코딩에 직접 접근합니다. 네트워크에서 H.265 프레임을 받아 하드웨어로 디코딩하고 표시합니다. 프레임이 메인 메모리를 한 번도 거치지 않습니다. 최신 Mac에서 디코딩부터 표시까지의 파이프라인은 4ms 미만입니다.
Electron에서는 같은 프레임이 Chromium의 컴포지터를 지나 IPC 브리지를 거치고, Skia가 그리고, 다시 OS가 합성합니다. 단계마다 지연이 더해지고 지터도 더해집니다. 그리고 지터, 즉 일정하지 않은 프레임 타이밍이야말로 평균 FPS가 괜찮아 보여도 원격 데스크톱을 어딘가 이상하게 느끼게 만드는 원인입니다.
순수주의자라서 네이티브를 택한 게 아닙니다. 문제의 물리가 요구했기 때문입니다. LAN에서 약 8ms의 처리 시간(나머지는 디스플레이 자체의 60Hz 주사율 주기)이면 중간에 브라우저가 들어갈 여지가 없습니다.
네이티브 대 웹 기능 대조표
Electron 대 네이티브 논쟁은 흔히 느낌 싸움에 빠집니다. 그래서 Remio가 모든 세션의 매 순간 의존하는 OS 기능 일곱 가지를 적어 놓고, 런타임별로 각각 어떤지 솔직하게 물었습니다.
| 기능 | 네이티브 | 웹 / Electron |
|---|---|---|
| 60fps 하드웨어 디코딩 | ● VideoToolbox / D3D11VA 직접 | ○ WebCodecs 경유, 지터 |
| 저지연 USB 오디오 | ● CoreAudio / WASAPI | — 샌드박스, 40ms 이상 |
| 키보드 입력 모니터링 | ● CGEvent / SendInput | — 포커스 밖에서는 차단 |
| Metal / D3D11 GPU 접근 | ● 제로 카피 텍스처 | ○ WebGPU, 복사 단계 존재 |
| 로컬 파일 시스템 | ● 전체, OS가 범위 지정 | ○ 선택기 전용, 확인 창 |
| 멀티 윈도, 멀티 디스플레이 | ● OS 네이티브 윈도잉 | ○ BrowserWindow 보정 코드 |
| 백그라운드 유지 | ● LaunchAgent / 서비스 | — 숨기면 일시 중지 |
각 행은 오늘 저희가 제공하는 기능입니다. 웹 열의 각 —는 저희가 직접 고안하고, 문서화하고, 사과해야 했을 우회책입니다. 네이티브로 간다는 건 이 기능들을 폴리필이 아니라 기본 요소로 다룬다는 뜻입니다.
플랫폼의 시민으로서
한 번 작성해 어디서나 실행하자는 쪽이 거의 말하지 않는 사실이 있습니다. 사용자는 압니다.
앱이 시스템 다크 모드 전환을 존중하지 않을 때, 스크롤 물리가 조금 어색할 때, Spotlight에 나타나지 않거나 macOS 창 관리를 제대로 처리하지 못할 때, 컨텍스트 메뉴가 네이티브 메뉴가 아니라 웹 페이지처럼 보일 때 사용자는 알아챕니다.
있으면 좋은 정도가 아닙니다. 사람들이 참고 쓰는 앱과 사랑하는 앱을 가르는 차이입니다. iOS에서는 특히 중요합니다. App Store 심사는 네이티브답지 않은 앱을 거절하며, 그건 당연합니다.
SwiftUI를 쓰면 이 모든 것을 공짜로 얻습니다. Dynamic Type, VoiceOver 접근성, 시스템 햅틱, iPad 멀티태스킹, Mac의 키보드 단축키까지요. 플랫폼 기능을 다시 구현하는 게 아니라 그대로 쓰는 겁니다.
대가도 분명히 있습니다
단점도 솔직히 말하겠습니다. 네이티브로 가는 건 비쌉니다. 돈이 아니라 시간과 복잡성으로요.
저희는 UI 코드베이스를 네 개 관리합니다. 네트워킹과 코덱 계층은 공유하지만(시그널링은 Go, 스트리밍 프로토콜은 C++), 화면에 보이는 모든 픽셀은 플랫폼별 코드입니다. Electron에서 하루 걸리는 기능이 저희는 사흘에서 닷새 걸립니다. 플랫폼마다 맞춰 네 번 만들기 때문입니다.
애니메이션 하나가 iOS에서는 아름답게 동작하는데 Android에서는 깨지고 Windows에서는 버벅이는 주가 있었습니다. 디버깅 매트릭스는 가혹합니다.
그래도 배운 게 있습니다. 가장 어려운 길과 옳은 길은 종종 같은 길입니다. 사용자는 저희 아키텍처를 알지도 신경 쓰지도 않습니다. Remio가 빠르고, 느낌이 맞고, 배터리를 아침거리로 삼키지 않는다는 것만 압니다.
다시 하겠느냐고요
망설임 없이요.
네이티브 방식은 이 용도에서 Electron이 근본적으로 따라올 수 없는 성능을 주었습니다. 각 플랫폼에 어울리는 앱을 주었습니다. 380MB 대신 45MB 미만의 메모리 점유를 주었습니다. LAN에서 약 8ms의 처리 시간(나머지는 디스플레이 자체의 60Hz 주사율 주기)을 주었습니다. 브라우저로 감싼 경쟁 제품은 60ms를 넘습니다.
무엇보다 기술적 해자를 주었습니다. 네 플랫폼에서 네이티브로 만드는 건 충분히 어려워서 대부분의 경쟁자는 하지 않을 겁니다. 그 어려움이 저희의 강점입니다.
원격 데스크톱 분야는 Electron으로 감싼 웹 앱과 수년째 의미 있게 업데이트되지 않은 레거시 네이티브 앱이 지배해 왔습니다. 현대적이면서 네이티브인 것, 오늘의 하드웨어를 염두에 두고 처음부터 만든 것이 들어설 자리가 있습니다.
그게 Remio입니다. 어렵게 만들었습니다. 제대로 할 가치가 있는 일들이 있으니까요.