포스트

검색어 정규화: 공백·대소문자·전각이 일으키는 누락

앞뒤 공백, 대소문자, 전각/반각, 한글 자모 분리 같은 입력 변형이 검색 누락을 만드는 이유와 서버단 검색어 정규화 파이프라인 설계.

검색어 정규화: 공백·대소문자·전각이 일으키는 누락

검색 입력을 처리하는 작업을 한 적이 있다. “분명히 있는데 안 나온다”는 검색 누락의 범인은 대개 인덱스가 아니라 검색어와 색인된 값의 표현이 미세하게 다른 것이다. 키를 쥔 건 검색어 정규화 파이프라인이다.

같은 의미, 다른 바이트

검색은 결국 문자열 비교다. 의미가 같아도 바이트가 다르면 매칭되지 않는다. 입력 단계에서 끼어드는 대표적 변형은 이렇다.

  • 앞뒤 공백: 모바일 자판이 자동으로 붙이는 trailing space. "laptop " ≠ "laptop".
  • 대소문자: "iPhone" 검색이 "iphone" 색인을 못 찾는다.
  • 전각/반각(full-width/half-width): 한·중·일 입력기에서 "ABC123"(전각)가 들어온다. ASCII "ABC123"과 코드포인트가 완전히 다르다.
  • 유니코드 정규화 형태: 한글은 NFC(완성형 한)와 NFD(자모 분리 ㅎ+ㅏ+ㄴ) 두 표현이 있다. macOS 파일명·일부 입력기는 NFD를 흘린다. 눈에는 똑같지만 비교는 실패한다.

핵심 원칙: 색인 시점과 검색 시점에 동일한 정규화를 적용한다. 한쪽만 정규화하면 오히려 더 어긋난다. 정규화는 양쪽이 같은 함수를 통과해야 의미가 있다.

정규화 파이프라인

순서가 중요하다. 유니코드 형태를 먼저 통일하고, 호환 문자를 접고, 케이스를 접고, 공백을 정리한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
public final class QueryNormalizer {

    public static String normalize(String raw) {
        if (raw == null) return "";
        // 1) NFKC: 전각→반각, 호환 문자 정규화 + 정준 결합(NFC 포함)
        String s = Normalizer.normalize(raw, Normalizer.Form.NFKC);
        // 2) 케이스 접기
        s = s.toLowerCase(Locale.ROOT);
        // 3) 앞뒤 공백 제거 + 내부 공백 축약
        s = s.strip().replaceAll("\\s+", " ");
        return s;
    }
}

여기서 NFC가 아니라 NFKC를 쓴 게 포인트다. NFKC는 “호환 분해 후 정준 결합”으로, 전각 A를 반각 A로, ①을 1로 접는다. NFC만 쓰면 전각/반각이 그대로 남아 검색이 누락된다.

1
2
3
4
-- 색인 컬럼도 같은 규칙으로 표준화해 둔다
SELECT id, name
FROM   product
WHERE  name_normalized LIKE CONCAT(?, '%');   -- ? = normalize(query)

색인 컬럼 name_normalized를 미리 채워두면, 검색 시 정규화한 키워드와 1:1로 맞아떨어진다.

운영 함정

NFKC는 정보를 잃을 수 있다. ㎏→kg, ½→1⁄2처럼 표시 의미가 섞인 문자가 분해된다. 검색 매칭에는 이롭지만, 정규화 결과를 그대로 화면에 다시 보여주면 사용자 원문이 변형돼 보인다. 검색용 정규화 값과 표시용 원문을 분리 저장한다.

과한 정규화는 오탐을 부른다. 형태소 분석 없이 공백만 지우면 "서울 시청"과 "서울시청"이 합쳐져 의도와 다른 결과가 섞인다. 정규화는 “표기 변형 제거”까지만 하고, 의미 단위 처리는 분석기/토크나이저에 맡긴다.

핵심 요약

  • 검색 누락의 흔한 원인은 인덱스가 아니라 검색어·색인값의 표기 차이다.
  • NFKC + lowercase + trim 파이프라인으로 전각·케이스·공백·유니코드 형태를 통일한다.
  • 색인 시점과 검색 시점에 동일한 정규화를 적용해야 한다. 표시용 원문은 따로 보관한다.
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.