- Val Town 검색은 Postgres ILIKE 기반 부분 문자열 검색이라 순위화가 거의 없고, 여러 단어 쿼리도 약해 개선 요구가 많음
- 자연어 검색의 불용어 제거, 어간 추출, 표제어 처리 같은 규칙은 코드의 변수명·함수명·토큰 경계를 망가뜨릴 수 있음
- Postgres Full Text Search는 인프라를 단순하게 유지할 수 있지만, 이전 프로젝트에서 확장성 문제가 있었고 Val Town도 단일 노드 Postgres 한계를 시험 중임
- 소프트 론치한 v2 검색은 pg_trgrm 기반 trigram 검색을 쓰지만, 정규식 검색과 달리 자유형 쿼리의 순위화는 원하는 수준으로 맞추기 어려움
- Elasticsearch, Meilisearch, Zoekt, ParadeDB 같은 대안은 있으나 별도 인프라, 운영 부담, 호스팅 지원 여부가 선택의 제약으로 남아 있음
Val Town 검색이 막힌 지점
- Val Town 검색은 현재 Postgres의 ILIKE를 사용함
- 검색어가 코드 안에 포함되어 있으면 결과에 나타나는 부분 문자열 검색 방식임
- 순위화는 거의 없고, 여러 단어 쿼리는 제대로 지원되지 않음
- 더 나은 검색은 Val Town에서 가장 많이 요청된 기능 중 하나임
- 개선 작업은 진행 중이지만, 아직 요구사항에 맞는 해법을 찾지 못함
- 지금까지 확인한 조건은 다음과 같음
- 주류 검색 솔루션은 자연어에 맞춰 설계됨
- 코드 검색이 필요한 대기업은 자체 검색 시스템에 많은 시간과 비용을 투자함
- Val Town은 이미 많은 데이터를 가지고 있어 잘 확장되는 해법이 필요함
- 데이터베이스 확장 대신 별도 검색 서비스를 쓰면 인프라와 복잡도 측면의 절충이 중요해짐
자연어 검색 규칙이 코드에 맞지 않는 이유
- 일반적인 전문 검색(FTS) 설정은 영어 같은 자연어를 대상으로 한 알고리듬을 기본 제공함
- 불용어 제거: “the”, “it”처럼 너무 흔한 단어를 색인 전에 제거함
- 어간 추출: “running”을 “run”으로 바꿔 “runs” 검색으로도 찾을 수 있게 함
- 표제어 처리: “excellent” 검색이 “great”가 포함된 문서도 찾도록 동의어를 더 흔한 단어로 대체할 수 있음
- 같은 규칙을 코드에 적용하면 의미가 어긋남
- TypeScript에서
the는 불용어가 아니라 검색하고 싶은 유효한 변수명일 수 있음 - 코드의 단어 경계는 자연어와 다름
- 함수명에 어간 추출을 적용해도 의미 있는 결과를 기대하기 어려움
- TypeScript에서
- Postgres
to_tsvector('english', ...)는 자연어 문장을 색인하면서 원문을 크게 바꿈I am writing this example sentence는'exampl':5 'sentenc':6 'write':3처럼 변환됨
- 코드에서는 토큰화 문제가 더 두드러짐
function stringifyNumber(a: number): string { return a.toString() }가'a.tostring':7 'function':1 'number':4 'return':6 'string':5 'stringifynumb':2처럼 색인됨function같은 단어는 남고,a.toString()은.이 기본 단어 경계가 아니어서 두 토큰으로 나뉘지 않음
Postgres Full Text Search의 장단점
- Postgres는 Full Text Search 확장을 제공하고, Val Town의 호스팅 제공자인 Render도 이를 지원함
- Val Town은 지금까지 Postgres를 적극적으로 사용해 왔고, Postgres는 문서화와 호스팅 지원이 좋은 기술로 평가됨
- 작은 팀에게는 인프라를 가능한 한 단순하게 유지하는 것이 중요해, Postgres로 해결할 수 있으면 Postgres를 쓰려는 유인이 큼
- 다만 이전에 FTS를 사용한 프로젝트들은 성능과 확장성 문제를 겪음
- Observable은 결국 Elasticsearch로 이동함
- Val Town은 많은 vals를 가지고 있고, 단일 노드 Postgres 클러스터의 한계를 시험하고 있음
- 코드 검색에 FTS를 성공적으로 쓴 사례를 찾기 어려워, 첫 번째 선택지로 쓰기보다는 예비안으로 남겨둔 상태임
pg_trgrm 기반 v2 검색 실험
- Val Town이 소프트 론치한 v2 검색 알고리듬은 Postgres의
pg_trgrm에 기반함pg_trgrm은 Postgres에서 trigram 검색을 구현함
- 코드 검색에서 trigram은 이미 성공 사례가 있음
- Russ Cox의 2012년 글은 Google Code Search가 trigram 색인과 특수 정규식 구현을 사용한 사례를 다룸
- GitHub의 새 코드 검색 시스템도 trigram 검색을 사용함
- Sourcegraph는 Google에서 이어받은 trigram 기반 검색 도구를 보유함
- Val Town의 Postgres
pg_trgrm접근은 Stephen Gutekanst의 Postgres 기반 로컬 저장소 색인 글에서 많은 영향을 받음 - 구현은 검색 텍스트가 들어 있는 컬럼에 GIN 색인과
gin_trgm_ops를 적용함 pg_trgrm은 정규식 검색에는 좋은 해법이지만, Val Town의 대부분 검색처럼 더 자유로운 쿼리에는 잘 맞지 않음- 검색 순위화에는 word_similarity를 사용 중임
- 합리적인 순위에 가깝게 알고리듬을 조정하는 일이 매우 어려움
검색 엔진 선택지와 운영 절충
- 검토 대상에는 독립 실행형 검색 서비스와 Postgres 확장이 섞여 있음
- Meilisearch: 독립 실행형, Rust, 41k 스타
- Typesense: 독립 실행형, C++, 17k 스타
- Zoekt: 독립 실행형, Go, 406 스타
- ParadeDB: Postgres 확장, Rust, 3.2k 스타
- Sonic: 독립 실행형, Rust, 19.4k 스타
- 코드 전용 도구는 존재하지만, 대부분은 비공개임
- GitHub 검색은 뛰어나지만, 전담 팀과 실제 시간 예산이 들어간 결과물임
- Sourcegraph가 유지하는 Zoekt 포크는 흥미롭지만 매우 니치하고, 큰 신규 인프라 투자가 필요함
- Elasticsearch는 결국 피할 수 없는 해법이 될 수 있음
- 코드 전용 처리는 없지만 거의 무한히 커스터마이즈할 수 있음
- Java 메모리 튜닝 학습, 애플리케이션에 첫 영구 디스크 스토리지 도입, 데이터의 추가 진실 공급원 관리가 부담임
- Elasticsearch Cloud를 쓰면 유지보수 부담을 줄일 가능성이 있음
- Meilisearch는 Elasticsearch 대안으로 유망해 보임
- Rust 기반이라는 매력이 있음
- 자체 비교 글에서는 확장성보다 지연 시간을 더 강조하는 듯하며, 인프라 부담이 더 낮을지는 확실하지 않음
- ParadeDB는 Elasticsearch처럼 동작하지만 “그냥 Postgres”라는 점이 매력적임
- 다만 Render에서는 아직 해당 확장을 사용할 수 없음
작은 팀이 검색 인프라를 고를 때의 부담
- 코드 검색은 영어 검색보다 난도가 높음
- 작은 팀은 인프라를 단순하게 유지하고, 개발 환경 설정을 쉽게 만들고, 데이터를 같은 곳에 두려는 유인이 있음
- Val Town은 지속적인 관리가 필요한 선택지에 성급히 묶이지 않으려 함
- 중대형 회사에 검색 “서비스”만 있는 것이 아니라 검색 “팀”이 있는 데는 이유가 있음