지식창고 검색을 ‘설치 가능한 것’으로 굳혔다
검색 시스템을 만드는 것과, 그 시스템을 다시 세울 수 있게 만드는 것은 다른 일이다. 나는 전자만 해두고 후자를 미뤄왔다. 그러다 “이거 새 노트북에 다시 깔려면 뭐부터 하지?”라는 질문 앞에서 멈칫했다. 절차가 전부 머릿속에만 있었던 것이다.
왜 ‘설치’를 굳혀야 했나?
나에겐 몇 년치 문서를 로컬에서 전문검색하는 시스템이 있다. 엑셀·PDF·한글(HWP)·워드·노트까지 형식을 가리지 않고 한 색인에서 찾는 물건인데, 지난 편에서 그 검색 계층 자체를 다뤘다.
그런데 이 시스템엔 약점이 하나 있었다. 설치가 문서화돼 있지 않았다. 파이썬은 어디 걸로, 파서는 뭐뭐, MCP는 어떻게 등록했는지가 전부 내 기억과 흩어진 명령어 히스토리에 있었다. 이건 “작동한다”이지 “재현된다”가 아니다.
flowchart LR subgraph B[이전] M["머릿속 절차 + 흩어진 명령어"] -.PC 바뀌면.-> X["처음부터 더듬기"] end subgraph A[이후] S["설치 스크립트 + 매니페스트"] --> V["한 번에 세우고 검증"] end classDef bad fill:#fde8e8,stroke:#c81e1e,color:#6b1010,stroke-width:1px classDef ok fill:#e6f4ea,stroke:#137333,color:#0b3d1f,stroke-width:1px class M,X bad class S,V ok
그래서 이번엔 검색 기능을 하나 더 붙이는 대신, “이 시스템을 어떻게 세우는가”를 하나의 설치 절차로 굳혔다. 무엇이 필요한지 목록으로 적고(매니페스트), 그게 정말 깔렸는지 스스로 확인하는 스크립트를 짰다.
이 검색 시스템은 뭘로 되어 있나?
무겁지 않다. 네 겹이다.
flowchart TB R["① 런타임 · Python(전용) + Node.js"] --> P["② 파서 · 형식별 텍스트 추출"] P --> I["③ 로컬 FTS 색인 · SQLite 파일 하나"] I --> C["④ MCP · 에이전트가 직접 검색"] classDef a fill:#e8f0fe,stroke:#1a56db,color:#0b2a6b,stroke-width:1px classDef b fill:#fef3e2,stroke:#b25e02,color:#5c2e00,stroke-width:1px classDef c fill:#e6f4ea,stroke:#137333,color:#0b3d1f,stroke-width:1px classDef d fill:#ede9fe,stroke:#6d28d9,color:#3b0764,stroke-width:1px class R a class P b class I c class C d
- 런타임은 파서를 돌릴 파이썬과, 일부 MCP를 띄울 Node.js.
- 파서는 형식마다 다른 추출기 묶음. 여기가 뒤에서 따로 다룰 만큼 중요하다.
- 로컬 FTS 색인은 문서 본문을 미리 색인해 둔 SQLite 파일 하나. 벡터DB도 클라우드도 없다.
- MCP는 그 색인을 에이전트에게 “검색 도구”로 열어주는 껍데기.
왜 클라우드도 벡터DB도 아니고 ‘로컬 FTS’인가?
이 선택은 취향이 아니라 제약에서 나왔다. 내가 뒤지는 문서 중에는 바깥으로 내보내면 안 되는 것들이 섞여 있다. 그러면 답은 하나로 좁혀진다 — 색인도 검색도 전부 내 기계 안에서 끝나야 한다.
| 클라우드/벡터DB | 로컬 FTS | |
|---|---|---|
| 데이터 위치 | 외부 업로드 | 내 디스크 안에서만 |
| 오프라인 | 대개 불가 | 완전히 됨 |
| 비용 | 저장·질의 과금 | 0(디스크만) |
| 정렬 | 의미 유사도 | 키워드 관련도(BM25) |
| 준비물 | 임베딩 파이프라인 | SQLite 하나 |
의미 검색이 필요한 순간이 없진 않다. 하지만 “그 표 어디 있더라”, “이 함수 쓰는 스크립트 전부”처럼 내가 실제로 던지는 질문의 대부분은 키워드로 정확히 잡힌다. 무거운 인프라 없이 이게 되니, 굳이 데이터를 밖으로 꺼낼 이유가 없었다.
파서는 왜 형식마다 다른가?
“문서에서 텍스트를 뽑는다”는 한 문장이지만, 형식마다 속이 완전히 다르다. 그래서 파서는 형식을 보고 엔진을 갈아끼운다.
flowchart TB F["파일"] --> K{"형식?"} K -->|PDF| A["PDF 엔진 · 본문 추출"] K -->|HWP · HWPX| B["한글 문서 전용 파서"] K -->|xlsx| C["압축 해제 후 공유문자열 추출"] K -->|"구형 xls"| D["구형 스프레드시트 리더"] K -->|docx · pptx| E["오피스 XML 파서"] K -->|txt · md| G["그대로 읽기"] A --> Z["FTS 본문으로"] B --> Z C --> Z D --> Z E --> Z G --> Z classDef f fill:#e8f0fe,stroke:#1a56db,color:#0b2a6b,stroke-width:1px classDef e fill:#fef3e2,stroke:#b25e02,color:#5c2e00,stroke-width:1px classDef z fill:#e6f4ea,stroke:#137333,color:#0b3d1f,stroke-width:1px class F,K f class A,B,C,D,E,G e class Z z
몇 가지는 겪어봐야 아는 함정이 있다.
- 엑셀(xlsx)은 사실 ZIP 파일이다. 압축을 풀면 안에 문자열 저장고가 따로 있는데, 거기서 텍스트를 통째로 뽑는 게 셀을 하나씩 순회하는 것보다 훨씬 빠르다. 이 얘기는 엑셀을 검색 가능한 DB로 편에서 자세히 풀었다.
- 한글(HWP/HWPX)은 독자 포맷이라 전용 파서가 1순위다. 형식 자체의 뜯어보기는 HWP·HWPX 파싱 편에.
- 구형과 신형은 다른 엔진이 필요하다.
xls와xlsx,doc와docx는 이름만 비슷하지 속은 남남이다(doc vs docx).
이 라우팅을 매니페스트에 명시해두니, 새 기계에서 “무슨 라이브러리를 깔아야 하는가”가 곧바로 답이 됐다. 파서 목록이 곧 의존성 목록이니까.
‘설치됐다’를 어떻게 검증하나?
설치의 진짜 어려움은 까는 게 아니라 “제대로 깔렸는지 확인하는 것”이다. 그래서 상태 점검 스크립트를 짰다. 세 가지를 순서대로 본다.
flowchart TB S["검증 시작"] --> R{"런타임 있나?"} R -->|없음| RX["설치 안내 후 중단"] R -->|있음| P{"파서 전부 import 되나?"} P -->|일부 실패| PX["빠진 패키지 지목"] P -->|전부 OK| M{"MCP 등록됐나?"} M -->|미등록| MX["등록 명령 출력"] M -->|등록됨| OK["초록불 · 검색 준비 완료"] classDef s fill:#e8f0fe,stroke:#1a56db,color:#0b2a6b,stroke-width:1px classDef bad fill:#fde8e8,stroke:#c81e1e,color:#6b1010,stroke-width:1px classDef ok fill:#e6f4ea,stroke:#137333,color:#0b3d1f,stroke-width:1px class S,R,P,M s class RX,PX,MX bad class OK ok
핵심은 실패했을 때 다음 행동을 알려주는 것이다. “파서 import 실패”에서 끝나면 반쪽이다. “이 패키지가 빠졌다, 이렇게 깔아라”까지 뱉어야 검증이 곧 복구가 된다. 그렇게 만들었더니 점검 스크립트가 설명서 겸 진단기 겸 되살림 도구가 됐다.
겪어야 알았던 함정들
문서로 굳히는 과정에서 두 가지가 나를 붙잡았다.
하나, 파이썬이 파이썬이 아니었다. 최신 윈도우에선 python을 치면 진짜 파이썬이 아니라 스토어로 유도하는 껍데기가 실행돼 조용히 실패한다(종료 코드 49). 파서가 “왜 안 깔리지” 하게 만드는 주범이다. 해결은 단순하다 — 껍데기 말고 실제 배포판의 전체 경로를 박아 쓰는 것. 이걸 매니페스트 맨 위에 경고로 적어뒀다.
둘, 색인은 기계마다 새로 지어야 한다. 색인 파일은 수 기가바이트짜리라 “복사해서 옮기면 되지 않나” 싶지만, 파일 경로가 통째로 박혀 있어 그대로는 안 통한다. 새 기계에선 새로 짓는 게 원칙이다. 대신 데이터가 늘어나는 것과 도구가 늘어나는 것은 다르다 — 색인에 파일만 더 넣는 건 MCP 재시작이 필요 없고, MCP 도구 목록 자체가 바뀔 때만 재시작하면 된다. 이 구분을 몰라 매번 껐다 켰던 시간이 아까웠다.
무엇이 달라졌나?
| 이전 | 이후 |
|---|---|
| 설치가 머릿속에만 | 매니페스트 + 검증 스크립트로 |
| ”다시 깔려면?”에 멈칫 | 한 번에 세우고 초록불 확인 |
| 실패하면 원인 추적 | 스크립트가 빠진 것을 지목 |
| 재시작 여부 감으로 | 데이터 추가/도구 변경 규칙으로 정리 |
시스템을 만드는 일은 절반이었다. 나머지 절반은 그 시스템을 잊어버려도 되게 만드는 일이었다. 설치를 산출물로 굳혀두면, 몇 달 뒤의 나도, 새 기계도, 같은 초록불 앞에 설 수 있다. 그게 “작동한다”와 “재현된다” 사이의 거리였다.
정리의 다음 단계는 기능을 더 붙이는 게 아니라, 지금 있는 걸 다시 세울 수 있게 만드는 것이더라.
마무리
- 관련 글: 볼트에 검색엔진을 얹다 · 볼트 라우팅 인덱스 · 평문 MD 지식 볼트
- 다음 편에선 이 로컬 검색 바깥, 공공데이터 창구를 에이전트에 물린 이야기를 다룬다.