사전 검색 엔진 라이브러리
개념

검색 엔진 라이브러리

gabury1고친 사람 github-actions[bot]

검색 엔진 라이브러리는 내 프로그램 안에 검색 기능을 넣어 줍니다. 글을 낱말로 찾고 잘 맞는 순서로 줄 세우는 일을 함수 몇 개를 불러 맡깁니다. 따로 띄워 두는 서버가 없습니다. 검색 서버 제품들도 속에 이런 라이브러리를 품고 있습니다.

쉽고 빠른 이해

글을 낱말로 찾아 주는 부품을 내 프로그램에 끼워 넣는 것입니다. 블로그 프로그램에 글 검색을 붙일 때 검색 서버를 따로 두지 않고 프로그램 안에서 바로 찾는 식입니다.

왜 이렇게 하나. 데이터베이스에서 글 속 낱말을 찾으면 모든 행을 하나씩 읽습니다. 어느 글이 더 잘 맞는지도 알려 주지 않습니다. 검색 서버를 하나 더 띄우면 굴릴 것이 늘어납니다. 라이브러리는 서버 없이 검색만 들여옵니다.

어떻게 도나.

  1. 글을 넣으면 라이브러리가 낱말로 쪼개 찾아보기를 만듭니다
  2. 찾아보기는 내 기계의 디스크에 파일로 남습니다
  3. 찾을 때 함수를 부르면 그 낱말이 든 글을 점수 높은 순으로 돌려줍니다

대가. 찾아보기가 한 기계의 파일이라 서버 여러 대가 함께 쓰기 어렵습니다. 대마다 같은 글을 넣어 주는 일을 내가 짜야 합니다.

언제 쓰나. 프로그램 하나가 자기 기계에서 찾을 때 씁니다. 같은 프로그램을 여러 대 띄워 함께 찾아야 하면 검색 서버를 씁니다.

상세

자동차 회사가 엔진을 직접 만들지 않고 엔진 회사에서 사 오기도 합니다. 사 온 엔진을 자기 차체에 얹고 핸들과 바퀴를 붙이면 차 한 대가 됩니다. 엔진은 같아도 어떤 차가 되는지는 얹는 회사가 정합니다.

검색 엔진 라이브러리가 이 엔진 노릇을 합니다. 글을 낱말로 찾고 점수를 매기는 핵심만 떼어 라이브러리로 내놓은 것입니다. 라이브러리는 프로그램에 끼워 넣고 함수처럼 불러 쓰는 코드 묶음입니다. 라이브러리를 넣은 프로그램이 곧 검색 엔진이 됩니다.

블로그 프로그램에 글 검색을 붙인다고 해 봅시다. 라이브러리를 빌드 설정에 추가합니다. 글을 저장할 때마다 라이브러리에도 넣습니다. 검색창에 낱말이 들어오면 라이브러리의 검색 함수를 부릅니다. 돌아온 글 번호로 화면을 그립니다.

데이터베이스로는 모자란 대목

글 전체를 대상으로 낱말로 찾는 일을 전문 검색이라고 부릅니다. 상품 설명이나 게시글 본문, 로그 메시지에서 낱말로 찾는 일이 다 여기에 듭니다.

관계형 데이터베이스에서도 LIKE '%이어폰%' 같은 조건으로 찾을 수는 있습니다. 앞에 % 가 붙으면 조회를 빠르게 하려고 만들어 둔 인덱스가 듣지 않습니다. 결국 모든 행의 글을 하나씩 열어 봅니다. 글이 늘수록 그만큼 오래 걸립니다.

속도만 문제가 아닙니다. LIKE 는 낱말이 들었는지 안 들었는지만 답합니다. 어느 글이 더 잘 맞는지 줄 세워 주지 않습니다.

이 둘을 풀려면 글을 미리 낱말로 정리해 두는 장치가 있어야 합니다. 그 장치는 찾을 때 점수도 매깁니다. 이 장치를 처음부터 짜기는 어렵습니다. 검색 엔진 라이브러리는 이 장치를 다 만든 부품으로 줍니다.

라이브러리가 맡는 일

라이브러리 안에는 검색에 필요한 부품이 몇 가지 들어 있습니다. 이 소절은 글이 들어와서 검색 결과로 나가기까지 어느 부품이 무엇을 하는지 봅니다.

글이 들어오면 라이브러리는 먼저 글을 낱말 단위로 쪼갭니다. 이 일이 토큰화입니다. 쪼개 놓아야 낱말 하나하나를 따로 적어 둘 수 있습니다.

쪼갠 낱말은 모양을 하나로 맞춥니다. 대문자는 소문자로 바꿉니다. 영어의 「runs」와 「running」은 「run」으로 모읍니다. 쪼개고 모양을 맞추는 일을 한데 묶은 부품이 분석기입니다.

한국어는 낱말 끝에 조사와 어미가 붙습니다. 「한강을」과 「한강에서」, 「달렸다」와 「달리는」을 같은 낱말로 보려면 이것을 떼어 내야 합니다. 그래서 한국어 분석기는 쪼개는 단계에서 형태소 분석을 씁니다. 형태소 분석은 낱말을 뜻의 가장 작은 단위로 나누는 일입니다.

