Flutter vs Electron: 메모리 점유율 80% 절감한 데스크탑 앱 마이그레이션 분석

최근 사내 메신저 클라이언트를 리팩토링하면서 가장 치열하게 고민했던 주제는 바로 "기존 Electron 기반의 데스크탑 앱을 유지할 것인가, 아니면 Flutter로 완전히 전환할 것인가"였습니다. 2025년 현재, Electron은 여전히 VS Code나 Slack 같은 거대 앱들의 표준이지만, 저희가 겪고 있던 고질적인 문제는 '무거운 리소스 점유율'이었습니다. 사용자들은 백그라운드에 메신저만 켜놔도 램을 500MB씩 잡아먹는 상황을 더 이상 용납하지 않았습니다. 이 글에서는 실제 Electron 프로젝트를 운영하며 겪었던 한계와, Flutter로 전환하며 얻은 성능적 이점, 그리고 그럼에도 불구하고 Electron이 필요한 엣지 케이스를 엔지니어 관점에서 상세히 분석합니다.

아키텍처의 근본적 차이: Chromium vs Impeller

이 두 프레임워크의 성능 차이는 단순히 언어의 차이(JavaScript vs Dart)에서 오는 것이 아닙니다. 렌더링 엔진과 아키텍처의 근본적인 접근 방식이 다르기 때문입니다.

Electron은 본질적으로 Chromium 브라우저 인스턴스와 Node.js 런타임을 번들링합니다. 즉, 간단한 "Hello World" 앱을 실행하더라도, 거대한 웹 브라우저 엔진 전체가 메모리에 로드되어야 합니다. 각 창(Window)마다 별도의 렌더러 프로세스가 생성되므로 멀티 윈도우 앱을 만들 경우 리소스 사용량은 선형적으로가 아니라 기하급수적으로 늘어날 위험이 있습니다.

Note: Electron은 Main Process(Node.js)와 Renderer Process(Chromium)가 IPC(Inter-Process Communication)를 통해 통신하는 구조입니다. 이 직렬화/역직렬화 과정에서 대용량 데이터 전송 시 병목이 발생할 수 있습니다.

반면 Flutter는 Skia(또는 최신 버전의 Impeller) 그래픽 엔진을 사용하여 UI를 직접 그립니다. OS의 네이티브 캔버스 위에 픽셀 단위로 직접 렌더링하기 때문에, DOM(Document Object Model)을 조작하는 오버헤드가 없습니다. 또한 Dart 코드는 AOT(Ahead-of-Time) 컴파일되어 네이티브 ARM64/x64 기계어로 변환되므로 실행 속도 면에서 인터프리터 언어 기반인 Electron보다 유리할 수밖에 없습니다.

Electron 최적화의 한계와 실패 경험

초기에는 Electron을 버리지 않고 최적화를 시도했습니다. Webpack 번들 사이즈를 줄이고, `ContextBridge`를 통해 불필요한 모듈 로딩을 막아보려 했지만, 'Idle 상태의 메모리 점유율'은 해결되지 않았습니다. 빈 윈도우 하나만 띄워도 기본적으로 약 120MB~150MB의 RAM을 소비했습니다. 저사양 노트북을 사용하는 클라이언트 환경에서 이는 치명적이었습니다. 특히, 다수의 이미지 썸네일을 로드하는 채팅방 리스트에서 스크롤 버벅임(Jank) 현상을 잡기 위해 가상 스크롤(Virtual Scrolling)을 구현해야 했지만, DOM 노드 수가 수천 개를 넘어가면 GC(Garbage Collection)가 빈번하게 발생하여 프레임 드랍을 막기 어려웠습니다.

IPC 및 네이티브 연동 코드 비교

두 프레임워크가 시스템 리소스에 접근하는 방식의 차이를 코드로 살펴보겠습니다. Electron은 Node.js API를 직접 사용할 수 있어 강력하지만 보안 설정이 복잡하고, Flutter는 Platform Channel을 통해야 하므로 코드가 다소 길어질 수 있습니다.

다음은 시스템 트레이(System Tray)에 아이콘을 생성하고 클릭 이벤트를 처리하는 로직의 차이입니다.

// 1. Electron (Main Process)
// Node.js 환경에서 직접 트레이 생성. 간결하지만 런타임이 무거움.
const { app, Menu, Tray } = require('electron');

let tray = null;
app.whenReady().then(() => {
  tray = new Tray('icon.png');
  const contextMenu = Menu.buildFromTemplate([
    { label: 'Item1', type: 'radio' },
    { label: 'Item2', type: 'radio' }
  ]);
  tray.setToolTip('Electron App');
  tray.setContextMenu(contextMenu);
});

// 2. Flutter (Dart)
// 시스템 API 호출을 위해 패키지(system_tray)를 사용하거나 MethodChannel 구현 필요.
// 아래는 일반적인 구현 패턴입니다.
import 'package:system_tray/system_tray.dart';

