검색 시스템이 문장을 저장하는 대신 텍스트를 정규화하고 분해해 토큰으로 만드는 과정을, 대소문자/문자 폴딩, 토큰화, 불용어 제거, 어간 추출 단계별로 살펴본다.
URL: https://www.paradedb.com/blog/when-tokenization-becomes-token

James Blackwood-Sewell · 2025년 10월 10일

검색창에 문장을 입력할 때, 검색 엔진이 우리가 보는 것과 똑같이 문장을 “본다”고 상상하기 쉽습니다. 하지만 실제로 검색 엔진(혹은 검색 데이터베이스)는 텍스트 덩어리를 저장하지도, 문장을 저장하지도 않습니다. 심지어 우리가 생각하는 방식 그대로의 “단어”를 저장하지도 않습니다. 대신 입력 텍스트(색인되는 텍스트와 쿼리 텍스트 모두)를 해체하고, 불필요한 것을 제거해 깨끗이 만든 뒤, 약간 더 추상적이면서 훨씬 더 유용한 형태로 다시 조립합니다. 바로 **토큰(token)**입니다. 이 토큰이 우리가 검색할 때 사용하는 것이고, 검색을 수행하기 위해 역색인(inverted index)에 저장되는 것입니다.
이제 속도를 늦추고, 파이프라인이 실제로 어떻게 동작하는지 단계별로 살펴보겠습니다. 각 단계에서 언어가 어떻게 분해되고 다시 만들어지는지, 그리고 그 과정이 결과에 어떤 영향을 미치는지 확인해 보죠.
테스트 케이스로는 “The quick brown fox jumps over the lazy dog”를 살짝 비튼 문장을 사용하겠습니다. 이 문장에는 토큰화를 흥미롭게 만드는 요소들이 모두 들어 있습니다. 대문자, 구두점, 악센트, 그리고 파이프라인을 거치며 형태가 변하는 단어들까지. 마지막에는 겉모습이 달라지겠지만, 검색을 위해 완벽히 준비된 형태가 될 겁니다.
The full-text database jumped over the lazy café dog
여기서 다루는 것은 “완전한” 파이프라인이 아니라, 어휘 기반(lexical) 검색 시스템에서 흔히 볼 수 있는 몇 가지 필터를 살펴보는 것입니다. 데이터베이스와 검색 엔진마다 이런 필터들을 조합 가능한 빌딩 블록으로 제공하는 경우가 많아, 필요에 맞게 켜고/끄고/순서를 바꿀 수 있습니다.
이 글의 핵심 아이디어는 Lucene/Elasticsearch, Tantivy/ParadeDB, 또는 Postgres 전문 검색(full-text search) 중 무엇을 쓰든 대체로 동일하게 적용됩니다.
텍스트를 쪼개기(토큰화) 전에, 유용하지 않은 것들을 먼저 걸러낼 필요가 있습니다. 보통 이는 텍스트 문자열을 구성하는 문자들을 점검(audit)하는 것을 의미합니다. 즉, 모든 글자를 소문자로 변환하고, 필요하다면 이력서(résumé), 파사드(façade), 노엘(Noël) 같은 단어에 있는 분음부호(예: 발음 구별 기호)를 기본 문자로 폴딩(folding) 합니다.
이 단계는 토큰화가 시작되기 전에 문자가 정규화되고 일관되게 맞춰지도록 보장합니다. café는 cafe가 되고, résumé는 resume이 되어 악센트 유무와 상관없이 검색이 매칭될 수 있습니다. 소문자화는 database가 Database와 매칭되도록 하지만, 약간의 부작용도 있습니다. 예를 들어 Olive(사람 이름)와 olive(올리브 간식)가 매칭되는 식이죠. 대부분의 시스템은 이런 트레이드오프를 받아들입니다. 누락(false negative)보다 오탐(false positive)이 낫기 때문입니다. 다만 코드 검색(code search)은 예외인데, 보통 기호를 보존하고 _camelCase_나 PascalCase 같은 대소문자 규칙을 존중해야 하기 때문입니다.
입력 문자열이 어떻게 변환되는지 봅시다. 대문자 T를 소문자로 바꾸고, é를 e로 폴딩합니다. 딱히 놀랄 것은 없습니다. (원문에서는) 아래의 박스들이 모두 인터랙티브라서, 여러분의 문장을 넣어 결과를 확인할 수 있습니다.
↓
lowercase & fold diacritics
the full-text database jumped over the lazy cafe dog
물론 여기서도 더 많은 필터를 적용할 수 있지만, 간결함을 위해 다음 단계로 넘어가겠습니다.
토큰화(tokenization) 단계는 필터링된 텍스트를 받아 색인 가능한 단위로 분해합니다. 문장을 하나의 덩어리로 다루던 것에서 벗어나, 토큰이라 불리는 개별적이고 검색 가능한 부분들의 집합으로 취급하게 되는 지점입니다.
영어 텍스트에서 가장 흔한 방식은 공백과 구두점을 기준으로 나누는 단순 토큰화입니다. 공백과 기호를 기준으로 split하면 토큰이 됩니다. 하지만 이 기본적인 단계에도 미묘한 차이가 있습니다. 탭, 줄바꿈, 또는 full-text 같은 하이픈 연결어가 시스템마다 다르게 동작할 수 있죠. 각 시스템에는 고유한 특성이 있는데, 예를 들어 기본 Lucene 토크나이저는 it’s를 [it's]로 만들지만, Tantivy는 [it, s]로 쪼갭니다1.
일반적으로 토크나이저는 세 가지 범주로 나눌 수 있습니다.
단어 지향 토크나이저(Word oriented tokenizers): 단어 경계에서 텍스트를 개별 단어로 분해합니다. 공백 기준의 단순 토크나이저뿐 아니라, 비영어 문자 집합을 이해하는 언어 인지형 토크나이저도 포함됩니다2. 전체 단어 매칭이 필요한 대부분의 검색 애플리케이션에 적합합니다.
부분 단어 토크나이저(Partial Word Tokenizers): 단어를 더 작은 조각으로 분해해, 단어 일부를 매칭하거나 복합어(compound term)를 처리할 때 유용합니다. N-그램 토크나이저는 겹치는 문자 시퀀스를 만들고, 엣지 n-그램(edge n-gram) 토크나이저는 접두사/접미사 같은 가장자리(prefix/suffix)에 집중합니다. 자동완성이나 퍼지 매칭에는 강력하지만, 검색 결과에 노이즈를 만들 수 있습니다.
구조화 텍스트 토크나이저(Structured Text Tokenizers): URL, 이메일 주소, 파일 경로처럼 특정 데이터 형식에 맞춰 설계됩니다. 일반 토크나이저가 망가뜨리기 쉬운 의미 있는 구분자를 보존하고, 도메인 특화 패턴을 처리합니다. 산문이 아닌 텍스트가 포함된 콘텐츠에서 필수적일 수 있습니다.
이 예시에서는 단순 토크나이저를 사용하겠습니다. 다만 아래에서 trigram(길이 3의 n-그램) 토크나이저로 토글해 출력이 어떻게 달라지는지 감을 잡을 수도 있습니다. (맨 위 박스의 텍스트도 바꿔가며 실험할 수 있다는 점도 잊지 마세요.)
↓
split on whitespace and punctuation
the full text database jumped over the lazy cafe dog
10
tokens
어떤 단어들은 정보량이 거의 없습니다. 어디에나 등장하며 의미를 희석시키죠. 예: “the”, “and”, “of”, “are”. 이런 단어들을 **불용어(stopwords)**라고 합니다. 검색 엔진은 종종 이런 단어를 통째로 버립니다3. 남는 것들이 더 강한 신호(signal)를 담고 있을 것이라고 판단하는 겁니다.
물론 위험이 없는 건 아닙니다. 예를 들어 _The Who_에서는 “the”가 중요합니다. 그래서 불용어 목록은 보통 설정 가능하며4 보편적으로 고정되어 있지 않습니다. BM25를 지원하는 시스템에서는, 랭킹 공식이 매우 흔한 용어에 낮은 가중치를 주기 때문에 불용어를 아예 사용하지 않는 경우도 많습니다. 반면 BM25를 지원하지 않는 시스템(예: Postgres tsvector)에서는 불용어가 매우 중요합니다.
↓
remove stop words
full text database jumped over lazy cafe dog
8
tokens
불용어를 제거하자마자 토큰 목록이 더 집중된 것을 볼 수 있죠. 10개 토큰에서 8개로 줄었고, 남은 것들은 더 큰 의미적 무게를 갖습니다.
Jump, jumps, jumped, jumping. 사람은 즉시 연관성을 알아봅니다. 하지만 컴퓨터는 우리가 방법을 제공해 주지 않으면 그렇지 못합니다5.
여기서 등장하는 것이 **어간 추출(stemming)**입니다. 스테머(stemmer)는 규칙 기반으로 단어를 공통된 핵심 형태로 잘라내는 기계입니다. 때로는 우아하게, 때로는 거칠게 잘려 나갑니다. 현대 영어 스테밍의 기반은 Martin Porter의 1980년 알고리즘에서 비롯되었는데, 이는 검색 엔진이 접미사를 일관되게 제거하면서도 단어 구조를 존중하는 규칙을 마련한 접근법을 정의했습니다. 오늘날 많은 스테머는 Snowball 변형을 기반으로 합니다.
결과는 어색해 보일 수 있습니다. Database는 databas가 되고, lazy는 lazi가 됩니다. 하지만 괜찮습니다. 스테머는 미관을 신경 쓰지 않고 일관성을 신경 씁니다. lazy의 모든 형태가 lazi로 수렴하면, 검색 엔진은 그것들을 하나로 취급할 수 있습니다6. 이와 달리 표제어 추출(lemmatization)은 언어학 지식을 사용해 단어를 사전형으로 바꾸지만, 스테밍의 “충분히 괜찮은” 접근보다 더 복잡하고 계산 비용이 큽니다7.
↓
porter stemming
full text databas jump over lazi cafe dog
8
tokens
이것이 최종 변환입니다. 토큰들이 핵심 어간으로 줄어들었습니다. Jumped는 jump가 되고, lazy는 lazi가 되며, database는 databas가 됩니다. 이런 어간은 실제 단어처럼 보이지 않을 수 있지만, 결정적으로 중요한 목적을 가집니다. 바로 일관성입니다. 누군가 jumping, jumped, jumps로 검색하더라도 모두 jump로 줄어들어, 우리가 색인한 콘텐츠와 매칭될 수 있습니다. 이것이 스테밍의 힘입니다. 사람이 같은 개념을 표현하는 다양한 방식 사이의 간극을 메워 줍니다.
문장은 전체 파이프라인을 통과했습니다. _“The full-text database jumped over the lazy café dog”_로 시작한 문장이, 각 단계를 거치며 구두점과 대소문자가 제거되고, 개별 단어로 분해되며, 흔한 불용어가 필터링되고, 마지막으로 어간으로 축약되었습니다.
결과는 다음의 깔끔한 8개 토큰입니다.
full text databas jump over lazi cafe dog
이 변환은 역색인에 저장하는 모든 데이터에 적용되며, 쿼리에도 동일하게 적용됩니다. 누군가 “databases are jumping”을 검색하면, 그 쿼리 역시 소문자화되고, 분해되고, 불용어가 제거되고, 스테밍됩니다. 그 결과 databas와 jump가 되어, 색인된 콘텐츠와 정확히 매칭됩니다.
토큰화는 스포트라이트를 받지 못합니다. 컨퍼런스에서 불용어 필터를 자랑하는 사람은 거의 없죠. 하지만 토큰화는 검색의 조용한 엔진입니다. 토큰화가 없다면 dogs는 dog와 매칭되지 않고, jumping은 jump를 찾지 못할 것입니다.
모든 검색 엔진은 여기에 많은 투자를 합니다. 다른 모든 것(스코어링, 랭킹, 관련도)이 올바른 토큰을 얻는 것에 달려 있기 때문입니다. 화려하진 않지만 정밀하며, 이 부분을 제대로 해내면 검색의 나머지 모든 것도 더 잘 동작합니다.
ParadeDB 시작하기에서 현대적인 검색 데이터베이스가 토큰화를 어떻게 처리해 주는지 확인해 보세요. 그리고 GitHub에서 별(star)도 부탁드립니다.
어느 쪽이 더 나을까요? 상황에 따라 다릅니다. it's가 더 “정확해” 보이고 쓸모없는 s 토큰 저장을 피할 수 있지만, it로 검색했을 때 매칭되지 않습니다. ↩
Jieba나 Lindera 같은 범용 형태소 라이브러리는 중국어, 한국어, 일본어 문자 등을 처리할 수 있는 토크나이저를 제공하는 데 흔히 사용됩니다. ↩
불용어를 제거하더라도 문서 내 각 토큰의 원래 위치 정보는 유지합니다. 이는 버려진 단어가 있더라도 위치 기반 쿼리(예: “cat을 dog의 5단어 이내에서 찾아라”)를 가능하게 합니다. ↩
Lucene과 Tantivy는 기본적으로 불용어가 꺼져 있으며, 영어에 대해 활성화하면 동일한 기본 목록을 사용합니다: [a, an, and, are, as, at, be, but, by, for, if, in,into, is, it, no, not, of, on, or, such, that, the, their, then, there, these, they, this, to, was, will, with] ↩
또 다른 방법으로는 벡터 검색(vector search)이 있는데, 어휘적 스테밍 대신 의미(semantic meaning)에 대한 검색으로 트레이드오프를 합니다. ↩
과도한 스테밍(overstemming)도 발생할 수 있습니다. 예를 들어 university와 universe는 둘 다 univers로 어간 추출되지만 의미는 크게 다릅니다. ↩
표제어 추출(Lemmatization)은 실제 단어 목록을 사용해 결과가 “진짜 단어”가 되도록 합니다. 이를 정확히 하려면 원 단어가 어떤 품사(명사, 동사 등)인지 알아야 합니다. ↩