Electron IPC 통신 최적화 — 대용량 데이터 주고받을 때 주의점
10MB JSON을 IPC로 보내다가 앱이 1초 멈추는 걸 봤다.
IPC의 성능 문제
간단한 테스트:
// main.ts
const data = {
frames: Array(10000).fill({
timestamp: Date.now(),
waveform: Array(1024).fill(0),
}),
};
ipcMain.handle('get-audio-data', () => {
return data; // 직렬화 → 전송 → 역직렬화
});
// renderer.ts
const data = await ipcRenderer.invoke('get-audio-data');
// ← 이 줄에서 1초 대기
문제:
1. 직렬화 (10MB JSON → 문자열): 200ms
2. 프로세스 간 통신: 600ms
3. 역직렬화 (문자열 → 객체): 200ms
총: 1000ms (1초)
벤치마크
| 데이터 크기 | IPC 시간 | SharedArrayBuffer | 개선 |
|---|---|---|---|
| 1MB | 50ms | 5ms | 10배 |
| 10MB | 520ms | 30ms | 17배 |
| 100MB | 5200ms | 250ms | 20배 |
해결 1: SharedArrayBuffer
메인과 렌더러 프로세스가 같은 메모리를 공유:
// main.ts
const audioData = new Float32Array(1024);
for (let i = 0; i < 1024; i++) {
audioData[i] = Math.random();
}
ipcMain.handle('get-audio-shared', () => {
return audioData.buffer; // ArrayBuffer 전송 (복사 X)
});
// renderer.ts
const buffer = await ipcRenderer.invoke('get-audio-shared');
const audioData = new Float32Array(buffer); // 공유 메모리
console.log(audioData[0]); // 즉시 접근
주의: SecurityError가 발생할 수 있다. 해결:
// main.ts
const mainWindow = new BrowserWindow({
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
preload: path.join(__dirname, 'preload.ts'),
// ← contextIsolation: true면 SharedArrayBuffer 사용 불가
// 해결: contextIsolation: false (권장 아님)
// 또는 MessagePort 사용
},
});
해결 2: MessagePort
contextIsolation과 호환:
// main.ts
const { port1, port2 } = new MessageChannel();
ipcMain.handle('get-audio-port', () => {
// port2의 소유권을 renderer로 전이
return { port: port2 }; // 실제로는 port2.transferList로 전송
});
// renderer.ts (preload)
contextBridge.exposeInMainWorld('audioAPI', {
getAudioPort: async () => {
const { port } = await ipcRenderer.invoke('get-audio-port');
return new Promise(resolve => {
port.onmessage = (event) => {
const audioData = new Float32Array(event.data);
resolve(audioData);
};
});
},
});
해결 3: Streaming (매우 대용량)
100MB 이상 데이터:
// main.ts
async function* generateAudioChunks(totalSize: number) {
const chunkSize = 1024 * 1024; // 1MB chunk
for (let i = 0; i < totalSize; i += chunkSize) {
yield Buffer.alloc(Math.min(chunkSize, totalSize - i));
}
}
ipcMain.handle('stream-audio-start', async (event, totalSize) => {
let bytesStreamed = 0;
for await (const chunk of generateAudioChunks(totalSize)) {
event.sender.send('audio-chunk', chunk);
bytesStreamed += chunk.length;
// UI 업데이트
event.sender.send('stream-progress', {
bytesStreamed,
totalSize,
percent: (bytesStreamed / totalSize) * 100,
});
}
event.sender.send('stream-complete');
});
// renderer.ts
ipcRenderer.on('audio-chunk', (event, chunk) => {
audioBuffer.push(chunk);
});
ipcRenderer.on('stream-progress', (event, { percent }) => {
updateProgressBar(percent);
});
실전 패턴
데이터 크기별 권장:
< 1MB: 일반 IPC (invoke/send)
1-100MB: SharedArrayBuffer 또는 MessagePort
> 100MB: Streaming
결론
IPC는 프로세스 간 통신이므로 오버헤드가 있다. 대용량 데이터는 SharedArrayBuffer로 공유 메모리를 사용하자. 최고 20배 성능 향상을 볼 수 있다.