Future<void> initSystemTray() async {
  final SystemTray systemTray = SystemTray();
  
  // 초기화 및 아이콘 설정
  await systemTray.initSystemTray(
    iconPath: getTrayIconPath(),
  );

  final Menu menu = Menu();
  await menu.buildFrom([
    MenuItemLabel(label: 'Show', onClicked: (menuItem) => appWindow.show()),
    MenuItemLabel(label: 'Exit', onClicked: (menuItem) => appWindow.close()),
  ]);

  await systemTray.setContextMenu(menu);
  
  // 시스템 이벤트 핸들링 등록
  systemTray.registerSystemTrayEventHandler((eventName) {
    if (eventName == kSystemTrayEventClick) {
      systemTray.popUpContextMenu();
    }
  });
}

Electron은 웹 개발자에게 익숙한 JS 문법으로 데스크탑 기능을 제어할 수 있다는 엄청난 장점이 있습니다. 반면 Flutter는 Dart라는 언어를 새로 배워야 하고, 데스크탑 전용 플러그인이 모바일보다 적을 수 있다는 단점이 있습니다. 하지만 위 코드에서 볼 수 있듯이 Flutter의 생태계도 2025년 기준 매우 성숙해져 있어, 대부분의 기능은 패키지로 해결 가능합니다.

성능 벤치마크: 메모리 및 빌드 사이즈

동일한 기능을 가진 'Markdown 메모장' 앱을 두 프레임워크로 각각 빌드하여 테스트한 결과입니다. 테스트 환경은 macOS Sonoma, Apple M2 Pro, 16GB RAM입니다.

지표 (Metric) Electron (v30.0) Flutter (v3.22) 개선율
설치 파일 크기 (DMG/Zip) ~180 MB ~45 MB 75% 감소
Idle 메모리 사용량 145 MB 28 MB 80% 절감
앱 실행 속도 (Cold Start) 1.8s 0.6s 3배 빠름
CPU 점유율 (Idle) 0.5% ~ 1.2% 0.0% ~ 0.1% 안정적

결과를 분석해보면, Flutter의 압승입니다. 특히 배포 파일 사이즈가 45MB 수준으로 줄어든 것은 사용자 경험에 큰 영향을 미쳤습니다. Electron은 Chromium 바이너리를 포함해야 하므로 아무리 최적화해도 100MB 이하로 줄이기가 매우 어렵습니다. 반면 Flutter는 필요한 엔진 코드만 컴파일하여 포함하므로 훨씬 가볍습니다.

Flutter 데스크탑 공식 문서 확인하기

주의사항: 언제 Electron을 써야 할까?

그렇다면 모든 상황에서 Flutter가 정답일까요? 절대 아닙니다. 엔지니어로서 기술을 선택할 때는 트레이드오프를 고려해야 합니다. 다음과 같은 상황에서는 여전히 Electron이 더 나은 선택일 수 있습니다.

SEO 및 웹 호환성: 기존에 이미 React나 Vue로 작성된 거대한 웹 애플리케이션이 있다면, 이를 Flutter로 포팅하는 것은 재앙에 가깝습니다. Electron을 사용하면 기존 웹 코드를 99% 재사용할 수 있습니다.
  1. 기존 웹 자산 활용: 팀이 웹 기술(JS/CSS)에 매우 능숙하고 Dart 학습 곡선을 감당할 여력이 없는 경우.
  2. OS 깊숙한 제어: Electron은 Node.js의 방대한 생태계(ffi-napi 등)를 이용해 OS의 저수준 API에 접근하기가 Flutter보다 상대적으로 수월하고 레퍼런스가 많습니다.
  3. 빠른 UI 변경: Flutter는 UI 변경 시 재빌드 및 배포가 필요하지만(Code Push 같은 기능이 제한적), Electron은 원격 웹 리소스를 로드하는 방식으로 앱 업데이트 없이 UI를 즉시 변경하는 전략을 취하기 쉽습니다.

결론

데스크탑 앱 개발 환경에서 Flutter는 성능과 리소스 효율성 측면에서 Electron의 강력한 대안이 되었습니다. 특히 메모리 누수와 무거운 바이너리 크기로 고통받고 있다면 Flutter로의 전환은 충분한 투자 가치가 있습니다. 하지만 기존 웹 프로젝트의 마이그레이션 비용과 팀의 기술 스택을 고려하여, '성능'이 우선인지 '생산성(코드 재사용)'이 우선인지 판단해야 합니다. 저희 팀의 경우, 초기 학습 비용을 감수하고 Flutter로 전환한 덕분에 고객 클레임(앱이 무겁다는 불만)을 90% 이상 줄일 수 있었습니다.

OlderNewest

Post a Comment