
구독 피로를 해결하기 위해 로컬 우선(Local-First) 미디어 스위트를 구축한 이유 (및 내부 작동 원리)
요약
SaaS 구독 모델과 데이터 보안 문제를 해결하기 위해 로컬 우선(Local-first) 미디어 처리 스위트인 Formatif을 구축한 사례를 다룹니다. Electron, Node.js, ONNX Runtime을 활용하여 무거운 미디어 파이프라인과 머신러닝 모델을 데스크톱 환경에서 효율적으로 실행하는 아키텍처를 설명합니다.
핵심 포인트
- Electron과 IPC를 활용한 프론트엔드와 네이티브 프로세스 간의 엄격한 관심사 분리
- FFmpeg, ImageMagick 등 검증된 바이너리를 안전한 인자 배열 방식으로 래핑
- ONNX Runtime을 통한 로컬 신경망 처리 및 미디어 파이프라인 구현
- 배치 큐 시스템을 통한 시스템 리소스 최적화 및 프로세스 오케스트레이션
우리 모두 그런 경험이 있습니다. 프로덕션에 배포하기 전에 비디오 에셋을 빠르게 압축해야 하거나, 목업에서 배경을 제거해야 할 때가 있습니다. 온라인 도구를 검색하고, 파일을 업로드하고, 서버 대기열에서 기다리다 보면 "계정을 생성하고 월 12달러를 결제하세요"라는 장벽에 부딪힙니다. 더 나쁜 것은, 민감한 클라이언트 데이터를 다루고 있다면, 당신은 그 데이터를 무작위의 제3자 서버에 넘겨준 셈이 된다는 것입니다.
저는 "모든 것이 SaaS(Software as a Service)인" 모델에 지쳤습니다. 저는 빠르고, 완전히 프라이빗하며, 제 컴퓨터에서 실행되는 유틸리티를 원했습니다.
그래서 저는 Formatif를 구축했습니다. 이는 두 개의 로컬 우선(Local-first) 애플리케이션으로 나뉜 네이티브 데스크톱 미디어 처리 스위트입니다: Formatif Pro(59개의 클래식 미디어 유틸리티)와 Formatif AI(로컬 신경망 처리).
여기서 저는 Electron, Node.js, 그리고 ONNX Runtime을 사용하여 UI를 차단하지 않으면서 어떻게 무거운 미디어 파이프라인(Media pipelines)과 머신러닝(Machine learning) 모델을 데스크톱으로 가져왔는지 심층적으로 살펴보겠습니다.
핵심 데스크톱 아키텍처
웹 개발자로서 저의 본래 서식지는 JavaScript/TypeScript 생태계입니다. 이를 여러 플랫폼에서 구축하기 위해 저는 Electron, React, 그리고 Tailwind CSS를 사용했습니다.
데스크톱 아키텍처의 근본적인 규칙은 엄격한 관심사 분리(Separation of concerns)입니다. React 프론트엔드는 사용자 상호작용과 배치(Batch) 상태를 처리하며, Electron 메인 프로세스는 오프라인 작업 브로커(Job broker) 역할을 하여 무거운 작업을 격리된 네이티브 프로세스로 전달합니다.
흐름: React UI (파일 경로 및 프리셋) ➔ Preload IPC Bridge ➔ Electron 메인 프로세스 (제어된 배치 큐) ➔ 네이티브 바이너리(Native Binaries) 및 ONNX 추론 엔진(Inference Engines).
심층 분석: Formatif Pro 내부 (클래식 유틸리티)
Formatif Pro는 검증된 미디어 바이너리인 FFmpeg, FFprobe, ImageMagick, 그리고 Sharp를 Electron의 프로세스 간 통신(IPC, Inter-Process Communication) 레이어 뒤로 래핑(Wrap)합니다.
1. 안전한 프로세스 오케스트레이션 (Safe Process Orchestration)
한꺼번에 20개의 거대한 인코딩 작업을 실행하여 사용자의 컴퓨터를 멈추게 하는 대신, Formatif은 배치(Batch)를 제어된 순차적 큐(Sequential Queue)로 취급합니다.
셸 인젝션(Shell Injection)과 인용 부호 버그(Quoting Bugs)를 방지하기 위해, 메인 프로세스는 거대한 명령 문자열을 절대 실행하지 않습니다. 대신, 깨끗한 인자 배열(Argument Arrays)을 구축하고 execFile 스타일의 자식 프로세스(Child Processes)를 사용하여 바이너리를 실행합니다:
// 네이티브 바이너리 러너(Native Binary Runner)의 단순화된 모습
function runBinaryJob({ taskId, binary, args, duration, send }) {
return new Promise((resolve, reject) => {
...
2. UI에 실제 진행 상황 제공하기 (Stderr 트릭)
FFmpeg 래퍼(Wrapper)를 만들어 본 적이 있다면, FFmpeg가 실행 진행 상황을 stdout이 아닌 stderr를 통해 보고한다는 사실을 알고 있을 것입니다.
사용자에게 가짜 로딩 스피너(Loading Spinner) 대신 실제 진행률 표시줄(Progress Bar)을 제공하기 위해, 앱은 들어오는 데이터 스트림(Data Stream)을 즉석에서 파싱합니다:
child.stderr?.on("data", chunk => {
const seconds = parseFfmpegTime(chunk.toString()); // "time=00:01:23.45" 형식을 찾음
...
사용자가 취소(Cancel)를 누르면, 메인 프로세스는 activeProcesses 맵(Map)에서 작업 ID(Task ID)를 가져와 다음과 같이 깔끔하게 실행합니다:
child.kill("SIGTERM");
이는 즉시 FFmpeg 프로세스를 중단시키고, 고아 프로세스(Orphaned Processes)가 계속해서 CPU 자원을 소모하는 것을 방지합니다. 또한 처리 과정 중에 생성되었을 수 있는 임시 부분 파일(Temporary Partial Files)들을 애플리케이션이 정리할 수 있게 해줍니다.
심층 분석: Formatif AI 내부 (로컬 신경망 처리)
Formatif AI는 이미지 업스케일링(Upscaling), 배경 제거(Background Removal), 디테일 복원(Detail Restoration)과 같은 복잡한 신경망 작업(Neural Tasks)을 처리합니다. 파일을 비용이 많이 드는 클라우드 API로 라우팅하는 대신, ONNX Runtime을 통해 모델을 로컬에서 실행합니다.
1. 중앙 집중식 세션 캐싱 (Centralized Session Caching)
ONNX 머신러닝 세션(Machine Learning Session)을 인스턴스화(Instantiating)하는 것은 매우 비용이 많이 드는 작업입니다. 런타임(Runtime)이 모델 그래프(Model Graph)를 컴파일하고 하드웨어 메모리를 할당해야 하기 때문입니다.
배치(Batch) 내의 모든 이미지에 대해 막대한 시작 지연(Startup Delay)이 발생하는 것을 방지하기 위해, Formatif AI는 중앙 집중식 세션 캐시(Session Cache)를 구현합니다.
import onnxruntime as ort
_sessions = {}
...
첫 번째 이미지는 초기화 비용을 지불하지만, 이후의 모든 이미지는 이미 로드된 모델 세션(Model Session)을 재사용할 수 있습니다. 이는 대규모 배치를 처리할 때 처리량(Throughput)을 극적으로 향상시키며 불필요한 GPU 메모리 재할당을 방지합니다.
2. 하드웨어 폴백(Hardware Fallbacks) 및 동적 정밀도(Dynamic Precision)
모든 사용자가 고사양의 전용 GPU를 보유하고 있는 것은 아닙니다.
엔진은 시작 시 시스템 하드웨어를 동적으로 조회하여 최적화된 실행 제공자(Execution Provider) 폴백 체인을 구축합니다. DirectML (Windows), CoreML (macOS) 또는 CUDA와 같은 네이티브 가속기를 우선시하며, 필요한 경우 CPU로 유연하게 폴백(Fallback)합니다.
def provider_chain(preferred):
available = ort.get_available_providers()
...
이를 통해 동일한 애플리케이션이 전용 GPU를 갖춘 고사양 워크스테이션부터 CPU 추론(Inference)에 전적으로 의존하는 엔트리급 기기에 이르기까지 광범위한 하드웨어 구성에서 효율적으로 실행될 수 있습니다.
안정성을 더욱 향상시키기 위해, 엔진은 런타임(Runtime)에 모델 메타데이터를 검사하고 FP16과 FP32 정밀도 모드 사이를 동적으로 전환합니다. FP16은 메모리 소비를 줄이고 지원되는 하드웨어에서 추론 속도를 크게 높일 수 있는 반면, FP32는 네이티브 반정밀도(Half-precision) 가속 기능이 없는 시스템에서 최대의 호환성을 제공합니다.
이러한 적응형 접근 방식은 메모리 부족(Out-of-memory, OOM) 오류를 방지하는 동시에 사용자가 자신의 하드웨어가 안전하게 제공할 수 있는 최상의 성능을 얻을 수 있도록 돕습니다.
3. 실제 엔지니어링: 타일드 추론(Tiled Inference)
모델이 512px 크기의 작은 이미지 데모에서는 완벽하게 작동할 수 있지만, 실제 사용자들은 4K 사진과 거대한 스크린샷을 입력값으로 던집니다.
대형 이미지를 업스케일링(Upscaling) 모델에 한꺼번에 통과시키면 가용 메모리가 빠르게 고갈되어 일반적인 노트북에서 메모리 부족(Out-of-memory, OOM) 충돌이 발생할 수 있습니다.
이를 해결하기 위해 Formatif AI는 타일 기반 추론 (tiled inference) 전략을 활용합니다. 큰 이미지는 서로 겹치는 영역으로 나뉘며, 각 타일은 로컬 모델에 의해 독립적으로 처리된 후, 결과물들이 다시 하나로 혼합되어 끊김 없는 최종 이미지를 생성합니다.
이러한 접근 방식은 피크 메모리 (peak memory) 사용량을 획기적으로 줄이는 동시에, 소비자용 하드웨어에서도 고해상도 이미지를 안정적으로 처리할 수 있게 해줍니다.
개발자로서 배운 점
로컬 우선 (local-first) 애플리케이션을 구축하는 것은 자원 최적화에 대해 생각하는 방식을 근본적으로 바꾸어 놓았습니다.
클라우드를 위한 소프트웨어를 작성할 때는 더 많은 RAM을 할당하거나 인프라를 확장함으로써 성능 문제를 해결할 수 있는 경우가 많습니다. 하지만 데스크톱 애플리케이션에는 그런 사치가 없습니다. 당신은 사용자의 기기에서 손님일 뿐입니다.
이는 CPU 사용량을 존중하고, 파일 잠금 (file-locking)의 예외 상황을 처리하며, 메모리를 신중하게 관리하고, 생성하는 모든 프로세스를 정리해야 함을 의미합니다. 서버에서는 눈에 띄지 않을 수 있는 작은 비효율성이 사용자의 개인 자원을 소모할 때는 매우 두드러지게 나타날 수 있습니다.
아이러니하게도, 저는 이 제품의 가장 큰 사용자 중 한 명이 되었습니다.
UI 비디오를 압축하거나, SVG 에셋을 최적화하거나, 이미지 배경을 제거하거나, 업데이트를 게시하기 전에 스크린샷을 업스케일링 (upscale)해야 할 때마다 저는 결국 Formatif을 먼저 사용하게 됩니다. 이 도구는 예전에 제 일상적인 워크플로의 일부였던 웹 기반 도구 모음들을 완전히 대체했습니다.
여러분의 의견은 어떠신가요?
웹 기술로 데스크톱 애플리케이션을 구축하는 것은 오늘날 그 어느 때보다 실용적이며, 특히 연산 집약적인 작업이 UI 스레드 (UI thread)로부터 적절히 격리되어 있다면 더욱 그렇습니다.
미디어 처리에 대한 로컬 우선 접근 방식을 탐구하는 데 관심이 있다면, formatif.net에서 Formatif을 무료로 체험해 보실 수 있습니다. 등록이나 신용카드는 필요하지 않습니다.
로컬 우선 (local-first) 움직임에 대해 어떻게 생각하시나요?
데스크톱 애플리케이션에서 ONNX Runtime, Electron 또는 로컬 AI 모델을 실험해 본 적이 있으신가요? 여러분의 경험을 듣고 댓글에서 함께 논의하고 싶습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기