Electron IPC 통신 최적화 — 대용량 데이터 주고받을 때 주의점

게시일: 2025년 4월 29일 · 15분 읽기

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개선
1MB50ms5ms10배
10MB520ms30ms17배
100MB5200ms250ms20배

해결 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배 성능 향상을 볼 수 있다.

iL
ian.lab

실무 개발자입니다. 현장에서 겪은 문제와 해결 과정을 기록합니다. 오류 제보는 연락처로 보내주세요.