모양을 맞춘 낱말은 역색인에 적힙니다. 역색인은 낱말마다 그 낱말이 든 글의 번호를 늘어놓은 표입니다. 책 뒤에 붙은 찾아보기와 같은 모양입니다. 글 셋을 넣으면 이렇게 됩니다.

글 번호 내용
1 무선 이어폰
2 유선 이어폰
3 무선 마우스

이 셋을 넣은 뒤의 역색인입니다. 낱말이 줄마다 하나씩 섭니다. 그 낱말이 든 글 번호가 옆에 붙습니다.

낱말 든 글
무선 1, 3
유선 2
이어폰 1, 2
마우스 3

「이어폰」을 찾으면 셋째 줄 하나만 읽고 1번과 2번을 얻습니다. 글이 셋이든 천만 건이든 읽는 줄은 하나입니다.

검색어도 넣을 때와 같은 분석기로 쪼갭니다. 그래야 넣은 「달렸다」와 찾는 「달리는」이 둘 다 「달리다」가 되어 역색인에서 만납니다.

후보가 여럿이면 순서를 정해야 합니다. 라이브러리는 후보마다 관련도 점수를 매깁니다. 관련도는 글이 검색어와 얼마나 잘 맞는지를 나타낸 수입니다. 검색어 낱말이 글에 자주 나올수록, 그 낱말이 다른 글에는 드물수록 점수가 올라갑니다.

이 점수를 내는 대표 수식이 TF-IDF(Term Frequency-Inverse Document Frequency, 낱말 빈도-역문서 빈도)입니다. 앞의 두 신호, 곧 낱말이 나온 횟수와 낱말이 드문 정도를 곱해 하나로 묶습니다.

여기에 글 길이까지 넣어 다듬은 수식이 BM25(Best Matching 25)입니다. 같은 낱말이 같은 횟수 나와도 짧은 글이 더 높은 점수를 받습니다. 긴 글에는 아무 낱말이나 한 번쯤 들어 있기 쉽기 때문입니다.

점수를 다 매기면 라이브러리는 높은 순으로 위에서 몇 건만 돌려줍니다. 사람은 대개 첫 화면만 보기 때문입니다.

역색인은 파일로 남는다

라이브러리는 역색인을 내 기계의 디스크에 파일로 적습니다. 프로그램을 껐다 켜도 파일이 남아 있어 역색인을 다시 만들지 않아도 됩니다. 어느 폴더에 둘지는 라이브러리를 부르는 프로그램이 정합니다.

글을 넣는 일과 그 글이 검색에 보이는 일 사이에는 틈이 있습니다. 글 하나 넣을 때마다 파일을 고쳐 쓰면 디스크 쓰기가 너무 잦아집니다. 그래서 넣은 글을 메모리에 모았다가 한꺼번에 파일로 적습니다. 파일에 적힌 뒤에야 그 글이 검색에 잡힙니다.

같은 역색인 파일에 여러 프로세스가 한꺼번에 쓰면 내용이 엉킵니다. 그래서 라이브러리는 대개 역색인 하나에 쓰는 쪽을 한 번에 하나만 받습니다. 둘째로 쓰려는 프로세스는 막힙니다. 읽는 쪽은 여럿이어도 됩니다.

막는 일은 라이브러리가 해도, 쓰는 일을 어느 프로세스에 몰지는 내 프로그램이 정합니다. 같은 역색인을 여러 프로세스가 고쳐야 한다면 쓰기를 한 프로세스에 모아 주는 길을 내가 짜야 합니다.

언어마다 다른 라이브러리

라이브러리는 대개 그것을 짠 언어의 프로그램에 넣어 씁니다. 그래서 고를 때는 내 서비스가 쓰는 언어가 먼저 걸립니다. 언어별로 이름난 라이브러리는 이렇습니다.

