Paper Finder: 무료 AI 기반 연구 도구와 내가 디버깅해야 했던 7초간의 프리징 현상
요약
브라우저 기반 AI 모델을 사용하는 Paper Finder 개발 중 발생한 메인 스레드 프리징 문제를 해결하는 과정을 다룹니다. WASM 추론이 비동기처럼 보여도 실제로는 CPU 집약적인 동기 작업임을 설명하고, Web Worker를 통한 해결책을 제시합니다.
핵심 포인트
- await를 사용해도 CPU 집약적인 WASM 추론은 메인 스레드를 차단할 수 있음
- transformers.js 모델 실행 시 발생하는 7초간의 프리징 현상 분석
- Web Worker를 사용하여 AI 모델 연산을 별도 스레드로 분리하여 해결
- 클라이언트 측 ML 작업 시 Web Worker 사용의 중요성 강조
나는 arXiv, Semantic Scholar, Crossref를 동시에 검색한 다음, 의미론적 관련성(semantic relevance)에 따라 결과를 재정렬하는 무료 도구인 Paper Finder를 만들었습니다.
이 랭킹 시스템은 브라우저에서 완전히 실행되는 작은 AI 모델을 사용하므로, API 비용이나 계정이 필요하지 않습니다. 하지만 이로 인해 고약한 버그가 발생했습니다. 검색창에 타이핑을 하면 최대 7초 동안 화면이 멈출 수 있었습니다.
버그
Paper Finder는 처음에 연도별로 결과를 정렬했습니다. 관련성을 높이기 위해 transformers.js를 통해 Xenova/all-MiniLM-L6-v2를 추가했습니다. 이 모델은 쿼리와 각 결과물을 임베딩(embedding)한 다음, 코사인 유사도(cosine similarity)를 사용하여 순위를 매깁니다.
재정렬이 시작되기 전에 결과가 나타났기 때문에, 나는 AI 작업이 안전하게 "백그라운드"에서 진행되고 있다고 가정했습니다.
그러던 중 한 사용자가 1,350.9 ms의 입력 지연(input delay)을 보여주는 Chrome DevTools 트레이스를 보내왔습니다. 나의 첫 생각은 "그건 AI 때문일 리 없어—비동기(async) 방식이니까"였습니다.
내 생각이 틀렸습니다.
프리징 측정
나는 메인 스레드(main thread)를 50ms 이상 차단하는 태스크를 보고하는 longtask 엔트리 타입과 함께 Chrome의 PerformanceObserver를 사용했습니다:
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log(`Blocked for ${entry.duration}ms`);
...
브라우저의 모델 캐시를 지우고 새로운 검색을 실행한 후, 다음과 같은 결과를 확인했습니다:
{ duration: 7079, start: 13735 }
하나의 태스크가 메인 스레드를 7,079ms 동안 차단했습니다. 그 시간 동안 페이지는 타이핑, 클릭 또는 그 외 어떤 것도 처리할 수 없었습니다.
await가 도움이 되지 않았던 이유
임베딩 호출에는 await를 사용했지만, 그것이 작업을 메인 스레드 밖으로 옮겨주지는 않습니다. await는 네트워크 요청이나 타이머와 같이 진정으로 비동기적인(asynchronous) 작업을 기다리는 동안에만 제어권을 양보할 뿐입니다.
WASM 추론(inference)은 동기적(synchronous) CPU 작업입니다. 이를 Promise로 감싸는 것은 결과를 받는 방식을 바꿀 뿐, 계산이 실행되는 위치를 바꾸지는 않습니다. 모델 초기화와 추론은 여전히 메인 스레드에서 렌더링 및 입력과 경쟁하고 있었습니다.
해결책
나는 모델을 전용 Web Worker로 이동시켰습니다:
// embedder.worker.ts
self.onmessage = async (event) => {
const extractor = await getExtractor();
...
이제 메인 스레드는 워커로 텍스트를 보내고 벡터가 돌아오기를 기다립니다. 공개 인터페이스는 변경할 필요가 없었습니다. 즉, Worker 기반 Promise는 호출자에게 동일하게 보입니다.
콜드 캐시 테스트를 반복한 후:
longtasks: []
긴 작업(long tasks)이 0개였고, 검색창은 내내 반응성을 유지했습니다.
핵심 교훈: await가 메인 스레드를 차단하지 않는다는 의미는 아닙니다. 클라이언트 측 ML 또는 다른 CPU 집약적 작업을 수행할 때는 처음부터 Web Worker를 사용해야 합니다.
소스 코드는 GitHub에 있으며, 라이브 앱은 paperfinder.dev에서 확인할 수 있습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기