01 — 왜 멀티프로세스인가
한 줄 답: Electron이 멀티프로세스인 건 선택이 아니라 상속이다 — Chromium이 이미 그런 모델이고, Electron은 그 위에 데스크톱 OS 권한 경계를 얹었다. 그래서 웹 브라우저는 개발자가 모르게 멀티프로세스지만, Electron은 개발자가 직접 설계해야 하는 멀티프로세스다.
Why — 왜 단일 프로세스로 못 만드는가
“브라우저 한 개 + Node 한 개를 한 프로세스에 합치면 안 되나?” — 충분히 합리적인 질문이다. 실제로 2008년 이전의 브라우저는 그랬다. Firefox 3.x, IE 6/7, Safari 초기는 모두 단일 프로세스였다.
그게 망가진 이유 셋.
1) 한 탭이 죽으면 전부 죽었다
Flash 한 개가 segfault → 브라우저 전체 종료. 사용자가 30분 동안 쓰던 다른 탭 19개도 같이 사라진다. 2008년 “sad tab” 아이콘이 등장하기 전, 사용자는 “브라우저 멈춤” 만을 봤다.
2) 한 탭이 메모리를 먹으면 전부 느려졌다
GC가 한 V8 힙을 정리할 때 다른 탭들도 같이 멈췄다. V8은 격리(isolate) 단위로 GC를 돌리지만, 한 프로세스 안의 모든 isolate는 같은 OS 스레드에서 스케줄된다.
3) 한 탭이 뚫리면 시스템이 뚫렸다
Flash 0-day → 브라우저 프로세스 권한 = 사용자 권한 → 디스크 전체 읽기. sandbox가 없으면 XSS의 영향이 origin에 머무르지 않는다.
Chrome이 2008년 “Multi-process architecture” 선언과 함께 등장한 이유다. 이게 The Chromium Projects: Multi-process Architecture 문서의 시작점.
How — Chromium의 사이트 격리를 Electron이 어떻게 물려받았나
Chromium의 프로세스 모델은 4계층이다.
각 Renderer는 별도 OS 프로세스고 별도 sandbox에 갇혀 있다. Renderer가 OS API를 부르고 싶으면 Browser Process에게 IPC로 요청해야 한다.
Electron은 이 구조를 거의 그대로 가져왔다. 단어만 바뀐다.
| Chromium | Electron | 책임 |
|---|---|---|
| Browser Process | Main Process | 앱 라이프사이클, 창 생성, OS API, 추가로 Node.js 전권 |
| Renderer Process | Renderer Process | 웹페이지(HTML/CSS/JS) 렌더링, V8 격리 |
| (없음) | Preload Script | Renderer가 시작되기 직전, 같은 프로세스 안의 특권 컨텍스트에서 1회 실행 |
| Utility Process | UtilityProcess (v22+) | Node 워커 분리용 |
| GPU Process | GPU Process | GPU 가속 (그대로) |
핵심 차이는 두 가지.
- Main 프로세스에 Node.js 전권이 박혀 있다. Chrome의 Browser Process는 내부 코드만 Node-like 권한을 가지지만, Electron의 Main에서는 사용자가 작성한 JS가
fs,child_process,os를 자유롭게 쓴다. - Preload라는 새 개념이 추가됐다. Renderer가 OS에 닿으려면 반드시 Preload를 거치라는 통로다 (04).
What — 한 Electron 앱이 떴을 때 실제로 보이는 프로세스
VS Code 같은 실제 앱을 켜고 ps를 찍어보자.
$ ps aux | grep -i "visual studio code" | awk '{print $11, $12}' | sort -u
.../Electron Framework.framework/Versions/A/Helpers/Electron Helper (GPU).app/...
.../Electron Framework.framework/Versions/A/Helpers/Electron Helper (Plugin).app/...
.../Electron Framework.framework/Versions/A/Helpers/Electron Helper (Renderer).app/...
.../Electron Framework.framework/Versions/A/Helpers/Electron Helper.app/...
.../Visual Studio Code.app/Contents/MacOS/Electron # ← Main리스트 한 줄 한 줄이 별도 OS 프로세스다. macOS Activity Monitor에서 “Code” 하나만 띄워도 보통 7~12개 프로세스가 보인다.
Code (Main) ← 항상 1개
├─ Code Helper (GPU) ← 항상 1개
├─ Code Helper (Plugin) ← Pepper plugin (거의 안 씀)
├─ Code Helper (Renderer) × N ← 창 N개당 N개
└─ Code Helper × M ← Extension Host, Shared Process 등메모리도 그만큼 곱해진다. 한 Renderer가 80~150MB, GPU 100MB, Main 100MB → 빈 창 하나 띄워도 300MB 이상. 이게 07 — 성능 & 메모리에서 자세히 다룰 Electron의 구조적 비용이다.
본질적 차이 — 브라우저는 몰래, Electron은 드러난
같은 멀티프로세스지만 누가 보느냐가 다르다.
웹 개발자에게 프로세스는 존재하지 않는 개념이다 — window 하나가 세계의 전부다. iframe을 쓰면서도 “다른 프로세스에서 돌고 있다”고 의식하지 않는다.
Electron 개발자는 그 의식을 강제로 한다. 파일 3개로 시작한다는 사실 자체가 — main.js, preload.js, renderer.js (또는 index.html) — 그 의식의 표상이다.
이게 가장 큰 인지 부담이다. Node도 쓸 줄 알고 React도 쓸 줄 아는데 Electron이 어렵게 느껴지는 이유의 90%는 여기서 온다.
프로세스 식별 — process.type
어떤 프로세스에서 코드가 도는지 런타임에 확인할 수 있다.
// 어디서든 동일
console.log(process.type)
// Main: 'browser' ← 역사적 이름. "browser process"의 약자
// Renderer: 'renderer'
// Preload: 'renderer' ← 같은 프로세스이므로 'renderer'로 나온다
// Utility: 'utility'주의: preload는 별도 type을 갖지 않는다. preload는 Renderer 프로세스 안의 isolated world에서 도는 코드이기 때문에 process.type === 'renderer'다. preload인지 메인 월드인지 구분하려면 process.contextIsolated 같은 다른 플래그를 본다.
// preload.js
console.log(process.type) // 'renderer'
console.log(process.contextIsolated) // true (디폴트, v12+)
console.log(typeof window.require) // 'undefined' (sandbox ON일 때)What-if — 단일 프로세스로 만들면 어떻게 될까
가설로 그려보자.
| 상황 | 단일 프로세스라면 | 멀티프로세스이면 |
|---|---|---|
| 사용자가 거대한 PDF를 렌더링 | UI 전체 freeze | 그 창만 느려짐, 다른 창·메뉴 멀쩡 |
| 외부 사이트에서 JS 에러 | 앱 전체 죽음 | 그 Renderer만 죽고 자동 복구 가능 |
악성 광고 코드가 fs.readFile('/etc/passwd') 시도 | 그대로 읽힘 | sandbox에 막힘 |
| 한 페이지가 메모리 누수 | 앱 전체가 천천히 OOM | 그 페이지 새로고침으로 해결 |
| Chromium upgrade로 V8 crash | 앱이 못 뜸 | Renderer만 crash, Main은 살아 재시작 시도 가능 |
그리고 반대 비용도 있다. 한 창당 80MB·시작 100ms·IPC 직렬화 오버헤드. Electron이 가진 모든 비용은 이 모델의 그림자다. 비용을 알면 어디서 줄일지도 보인다 (07).
Insight — 사이트 격리는 2017년에 완성된 모델
흥미로운 디테일: Chromium의 Site Isolation은 2017년에야 디폴트가 됐다. 그 전까지는 같은 origin 안의 iframe은 부모와 같은 프로세스에서 돌았다.
그 변화의 트리거가 — Spectre/Meltdown (2018년 1월 공개)이다. CPU의 speculative execution을 이용해 같은 프로세스의 메모리를 읽을 수 있다는 게 드러난 순간, 브라우저는 origin마다 프로세스를 다르게 두지 않으면 안 되게 됐다. 이게 Site Isolation의 완전 활성화 시점이다.
Electron은 그 변화를 자동으로 물려받았다. v6 (2019)부터 Site Isolation이 디폴트로 켜진 Chromium을 번들로 가져왔고, v20 (2022)부터는 sandbox도 디폴트가 됐다. 이게 Electron의 보안 디폴트가 점점 조여지는 방향으로 가는 큰 배경이다.
즉, 멀티프로세스 모델은 “성능·안정성” 문제가 아니라 “근본적 메모리 격리” 문제가 됐다. 이걸 알면 왜 sandbox와 contextIsolation을 끄지 말라는 권고가 그렇게 강한지 이해된다 — 끄는 순간 Spectre급 사이드채널이 다시 열린다.
요약
멀티프로세스는 Chromium에서 시작된 보안·안정성·반응성의 답이고, Electron은 그걸 데스크톱 OS 권한 경계로 확장했다. 핵심 차이는 — 웹 개발자는 모르고 쓰는 멀티프로세스가, Electron에서는 개발자가 직접 설계해야 하는 멀티프로세스가 된다는 것. 그래서 다음 세 문서가 이어진다 — Main(02)·Renderer(03)·Preload(04)의 역할 분담. 이 셋의 분리 이유를 모르면 Electron 코드는 반드시 어디선가 무너진다.