라이브러리 짠 언어
Apache Lucene [[Java
Tantivy Rust
Xapian C++
Bleve Go
Whoosh [[Python

이름은 달라도 하는 일은 앞 소절과 같습니다. 분석기로 쪼갭니다. 역색인에 적습니다. 점수를 매겨 돌려줍니다.

라이브러리 위에 얹은 검색 서버

라이브러리는 한 프로세스 안에서 도는 부품입니다. 그 프로세스 바깥의 일은 맡지 않습니다. 이 소절은 바깥의 일이 무엇이고 누가 맡는지 봅니다.

라이브러리가 맡지 않는 일 필요해지는 때
네트워크로 검색 요청 받기 다른 언어로 짠 서비스가 검색을 부를 때
역색인을 여러 대로 나눠 담기 역색인이 한 기계의 디스크와 메모리를 넘을 때
역색인 사본을 다른 기계에 두기 한 대가 죽어도 검색이 이어져야 할 때

역색인을 몇 묶음으로 갈라 여러 대에 나눠 담을 때 그 묶음 하나를 샤드라고 부릅니다. 사본을 다른 기계에 두는 일은 복제입니다.

이 일들을 라이브러리 위에 얹어 따로 띄우는 프로그램으로 만든 것이 검색 서버입니다. Elasticsearch, OpenSearch, Apache Solr 가 그런 제품입니다. 셋 다 속에 Lucene 을 품고 있습니다. 그 위에 요청을 받는 입구와 여러 대로 나눠 담는 기능을 얹었습니다.

두 쓰임새를 나란히 놓으면 이렇습니다.

flowchart TD
    subgraph LIB["라이브러리로 쓸 때"]
        A1["내 프로그램"] --> A2["검색 엔진 라이브러리"]
        A2 --> A3["역색인 파일 · 내 기계 디스크"]
    end
    subgraph SRV["검색 서버로 쓸 때"]
        B1["내 프로그램"] -->|네트워크 요청| B2["검색 서버"]
        B2 --> B3["검색 엔진 라이브러리"]
        B3 --> B4["역색인 파일 · 검색 서버 디스크"]
    end

위쪽은 함수 호출 한 번으로 끝납니다. 아래쪽은 요청이 네트워크를 한 번 건넙니다. 두 쪽 다 낱말을 찾는 부품은 같은 라이브러리입니다.

서버 여러 대에 넣을 때 생기는 틈

백엔드 서비스는 흔히 같은 프로그램을 여러 대 띄우고 로드 밸런서로 요청을 나눕니다. 로드 밸런서는 들어온 요청을 여러 서버에 고루 보내는 장치입니다. 여기에 라이브러리를 넣으면 대마다 자기 디스크에 역색인을 따로 갖게 됩니다.

글 하나를 새로 넣으면 그 요청을 받은 한 대의 역색인에만 들어갑니다. 다른 대로 간 검색 요청은 그 글을 못 찾습니다. 대마다 같은 글을 넣어 주는 장치를 따로 짜야 합니다.

그 장치를 짜는 것은 검색 서버가 하는 일을 다시 만드는 셈입니다. 그래서 서버가 여러 대인 서비스는 대개 검색 서버를 따로 둡니다.

데이터베이스 안에 든 전문 검색

데이터베이스가 전문 검색을 제 안에 갖춘 경우도 있습니다. PostgreSQL 전문 검색과 SQLite 의 전문 검색 확장이 그렇습니다. 역색인과 점수 계산을 데이터베이스가 맡으므로 라이브러리를 따로 넣지 않아도 됩니다.

원본 데이터와 역색인이 한 데이터베이스에 있으니 둘을 맞추는 장치도 따로 필요 없습니다. 이미 데이터베이스를 쓰고 있다면 서버도 라이브러리도 더 들이지 않고 검색을 붙일 수 있습니다.

언제 쓰고 언제 안 쓰나

어느 쪽을 고를지는 검색을 몇 대가 함께 쓰는지가 가장 먼저 가릅니다.

하려는 일 맞는 쪽
프로그램 한 대가 자기 기계의 글을 찾는다 검색 엔진 라이브러리
데스크톱 앱이나 모바일 앱이 인터넷 없이 찾는다 검색 엔진 라이브러리
검색 서버 제품을 직접 만든다 검색 엔진 라이브러리
같은 프로그램을 여러 대 띄워 함께 찾는다 검색 서버
역색인이 한 기계에 안 들어간다 검색 서버
원본이 데이터베이스에 있고 글이 많지 않다 데이터베이스 안의 전문 검색

라이브러리를 고르면 굴릴 서버가 늘지 않습니다. 대신 역색인을 어디에 두고 언제 다시 만들지를 내 프로그램이 책임집니다.

관련 항목

검색 엔진 라이브러리가 속하는 상위 분류

검색 · 검색 엔진 · 전문 검색 · 라이브러리 · 정보 검색

검색 엔진 라이브러리를 이루는 구성 요소

분석기 · 토크나이저 · 토큰 필터 · 역색인 · 포스팅 리스트 · 세그먼트 · 세그먼트 병합 · 문서 · 필드

글이 역색인에 들어가기 전 거치는 처리 단계

토큰화 · 토큰 · 형태소 분석 · 어간 추출 · 표제어 추출 · 불용어 · 동의어 사전

결과 순서를 정하는 점수 모델

관련도 · 랭킹 · TF-IDF · BM25 · 벡터 공간 모델

검색 엔진 라이브러리가 받는 질의 종류

질의 · 불리언 검색 · 구문 검색 · 퍼지 검색 · 접두사 검색 · 벡터 검색

검색 엔진 라이브러리를 구현한 제품

Apache Lucene · Tantivy · Xapian · Bleve · Whoosh

라이브러리를 품고 서버로 띄우는 검색 서버

Elasticsearch · OpenSearch · Apache Solr · 검색 서버

검색 서버가 라이브러리 위에 얹는 기능

샤드 · 샤딩 · 복제 · 클러스터 · 로드 밸런서

라이브러리 대신 쓸 수 있는 데이터베이스 쪽 검색

PostgreSQL 전문 검색 · SQLite · 관계형 데이터베이스 · 인덱스 · LIKE

다른 이름: search engine library · 검색 라이브러리 · 전문 검색 라이브러리 · full-text search library