# 자바스크립트로 만든 오프라인 시맨틱 검색 엔진: DB·API 없이 빠르고 간편하게 구현하는 방법

---

# # 서론 – “검색이 느려요?” 라는 문제를 해결하고 싶다면

웹 서비스나 데스크톱 애플리케이션을 개발할 때, **검색 기능**은 거의 필수입니다. 그런데 서버에 요청을 보내야 하거나, 별도의 데이터베이스를 구축해야 하는 경우가 많아 **속도 지연**과 **운영 비용**이 발생합니다. 특히 인터넷 연결이 불안정한 환경에서는 검색 자체가 불가능해질 수도 있죠.

이런 문제를 겪고 있던 개발자들이 최근 Reddit *r/programming*에 “I built an offline semantic search engine in JS (no DB, no APIs)” 라는 글을 올려 큰 관심을 모았습니다. 이번 포스트에서는 해당 프로젝트의 핵심 아이디어와 구현 방법을 **초보자도 이해할 수 있도록** 단계별로 풀어보겠습니다.

---

# # 1. 시맨틱 검색이란? – 단어가 아닌 의미를 찾는다

전통적인 키워드 매칭 검색은 입력한 문자열과 정확히 일치하는 문서를 찾아줍니다. 하지만 사용자가 “노트북 배터리 오래 가는 법”이라고 검색했을 때, “배터리 수명 연장 팁” 같은 의미가 비슷한 문서를 놓치기 쉽습니다.

**시맨틱 검색**은 텍스트를 벡터(숫자 배열)로 변환해 의미적 거리를 계산함으로써, **문맥과 의미가 비슷한 결과**를 반환합니다. 최근에는 BERT, Sentence‑Transformers 같은 사전 학습 모델이 주로 사용되지만, 이번 프로젝트에서는 **브라우저에서 바로 실행 가능한 작은 임베딩 모델**을 활용했습니다.

---

# # 2. 오프라인 검색 엔진 구현 원리 – DB·API 없이도 가능한 이유

## # 2‑1. 데이터 준비와 전처리

1. 검색 대상 문서를 JSON 혹은 텍스트 파일 형태로 로컬에 저장합니다.

2. 각 문서를 문장 단위로 나누고, 불용어(stop words)를 제거합니다.

## # 2‑2. 임베딩 생성

- `@tensorflow/tfjs` 와 `@tensorflow-models/universal-sentence-encoder` 를 사용해 **문장 임베딩**을 생성합니다.

- 모델은 브라우저에 다운로드된 뒤 캐시되므로, 최초 실행 이후에는 **네트워크 요청이 없습니다**.

```
js

import * as use from '@tensorflow-models/universal-sentence-encoder';

async function getEmbeddings(sentences) {

  const model = await use.load();

  const embeddings = await model.embed(sentences);

  return embeddings.array();   // 2‑D 배열 반환

}

```

## # 2‑3. 코사인 유사도 계산

검색어를 같은 방법으로 임베딩한 뒤, 저장된 문서 임베딩과 **코사인 유사도**를 구합니다. 가장 높은 점수를 가진 상위 N개의 문서를 결과로 반환합니다.

```
js

function cosineSimilarity(a, b) {

  const dot = a.reduce((sum, v, i) => sum + v * b[i], 0);

  const normA = Math.sqrt(a.reduce((s, v) => s + v * v, 0));

  const normB = Math.sqrt(b.reduce((s, v) => s + v * v, 0));

  return dot / (normA * normB);

}

```

## # 2‑4. 인덱스 저장 방식

임베딩 결과는 **IndexedDB** 혹은 로컬 파일(`.json`)에 저장합니다. 이렇게 하면 페이지를 새로 고침하거나 애플리케이션을 재시작해도 **데이터를 다시 계산할 필요가 없습니다**.

---

# # 3. 주요 코드와 핵심 포인트 – 초보자를 위한 상세 해설

| 단계 | 핵심 코드 | 설명 |

|------|-----------|------|

| **데이터 로드** | `fetch('data.json').then(r => r.json())` | 로컬 JSON 파일을 비동기로 읽어옵니다. |

| **임베딩 캐시** | `localStorage.setItem('embeddings', JSON.stringify(vec))` | 처음 생성된 임베딩을 브라우저 스토리지에 저장해 재사용합니다. |

| **검색 함수** | `async function search(query){ … }` | 사용자가 입력한 쿼리를 임베딩하고, 코사인 유사도로 정렬합니다. |

| **UI 업데이트** | `resultDiv.innerHTML = html` | 검색 결과를 동적으로 화면에 표시합니다. |

> **팁**: 개발 중 `console.time()` 과 `console.timeEnd()` 로 임베딩·검색에 걸리는 시간을 측정하면, 성능 최적화 포인트를 쉽게 찾을 수 있습니다.

---

# # 4. 활용 사례와 확장 가능성 – 어디에 쓸 수 있을까?

1. **오프라인 문서 뷰어** – 전자책, 매뉴얼, 사내 위키 등을 로컬에서 빠르게 검색.

2. **교육용 앱** – 학생들이 교재 내용에 대해 질문하면 즉시 관련 문장을 찾아줍니다.

3. **프라이버시‑중시 서비스** – 사용자 데이터가 서버에 전송되지 않아 개인정보 보호가 가능합니다.

추가 확장으로는

- **다중 언어 지원** : `multilingual‑sentence‑encoder` 모델 교체

- **벡터 압축** : PCA 혹은 OPQ 기법을 적용해 저장 용량 최소화

- **검색 UI 개선** : 하이라이트, 페이징, 필터링 기능 추가

이러한 확장은 모두 **자바스크립트**와 **브라우저 API**만으로 구현 가능하므로, 별도의 백엔드 인프라가 필요 없습니다.

---

# # 결론 – 직접 만들어보고 피드백을 남겨보세요!

이번 포스트에서는 **오프라인 검색 엔진**을 구현하는 전체 흐름을 살펴보았습니다. 핵심은 **시맨틱 검색**을 위한 임베딩 생성, 로컬 캐시, 코사인 유사도 계산이라는 세 가지 단계이며, 모든 작업을 **자바스크립트**만으로 처리한다는 점입니다.

프로젝트를 직접 클론하거나, 본인의 데이터에 맞게 변형해 보는 것을 강력히 권장합니다. 구현 과정에서 겪은 어려움이나 개선 아이디어가 있다면 댓글이나 깃허브 이슈에 남겨 주세요. 여러분의 피드백이 더 나은 오프라인 검색 솔루션을 만드는 데 큰 도움이 됩니다!

> **행동 유도**: 지금 바로 코드를 다운로드하고, 로컬에서 **시맨틱 검색**을 체험해 보세요! 🚀

---

**SEO 키워드**: `오프라인 검색 엔진`, `자바스크립트`, `시맨틱 검색`
