본문 바로가기
Databricks/RAG

한국어 RAG 검색 (Kiwi) ④ 부록 — 동의어 적용 위치·IATA 사전 생성/검증·품사·Nori 비교

by 여행을 떠나자! 2026. 6. 28.

📚 시리즈 — 한국어 RAG 검색 (Kiwi)

 

 

부록 A. 동의어 적용 위치 비교·선정

 

A.1 케이스 정의

동의어를 어느 분석 파이프라인 단계에 끼워 넣느냐에 따라 IR 엔진(Lucene·Elasticsearch·OpenSearch·Solr) 의 동작이 달라집니다.

  • Case A — 인덱싱 시점만 확장 (index-time only): 색인 분석기에서 synonym 또는 synonym_graph 필터를 적용해 원문 토큰과 동의어 토큰을 함께 포스팅에 기록.
  • Case B — 검색 시점만 확장 (search-time only): 색인은 원문 토큰만 저장, 검색 분석기 또는 질의 빌드 단계에서 동의어 OR 확장. Elastic·OpenSearch·Solr 모두 공식 권장 Elastic Blog, Elasticsearch Reference, OpenSearch Documentation.
  • Case C — 양쪽 확장 (both): 양쪽 분석기에 동의어 필터. df 가 양방향으로 부풀어 IDF 가 왜곡되며, 다토큰 동의어는 인덱스 시 token graph 가 평탄화되어 의미 손실 Elastic Blog, Elastic Docs.

 

A.2 BM25 이론 — IDF 인플레이션 메커니즘

BM25 의 IDF 항은 Robertson & Zaragoza (2009) 의 Probabilistic Relevance Framework 에 따라 다음과 같이 정의됩니다 The Probabilistic Relevance Framework: BM25 and....

IDF(q) = log( (N − df(q) + 0.5) / (df(q) + 0.5) )

df 가 커질수록 IDF 는 단조 감소합니다. 동의어를 인덱스 시점에 추가 하면 원문 외에 동의어 토큰도 함께 색인되어 동일 의미 어휘 전체의 df 가 부풀고 IDF 가 일제히 낮아집니다. 동시에 검색 시점 에도 OR 확장하면 확장된 모든 토큰이 이미 낮아진 IDF 로 다시 합산되어 score 가 이중 왜곡됩니다.

Lucene BM25SimilaritydiscountOverlaps=true 기본값으로 position increment 0 인 overlap 토큰을 문서 길이(norm) 계산에서 제외해 길이 정규화를 보정하지만, df 자체의 인플레이션은 막지 못합니다 BM25Similarity (Lucene 9.x core API). 검색 시점에는 별도로 SynonymQuery 가 도입되어 동의어 그룹 내 토큰을 동일 term 처럼 단일 similarity 호출로 묶어 TF 를 합산하는 blended scoring 을 수행합니다 SynonymQuery (Lucene 9.x core API). 즉 Lucene 의 공식 처방은 동의어는 검색 시 확장하고 SynonymQuery 의 blended 가중치에 맡긴다 입니다.

Elastic 의 Practical BM25 시리즈도 IDF 는 코퍼스 전체의 통계량이므로 인덱스 시 동의어 확장은 코퍼스 통계 자체를 변형시킨다고 설명합니다 Elastic Blog. 검색 시점 적용은 코퍼스 통계를 보존하면서 질의 의미 범위만 확장한다는 점에서 이론적으로 우월합니다.

 

A.3 운영 측면 — 재색인 비용·인덱스 크기·갱신 빈도

Elastic 공식 블로그 Boosting the power of Elasticsearch with synonyms 는 검색 시점 적용의 운영 이점을 다음과 같이 요약합니다 Elastic Blog.

"Using synonyms in search-time analyzers doesn't affect index size, the term statistics in the corpus stay the same, and changes in the synonym rules don't require reindexing of documents."

즉 (i) 인덱스 크기 보존, (ii) 코퍼스 통계 보존, (iii) 사전 변경 시 무재색인 의 3 중 이점입니다. 같은 결론을 Solr 측 Better synonym handling in Solr 도 명시합니다 — "Query-time expansion should have huge benefits, given that changes to the synonyms don't require re-indexing, the index size stays the same, and the IDF values for the documents don't get permanently altered." Better synonym handling in Solr

Databricks Vector Search 운영 비용 — Delta Sync Index 의 공식 비용 가이드는 "Use Triggered Sync instead of Continuous Sync where possible, to reduce streaming costs" 를 권장합니다 Databricks. 또한 소스 Delta 테이블을 OVERWRITE 로 갱신하면 모든 행의 임베딩이 재계산되어 비용이 폭증하므로, Change Data Feed(CDF) 를 활용해 변경 행만 재임베딩하라는 운영 패턴이 보고되어 있습니다 Databricks Community. 동의어를 인덱스 시 적용으로 운영하면 사전 갱신 → Delta 전체 OVERWRITE → 임베딩 풀 재계산 이라는 비싼 경로가 됩니다. 검색 시 적용이면 임베딩 인덱스를 건드리지 않고 BM25 채널만 영향을 받습니다.

사전 갱신 빈도별 권장:

  • ≥ 월 1회 → 검색 시 적용 (Case B) 사실상 강제
  • 거의 동결 + 풀 재색인 ≤ 10분 → Case A 도 고려 가능하나 다토큰 한국어에서는 비권장 (§A.4)
  • 양쪽 적용(Case C) 은 어느 운영 시나리오에서도 권장되지 않음 Elastic Blog, Better synonym handling in Solr

 

A.4 한국어 도메인 특수성 — 다토큰 동의어·Nori 함정

Lucene 의 색인 포맷은 토큰별 position 정보는 저장하지만 position length 정보는 저장하지 않기 때문에, 인덱스에 저장하는 순간 token graph 가 평탄화 됩니다. Mike McCandless 가 처음 정리한 이 한계는 Elastic 공식 Token graphs 레퍼런스와 Multi-Token Synonyms and Graph Queries 블로그가 재차 확인합니다 Elastic Blog, Elastic Docs. 그래서 Lucene 6.4.0 / Elasticsearch 5.2.0 부터 도입된 SynonymGraphFilter검색 분석기 전용 으로 설계되었습니다.

OpenSearch 공식 가이드도 동일하게 명시합니다 — "Graph token filters should be applied exclusively in search analyzers, removing the need for the flatten_graph filter." OpenSearch Documentation Solr 도 인덱스 분석기에 그래프 필터를 두면 FlattenGraphFilterFactory 로 평탄화해야 하며, 평탄화 과정에서 phrase query 의 일부 매칭이 깨지는 알려진 한계가 있습니다 Apache Solr Reference Guide.

한국어에는 추가로 Nori decompound 와 동의어 필터의 충돌 함정이 있습니다. Elastic GH Issue #37751 Korean (nori) Analysis Synonym Filter build faileddecompound_mode: mixed 의 nori 가 풋사과 같은 복합어를 분해할 때 position increment 0 인 다중 토큰을 생성하고, 이 다중 토큰을 인덱스 시 동의어 필터가 받아들이지 못해 빌드 자체가 실패하는 현상을 보고합니다 elastic/elasticsearch#37751. 한국어 형태소 분해 결과를 동의어 인덱스 시 확장에 그대로 흘리면 인덱스 빌드가 깨지는 사례가 공식 이슈로 기록 되어 있습니다.

AutoRAG 의 한국어 BM25 토크나이저 벤치마크가 Mecab·Kiwi·Kkma·Okt 등 형태소 분석기를 BM25 와 결합하는 패턴을 채택한 것도, 형태소 분해 결과는 검색 측 에서 정렬하는 것이 안전하다는 동일한 직관에 기반합니다 Making benchmark of different tokenizer in BM25.... KLUE 후속 한국어 ODQA 연구들도 검색 측 morpheme 토큰화 + BM25 가 일관되게 높은 성능을 낸다고 보고합니다 KLUE: Korean Language Understanding Evaluation .... 요컨대 한국어 도메인에서는 인덱스 시 동의어 확장이 깨지거나 부정확해질 위험이 더 큽니다.

 

A.5 권장 결론 — 검색 시점 OR 확장 (Case B)

다섯 곳의 외부 공식·학술 가이드가 일치된 권장을 제시합니다.

출처 권장 위치 핵심 근거
Elastic Boosting the power... Elastic Blog search-time 인덱스 크기·코퍼스 통계 보존, 무재색인
Apache Solr Better synonym handling... Better synonym handling in Solr query-time IDF 영구 왜곡 방지, 사전 hot reload
Lucene Token graphs + SynonymGraphFilter Elastic Blog, BM25Similarity (Lucene 9.x core API) search analyzer only token graph 평탄화 손실 회피
OpenSearch Synonym graph token filter OpenSearch Documentation search analyzer exclusively flatten_graph 불필요
Databricks vector_search function Databricks SQL hybrid 의 BM25 채널 = query_text, dense 채널 = query_vector 분리 입력 임베딩 채널 무영향, 비용 보존

Databricks Vector Search 의 hybrid 모드에서는 query_text 가 BM25 채널을, query_vector 가 ANN 임베딩 채널을 별도로 구동하고 결과는 RRF(rrf_param=60) 로 결합됩니다 Databricks SQL, Databricks. 따라서 동의어 OR 확장은 query_text 가 BM25 채널로 들어가기 직전에만 적용 하고, dense 채널의 임베딩은 사용자 원문 그대로 사용하는 것이 공식 패턴에 부합합니다. (i) 임베딩 모델의 의미 공간을 그대로 활용하면서, (ii) BM25 채널에서만 동의어 recall 을 확보하고, (iii) RRF 가 두 채널의 강점을 결합합니다.

Trotman et al. (ADCS 2014) 이 9 종 BM25 변형을 비교한 후 튜닝되면 변형 간 성능 차이는 작고, relevance feedback 과 stemming 같은 질의 확장 기법이 효과적 이라고 결론지은 점도 같은 방향입니다 — score 함수를 건드리기보다 질의 측 확장을 정교화하는 편이 ROI 가 높습니다 Improvements to BM25 and Language Models Examin.... Manning et al. Introduction to Information Retrieval 9장 Relevance feedback and query expansion 또한 동의어 처리는 thesaurus-based global query expansion 의 가장 흔한 형태이며 질의 확장 단계에서 다루는 것이 표준 이라고 정리합니다 Introduction to Information Retrieval, Ch. 9.

본 가이드 권장: 한국어 RAG 의 BM25/Hybrid 검색에서 동의어는 검색 시점 OR 확장 (Case B) 만 적용합니다. Hybrid 검색에서는 BM25 채널의 query_text 에만 확장을 반영하고, dense 채널의 query_vector 는 원문 임베딩을 유지합니다.

전제 조건 — 양방향 lookup 자동 빌드: Case B 가 value-only 어휘 (예: "캐리어"·"펫"·"위험품" — 대표어가 아닌 동의어 어휘) 로 검색하는 사용자에게도 효과적이려면 동의어 사전이 양방향 으로 빌드되어야 합니다. 본 가이드의 표준은 CSV (Solr synonym 형식) 단일 파일 (§9.1) — 운영팀은 그룹 단위로 작성하고 §10.3 load_synonyms런타임에 모든 어휘를 키로 등록 해 양방향 lookup 을 자동 빌드합니다. CI 빌드 단계 불필요. 한 방향으로만 확장해야 하는 관계는 => 단방향 모드(§6.6·§9.1)로 별도 표현하며, equivalent 그룹의 양방향 보장과 구분됩니다.

사용자 사전 vs 동의어 사전 시점 결정 매트릭스 — 두 사전의 적용 시점이 정반대 인 이유:

사전 종류 인덱싱 적용 검색 적용 이유
사용자 사전 (form·tag·boundary 재정의) 양쪽 동일 — 비대칭 시 §10.4 정책 D 와 동치 hit 0 (§10.6 정본)
동의어 사전 (df 통계 확장) Case B — 인덱스 시 확장은 IDF 평탄화·token graph 손실, 검색 시점이 코퍼스 통계 보존

 

A.6 출처

Elastic Blog: Boosting the power of Elasticsearch with synonyms — Elastic Blog Elasticsearch Reference: Synonym graph token filter — Elasticsearch Reference OpenSearch Documentation: Synonym graph token filter — OpenSearch Documentation Elastic Blog: Multi-Token Synonyms and Graph Queries in Elasticsearch — Elastic Blog Elastic Docs: Token graphs — Elastic Docs The Probabilistic Relevance Framework: BM25 and...: Robertson, S. & Zaragoza, H. (2009) — The Probabilistic Relevance Framework: BM25 and Beyond (PDF) BM25Similarity (Lucene 9.x core API): BM25Similarity (Lucene 9.x core API) SynonymQuery (Lucene 9.x core API): SynonymQuery (Lucene 9.x core API) Elastic Blog: Practical BM25 — Part 2 — Elastic Blog Better synonym handling in Solr: Nolan Lawson — Better synonym handling in Solr Databricks: Vector search cost management guide — Databricks Databricks Community: Behavior of Vector Index Sync with Delta Tables When Using OVERWRITE vs MERGE — Databricks Community Apache Solr Reference Guide: Filters — Apache Solr Reference Guide elastic/elasticsearch#37751: Korean (nori) Analysis Synonym Filter build failed — elastic/elasticsearch#37751 Making benchmark of different tokenizer in BM25...: AutoRAG — Making benchmark of different tokenizer in BM25 (Medium) KLUE: Korean Language Understanding Evaluation ...: KLUE: Korean Language Understanding Evaluation (arXiv 2105.09680) Databricks SQL: vector_search function — Databricks SQL Databricks: Query a vector search index — Databricks Improvements to BM25 and Language Models Examin...: Trotman, A., Puurula, A., Burgess, B. (2014) — Improvements to BM25 and Language Models Examined (PDF) Introduction to Information Retrieval, Ch. 9: Manning, Raghavan, Schütze — Introduction to Information Retrieval, Ch. 9


 

부록 B. IATA 사용자·동의어 사전 생성 및 성능 검증

IATA 항공사·공항 코드 사용자 사전과 동의어 사전을 외부 무료 데이터 소스 (OurAirports + OpenFlights) 로 자동 생성하고 Kiwi 토크나이저 단에서 성능·정확도를 검증한 실전 사례입니다. 처리 흐름은 Kiwi 가 형태소를 공백으로 join 한 토큰 문자열을 출력 → Databricks Vector Search FTS 가 그 문자열을 BM25 로 색인·검색 이고, 본 부록의 두 사전은 각각 코드를 한국어 결합 컨텍스트 (ICN공항에서 등) 에서도 단일 토큰으로 안정 분리 (사용자 사전) 와 한국어 풀이름 ↔ IATA ↔ ICAO 양방향 OR 확장 (동의어 사전) 을 담당합니다. §9.5 의 airag_iata.tsv 코드 블록은 최소 예시 (행 순서 규칙·라틴 score 양수 등 형식 시연) 이고, 본 부록은 그 형식을 운영급 규모 (NNP 최대 18,020행) 로 확장합니다.

⚠ 측정 환경 한계 본 부록 수치는 DBX sandbox (oneenv-yoosung-jeon-airag-usw2, us-west-2) 의 Serverless Job Compute (4 vCPU / 15.3 GB) 에서 측정한 결과입니다. A항공 운영 환경 (ap-northeast-2 dev/staging/prd) 과는 region·워크스페이스·워커 spec 이 다르므로, 상대 비교 (사전 크기별·warmup 후) 만 신뢰하세요.

 

B.1 사전 구축 — 데이터 소스·생성 스크립트

왜 외부 무료 소스인가

IATA 공식 Code Search 는 단건 조회만, Airline Coding Database유료 구독 입니다. 따라서 본 가이드는 OurAirports, OpenFlights 를 정본으로 사용합니다.

소스 라이선스 범위 갱신
OurAirports (GitHub davidmegginson/ourairports-data) Public Domain IATA 코드 있는 공항 9,057개 매일 갱신
OpenFlights airlines.dat (GitHub jpatokal/openflights) ODbL 활성 IATA 항공사 ~990개 2014 이후 정체

알려진 한계 (B.6 재인용) — OpenFlights 의 정체로 Air Premia(YP) 같은 2021 이후 신생 항공사가 raw 에 없습니다. 사전 구축 스크립트 안의 KE_AIRLINE_CODES (A항공 운영에 등장할 코드를 사람이 직접 선정한 set) 에 추가하면, raw 누락과 무관하게 사전에 포함됩니다.

사전 구축 스크립트

정본 위치: source/airag-rag/kiwi-tokenizer/data/builder/build_iata.py

# raw 자동 다운로드 (캐시: /tmp/iata) + 6 파일 생성 (user_dict/*.tsv · synonyms/*.csv 로 분리 출력)
python source/airag-rag/kiwi-tokenizer/data/builder/build_iata.py source/airag-rag/kiwi-tokenizer/data/

사전 파일 (3 범위 × {사용자 사전 TSV, 동의어 사전 CSV})

범위 사용자 사전 (NNP 등록) 동의어 사전 (equivalent 그룹) 의도
global airag_iata_global.tsv (18,020행) airag_iata_global.csv (8,006 그룹) 전 세계 활성 항공사 + 모든 IATA 공항
major airag_iata_major.tsv (10,548행) airag_iata_major.csv (5,011 그룹) 활성 항공사 + large/medium 공항만 (small·seaplane·heliport 제외)
ke airag_iata_ke.tsv (728행) airag_iata_ke.csv (360 그룹) 한국 국적사 + A항공 코드쉐어 + Star/SkyTeam/Oneworld 메이저 + 글로벌 허브 큐레이션

global — 사전 완전성 최대화. 단, 매뉴얼에 등장하기 어려운 소도시 공항 IATA(예: AAA, AAB) 가 다수 포함되어 false-positive 우려.

major — OurAirports type 컬럼이 large_airport·medium_airport 인 공항만. A항공 매뉴얼 도메인에 부합하는 적정 범위.
ke — A항공 매뉴얼·고객 문의에 실제 등장 가능성이 높은 코드만 큐레이션. false-positive 최소.

 

TSV ↔ CSV 정합 규칙

규칙 근거
TSV 는 IATA + ICAO 코드 모두 NNP 로 등록 매뉴얼·고객 문의에 동일 항공사·공항이 IATA(KE) 와 ICAO(KAL) 두 형태로 모두 등장 — 두 형태 모두 단일 토큰으로 인식해야 동의어 그룹이 작동
CSV 그룹 첫 토큰 = IATA 코드 (TSV NNP 등록 보장) 그룹 안 어휘는 어느 것이든 짝 TSV 에 NNP 로 등록되어 있어야 Kiwi 토큰화에서 분해되지 않음
단일 토큰 그룹 (ICAO·한국어 매핑 모두 없는 코드) 은 CSV 에서 제외 equivalent 모드에서 정규화 효과 없음 — 운영 명료성·사전 빌드 성능 양쪽에서 이득
한국어 풀이름은 CSV 의 내용 토큰 으로만 (TSV 에 별도 NNP 등록 없음) 도메인 한국어 어휘는 Kiwi 기본 분석기가 분해 안 함을 가정 — 분해 발견 시 TSV 에 NNP 추가
CSV 는 IATA 그룹 한 줄만 (ICAO 그룹 별도 행 두지 않음) equivalent 모드는 그룹 안 모든 어휘가 양방향 등가 — KE,KAL,대한항공 한 행만으로 KE↔KAL↔대한항공 모두 매칭. ICAO 그룹을 별도 행으로 두면 application 의 load_synonymsdup-token 룰로 그룹 자체를 skip + WARNING 로그
같은 ICAO 공유 다중 IATA 는 한 그룹으로 통합 예: SWRLX/SR 또는 TGWTR/TT 케이스 → TR,TT,TGW 한 행. 두 행으로 두면 ICAO 토큰이 두 그룹에 등장해 dup-token 위반

운영 주의

airag_*.tsv glob 으로 자동 로드되므로 current/ 디렉토리에는 3 범위 중 한 쌍만 배치하세요. 동시 로드 시 NNP 중복 등록 + 메모리 낭비가 발생합니다.


 

B.2 cold-start — 초기화·로드·메모리

측정 환경

항목 표기
노드 타입 serverless-driver-4cpu-low-mem-aws — 4 vCPU / 15.3 GB
Python · kiwipiepy 3.10.12 · 0.21.0

측정 결과

사전 NNP 행수 Kiwi 초기화 (ms) 사전 로드 (ms) 첫 호출 컴파일 (ms) 총 cold-start (ms) RSS 증가 (MB)
없음 (baseline) 0 1,047.8 0.0 1,276.9 2,324.7 428.0
ke 728 1,094.8 1.8 1,330.3 2,426.9 428.2
major 10,548 1,096.1 11.0 1,383.6 2,490.7 430.4
global 18,020 1,163.7 25.3 1,416.2 2,605.2 433.0

해설

  • 첫 호출 컴파일이 cold-start 지배 요인 — kiwipiepy 0.21.0 은 load_user_dictionary() 시점이 아닌 첫 tokenize() 호출 시점에 사전을 lazy 컴파일합니다 (global +139 ms, baseline 대비 +11 %). 운영자가 cold-start latency 를 산정할 때는 Kiwi 초기화 + 사전 로드 + 첫 호출 컴파일 을 모두 합산해야 정확합니다.
  • 사전 로드·메모리는 무시 수준 — 사전 크기에 비례해 선형 증가하지만 global 18,020행도 로드 25 ms / RSS +5 MB 에 그칩니다. Kiwi 코어 모델·embedding 이 RSS 의 대부분 (~428 MB).

 

B.3 인덱싱 측 성능 — tokenize_chunk (사용자 사전만)

§10.1 Pandas UDF 안의 tokenize_chunk 가 호출하는 토큰화 비용. 인덱싱 시점에는 사용자 사전 (TSV) 만 적용 되고 동의어 사전 (CSV) 은 호출되지 않습니다 (§9.1 정책).

벤치마크 데이터셋

항목 정의
입력 SSOT sandbox 합성 데이터 (A항공 매뉴얼 직접 접근 불가 — 운영 환경에서 A항공 매뉴얼 샘플로 재측정 권장)
입력 인덱싱 chunk 500~2000자, n=500 — 항공 매뉴얼 풍 합성 텍스트
warm-up 측정 전 100회 빈 호출 (JIT/캐시 안정화)

측정 결과

사전 p50 (ms) p95 (ms) throughput (texts/sec) 토큰 수/text 평균
없음 (baseline) 21.70 39.41 44.7 606.6
ke 23.13 41.01 42.2 606.6
major 22.85 40.71 42.6 606.8
global 23.30 44.84 40.4 606.8

해설

  • 사전 적용 throughput 손실은 극히 작음 — baseline → global 은 44.7 → 40.4 texts/sec (약 -9.6 %). 인덱싱 처리량에 미치는 영향은 미미합니다.
  • 토큰 수/text 평균이 사전별로 거의 동일 (606.6~606.8). 사전 적용은 토큰 수 가 아닌 개별 토큰의 형태·태그 만 바꿉니다 (§B.5 정확도 측정에서 확인).

 

B.4 검색 측 성능 — tokenize_query + load_synonyms + expand_query_for_bm25

§10.2 Model Serving 안의 검색 시점 전체 비용. 사용자 사전 (TSV) 토큰화 + 동의어 사전 (CSV) 양방향 OR 확장 두 단계가 합쳐집니다.

검색 시점 비용 구성

단계 함수 (§10.3) 호출 빈도 측정 sub-section
1. 토큰화 tokenize_query (= kiwi.tokenize()) 쿼리당 §B.4-(1)
2. 동의어 사전 로드 load_synonyms 워커 cold-start 1회 §B.4-(2)
3. 동의어 OR 확장 expand_query_for_bm25 쿼리당 §B.4-(3)
4. 검색 전체 (쿼리당) (1) + (3) 쿼리당 §B.4-(4)

벤치마크 데이터셋

항목 정의
입력 SSOT sandbox 합성 데이터
(1) 토큰화 입력 검색 쿼리 5~20자, n=500 — 예: ICN 출발편, 대한항공 마일리지, KE편 위탁수하물
(3) lookup 입력 500개 토큰 set (각 set 5~15 토큰), 한국어 + IATA + 사전 miss 토큰 혼합, hit rate ≈ 30 %, round-robin seed=20260528
warm-up 측정 전 100회 빈 호출

B.4-(1) tokenize_query 토큰화 latency (사용자 사전만)

사전 p50 (ms) p95 (ms) throughput (texts/sec) 토큰 수/text 평균
없음 (baseline) 0.150 0.527 3,690 3.95
ke 0.134 0.537 4,024 3.95
major 0.187 0.686 2,718 3.96
global 0.165 0.606 3,304 3.96

B.4-(2) load_synonyms cold-start (워커 시작 1회)

각 사전마다 별도 Python subprocess spawn 후 5회 median.

사전 그룹 수 로드 시간 (ms) RSS 증가 (MB) lookup 키 개수 skipped 그룹 dup-token 위반
ke 360 2.38 0.00 891 0 0
major 5,011 18.59 1.73 10,182 0 0
global 8,006 30.21 2.76 16,155 0 0

skipped/dup-token 위반이 모두 0 — 사전 구축 스크립트의 ICAO 그룹 제거 + 같은 ICAO 공유 IATA 통합 처리가 의도대로 작동합니다. lookup 키 개수가 그룹 수의 약 2~2.5배인 것은 양방향 매핑 (그룹 내 모든 어휘가 각자 key) 의 정상 결과입니다.

B.4-(3) expand_query_for_bm25 lookup latency (쿼리당)

사전 p50 (μs) p95 (μs) throughput (queries/sec) 평균 expansion 배수
ke 6.64 13.24 133,833 1.066
major 6.04 10.06 151,021 1.234
global 6.28 10.63 151,643 1.347

B.4-(4) 검색 전체 비용 (쿼리당) = (1) + (3)

워커 cold-start 의 (2) load_synonyms쿼리당 비용 아님 이라 제외. ke 사전 기준 검색 한 건의 비용 구성:

단계 latency (p50) 비중
(1) tokenize_query 134 μs ~95 %
(3) expand_query_for_bm25 6.6 μs ~5 %
검색 전체 (p50) ~140 μs 100 %

검색 전체 비용은 토큰화 비용이 지배 합니다. 동의어 확장은 사실상 무비용 추가.

해설

  • 검색 측 성능은 세 사전 모두 무비용 수준 — 검색 전체 p50 이 ke 140 μs / global 170 μs. 단일 RAG 요청의 BM25 확장 비용으로는 의사결정 변수가 아닙니다.
  • 사전 크기와 lookup latency 무관 — Python dict 의 O(1) 해시 lookup 가정이 실측에서 그대로 확인 (6~7 μs).
  • expansion 배수 1.07~1.35배 — 토큰 set 의 hit rate ≈ 30 % × 그룹당 평균 2~2.5 어휘 조합과 일치. 사전이 클수록 hit rate 가 살짝 늘어 expansion 도 커지지만, latency 영향 없음.
  • 운영 의사결정 시사 — 검색 측 성능 차이는 운영 의사결정 변수가 아닙니다. 사전 선택은 정확도 (§B.5 단일 토큰화율·false-positive) 가 결정합니다.

 

B.5 정확도 검증 — 단일 토큰화·라틴 SL 회피·컨텍스트

토큰화 자체의 정확도는 인덱싱·검색 양 시점에 공통으로 적용됩니다 (같은 kiwi.tokenize() 코어). 본 절은 시점 무관 정확도 검증입니다.

검증 케이스 SSOT 표 (회귀 검증 재실행 가능)

# 입력 텍스트 기대 토큰 검증 의도
1 ICN ICN/NNP (단독) 단독 IATA 단일 토큰화
2 RKSI RKSI/NNP (단독) ICAO 4자 단일 토큰화
3 ICN공항에서 ICN/NNP + 공항/NNG + 에서/JKB 한국어 결합 컨텍스트
4 KE편 예약 KE/NNP + 편/NNG + 예약/NNG 조사·의존명사 결합 ( 은 Kiwi 기본 모델에서 NNG 로 분류 — NNB 가 아님)
5 대한항공 대한항공/NNP (단일) 한국어 풀이름 — 동의어 그룹 내용 토큰의 토큰화 가정 검증
6 ABCD (미등록 라틴) 라틴 SL 기본 분해 NNP 미등록 대조 — score 5.0 효과

측정 결과 — SSOT 케이스

# 입력 baseline ke major global
1 ICN FAIL (ICN/SL) PASS PASS PASS
2 RKSI FAIL (RKSI/SL) PASS FAIL (RK/NNP + SI/NNP) FAIL (RK/NNP + SI/NNP)
3 ICN공항에서 FAIL (ICN→SL) PASS PASS PASS
4 KE편 예약 FAIL (KE→SL) PASS PASS PASS
5 대한항공 PASS PASS PASS PASS
6 ABCD (대조) PASS (ABCD/SL) PASS (ABCD/SL) FAIL (AB/NNP + CD/NNP) FAIL (AB/NNP + CD/NNP)

사전 등록 코드 단일 NNP 토큰화율 (sample n=100 per 사전)

사전 sample n 단일 NNP 토큰 단일 토큰화율
ke 100 99 99.0 %
major 100 58 58.0 %
global 100 57 57.0 %

발견된 이슈 — 항공 코드 도메인의 IATA-ICAO 부분 매칭 충돌

⚠ 본 측정의 가장 중요한 발견 — IATA 2자 코드 등록 수가 많을수록 ICAO 4자가 두 IATA 의 concatenation 으로 우연 분해. 사전 크기 자체가 아니라 항공 코드 도메인의 내재적 특성 입니다.

  1. ICAO 4자 코드 분해 (case 2)항공 도메인 내재적 특성: IATA(2~3자) 와 ICAO(3~4자) 가 같은 라틴 alphabet 공간을 공유. 사전에 등록된 IATA 2자 코드가 많아질수록 우연히 두 IATA 의 concatenation 이 ICAO 4자와 일치할 확률이 누적됩니다. 본 측정에서는 IATA RK + IATA SI동시 에 등록된 major/global 에서 ICAO 코드 RKSIRK/NNP + SI/NNP 로 분해. ke 사전은 IATA 2자 등록이 96개로 적어 (major/global 은 986개) 우연 매칭 확률이 100배 이상 낮아 PASS. 검색 영향: ICAO 4자 코드 (RKSI/RKPK/KLAX 등) 가 단일 NNP 로 보존되지 않아 BM25 매칭이 약화됩니다.
  2. 단일 NNP 토큰화율 격차 (ke 99 % vs major/global 57~58 %) — 위 메커니즘의 정량화. major/global 의 IATA 2자 986개 → 두 IATA concatenation 조합 ~972,196 가지 (96² ≈ 9,216 인 ke 대비 100배+). 등록된 NNP 의 절반 가까이가 다른 IATA prefix/suffix 와 우연 매칭되어 분해됩니다. 운영 영향: 항공 코드 도메인에서는 IATA 등록 수가 늘수록 ICAO 분해 충돌 확률 누적 — 사전 크기와 정확도는 비례 관계가 아닙니다.
  3. 라틴 SL 분해 대조군 무력화 (case 6)AB, CD 가 IATA 코드로 등록된 major/global 에서는 미등록 라틴 4자 ABCD 도 IATA 두 개의 concatenation 으로 인식되어 SL 단일이 아닌 AB/NNP + CD/NNP 로 분해. 위 1·2번과 같은 매커니즘이 NNP 미등록 라틴 시퀀스에도 영향 을 미칩니다.
  4. 태그 — 기대 NNB vs 실제 NNG 은 본래 의존 명사(NNB) 분류가 자연스럽지만 Kiwi 0.21.0 기본 모델은 일반 명사(NNG) 로 분해합니다. SSOT 기대값을 NNG 로 정정해 둡니다. (위 1~3번 매커니즘과 무관)

사용자 사전 효용 정량 검증 (ke 등록 코드 전체)

위 §B.5 단일 토큰화율은 사전 적용 후 검증이라 baseline 대비 사용자 사전이 얼마나 결정적인가 는 별도 검증이 필요했습니다. ke 등록 코드 728개 × 5 컨텍스트 (단독·조사 결합·한국어 결합·자연어 문장·라틴 결합) = 3,640 입력 으로 4 시나리오를 비교 측정했습니다.

시나리오 TSV CSV BM25 색인 보존율 동의어 lookup hit 율
S1 baseline only 98.8 % (719/728)
S2 baseline + CSV 98.8 % 77.2 %
S3 TSV only 98.6 % (718/728)
S4 TSV + CSV (현 권장) 98.6 % 78.6 %
카테고리 코드 수 baseline 보존 TSV 보존 TSV 우위
항공사 IATA 2-letter 96 90.6 % 100 % +9건 (alphanumeric IATA: 5J·5Y·7C·9C·A3·B6·F9·S7·Z2)
항공사 ICAO 3-letter 88 100 % 100 % 0
공항 IATA 3-letter 273 100 % 100 % 0
공항 ICAO 4-letter 271 100 % 96.3 % -10건 (KLAS·KLGA·LJLJ·MMPR·MUHA·NZAA·NZWN·RJAA·RJNK·UAAA — 항공사 ICAO 와 prefix·suffix 충돌)

발견·시사

  1. baseline Kiwi 단독으로 ke 등록 코드의 98.8 % 가 BM25 색인 보존됩니다. SEARCH_TAGS = {NNG, NNP, SL, SN, SH} 의 SL 포함 정책으로 라틴 IATA·ICAO 코드 대부분이 단일 SL 토큰으로 들어갑니다.
  2. 사용자 사전 TSV 의 결정적 효용은 alphanumeric IATA 9개에 한정5J·5Y·7C 등 숫자+영문 형식 코드는 baseline 이 5/SN + Y/SL 식으로 쪼개 검색·동의어 lookup 양쪽에서 깨집니다.
  3. TSV 는 부작용 10건을 동반 — ke.tsv 에 등록된 항공사 ICAO 3-letter (KL·AS·RJ 등) 가 공항 ICAO 4-letter (KLAS 등) 와 prefix·suffix 가 겹쳐 두 NNP 로 쪼개집니다. 위 1~3번 매커니즘과 동일한 IATA-ICAO 부분 매칭 충돌이 ke 사전 안에서도 작게나마 발생합니다.
  4. 순 효과 -1건 (TSV +9 / -10), 동의어 hit 율 차이 +1.4 %p — TSV 추가의 실질 검색 영향은 미미합니다.
  5. 운영 결정 — ke.tsv 728행 유지 (옵션 B): 미세 부작용 1건은 A항공 매뉴얼 트래픽에서 KLAS·RJAA 같은 미국·일본 공항 ICAO 가 드물게 등장한다는 가정 아래 실질 영향이 낮습니다. 사용자 사전 TSV 는 alphanumeric IATA 단일 토큰화 보장 + 미래 이형태 매핑 (§9.5 패턴) 기반 + SEARCH_TAGS 정책 변경 시 안전망 으로서 가치가 유지됩니다. 부작용이 운영에서 의미 있게 관측될 경우 옵션 A (TSV 를 alphanumeric IATA 9 행으로 축소) 또는 옵션 C (충돌 prefix 항공사 ICAO 제거) 로 전환합니다.

컨텍스트 의존성 비대칭 — TSV 의 4-letter ICAO 분해 10건은 단독 입력에서만 발생하고, 조사·한국어·자연어 문맥이 결합되면 4-letter 가 유지됩니다. 매뉴얼 본문에 ICAO 코드가 단독으로 등장하는 경우는 드물어 실제 운영 영향은 더 작을 가능성이 있습니다.


 

B.6 결론

3 사전 trade-off 결론

§B.5 가 보여준 IATA·ICAO 부분 매칭 충돌은 항공 코드 도메인의 내재적 특성 입니다 — IATA 2자 등록 수가 늘수록 ICAO 4자가 두 IATA 의 concatenation 으로 우연 분해될 확률이 누적됩니다. §B.4 의 검색 측 성능은 세 사전 모두 무비용 (검색 전체 ~140 μs/query) 이라 성능 은 의사결정 변수가 아니고, 정확도 가 결정합니다.

사전 권장 위치 근거
ke dev / staging / prd 공통 정본 권장 단일 NNP 토큰화율 99.0 % (sample n=100). IATA 2자 96개만 등록되어 ICAO 4자 분해 충돌 확률이 낮음. cold-start·throughput 도 baseline 과 유사 (긴 chunk -5.6 %). 검색 측 (load_synonyms 2.4 ms, expand 6.6 μs/query) 도 무비용. A항공 매뉴얼 도메인에 부합하는 코드만 큐레이션되어 false-positive 최소.
major 비권장 — IATA-ICAO 부분 매칭 충돌 누적 단일 NNP 토큰화율 58.0 %. IATA 2자 986개 → 두 IATA concatenation 조합이 ke 대비 100배+ 증가해 ICAO 4자 (RKSI) 가 RK + SI 로 분해. 등록한 NNP 의 절반이 의도대로 동작하지 않을 가능성.
global 비권장 — 충돌 누적 + false-positive 위험 단일 NNP 토큰화율 57.0 %. major 의 충돌 누적 + 9,000+ 소도시 공항 IATA 가 일반 텍스트와 우연 매칭 + 라틴 SL 회피 대조군 (ABCDAB+CD) 도 깨짐.

핵심 시사항공 코드 도메인에서는 IATA 등록 수가 늘수록 ICAO 분해 충돌 확률이 누적되므로 사전 크기와 정확도가 비례 관계가 아닙니다. A항공 큐레이션 (ke) 사전의 코드 set 을 A항공 운영팀이 점진적으로 확장 하는 전략이 major/global 채택 보다 운영 안정성·검색 품질 양쪽에서 우월합니다. 검색 측 성능은 세 사전 모두 충분하므로 (§B.4) 사전 크기 자체는 운영 결정 요소가 아닙니다.

§B.5 의 사용자 사전 효용 정량 검증 에 따르면 baseline Kiwi 단독으로도 ke 등록 코드의 98.8 % 가 BM25 색인 보존되며, 사용자 사전 TSV 의 결정적 효용은 alphanumeric IATA 9개 (5J·5Y·7C 등) 에 한정됩니다. 그럼에도 ke.tsv 728행을 그대로 유지하는 이유는 (1) alphanumeric IATA 단일 토큰화 보장, (2) §9.5 의 이형태 매핑 패턴 기반 확장 가능성, (3) SEARCH_TAGS 정책 변경 시 안전망 — 세 가지 보험 가치 때문입니다.


 

부록 C. PySpark Python worker 동작 원리

§10.1 Pandas UDF 가 어떻게 동작하는지 깊이 이해하기 위한 보조 자료입니다. 운영 가이드는 §10.1 본문이 정본이고, 본 부록은 디버그·튜닝 시 참조 용입니다.

 

C.1 아키텍처 — Executor JVM + Python worker

PySpark 의 Python 함수 (@pandas_udf·mapInPandas 등) 는 Executor JVM 과 분리된 별도 OS 프로세스 에서 실행됩니다.

Cluster 노드 (예: Linux VM)
├── Executor JVM 프로세스
│   ├── Spark Shuffle / Broadcast / RDD 처리 (Scala/Java)
│   └── PythonRunner — Python worker spawn 담당
│      │
│      │ socket + Arrow batch (IPC)
│      │
└── Python worker 프로세스 1 (별도 OS 프로세스, python3 인터프리터)
    ├── 모듈 import (kiwi_udf.py)
    │   ├── _kiwi: Kiwi   ← *이 프로세스 메모리 안* (~500MB)
    │   └── synonyms: dict (검색 측만)
    └── tokenize_chunk_udf() 실행
        ↑
└── Python worker 프로세스 2, 3, ..., N (서로 격리)

핵심 포인트:

  • Kiwi 인스턴스는 Python worker 프로세스 안 — JVM 안이 아님
  • Executor JVM ↔ Python worker 통신 = socket + Arrow batch (Arrow zero-copy, pickle 보다 10~100배 빠름)
  • Python worker 는 모듈 전역 변수 (_kiwi) 를 프로세스 lifecycle 동안 보존
  • Worker 간 격리 — 다른 worker 의 _kiwi 에 접근 못함, broadcast 불가 (사전 path 만 broadcast)

 

C.2 Job 실행 timeline

[T=0]  Job 시작 — Spark driver 가 Job graph 생성
   │
   ▼
[T=1]  Executor JVM spawn (cluster 의 노드들에 분산)
       (이 시점엔 아직 Python worker 없음)
   │
   ▼
[T=2]  첫 Pandas UDF task 분배 → Executor 가 Python worker 프로세스 spawn
       (Python worker 수 = task 동시성 — 보통 executor.cores 만큼)
   │
   ▼
[T=3]  Python worker 프로세스 시작
       ├── kiwi_udf.py 모듈 import
       │   ├── from kiwipiepy import Kiwi
       │   ├── from kiwi_common import KIWI_CONFIG, ...
       │   └── _kiwi = None    ← *모듈 전역 변수, 이 시점엔 아직 None*
       │
       └── 첫 task 호출: tokenize_chunk_udf(batch)
           ├── _get_kiwi() 호출
           │   ├── if _kiwi is None: True
           │   │     _kiwi = Kiwi(**KIWI_CONFIG)        ← *여기서 ~1초 소요*
           │   │     bootstrap_dictionary(_kiwi)         ← *TSV glob 로드, ~100~500ms*
           │   └── return _kiwi                          ← *이제 _kiwi 보존*
           ├── for batch_row in batch:
           │     _kiwi.tokenize(row, **INDEX_TOKENIZE_OPTS)   ← 빠름 (~1ms/row)
           └── 첫 task 완료
   │
   ▼
[T=4]  다음 task (같은 Python worker) — *재사용 (warm)*
       └── tokenize_chunk_udf(batch)
           ├── _get_kiwi() 호출 → _kiwi 이미 존재 → 재사용 (init 안 함)
           └── for batch_row in batch:
                 _kiwi.tokenize(row, ...)
   │
   ▼
[T=...]  Stage 안 모든 task 처리 — *같은 worker 재사용* (warm)
   │
   ▼
[T=END]  Stage 종료 → Executor 가 Python worker 유지하거나 종료
         (`spark.python.worker.reuse=true` 기본 → 다음 Stage 도 재사용)
   │
   ▼
[T=JOB_END]  Job 끝 → Executor 종료 → Python worker 프로세스 종료
             → 운영체제가 메모리 회수 → _kiwi 객체 폐기

워커당 cold start 1회 + 이후 수많은 task·batch 가 같은 Kiwi 재사용.

 

C.3 worker reuse + memoryOverhead

  • spark.python.worker.reuse=true (기본): task 끝나도 worker 유지 → Stage 간 _kiwi 보존
  • spark.executor.memoryOverhead: Python worker 메모리 영역 (JVM Heap 외)
  • 권장: worker 수 × ~500MB (Kiwi 본체 LM 모델 'cong' + 내장 사전 + Arrow buffer). 사용자 사전 ~수천 행 추가분 무시 가능.
  • 부족 시 OOM, executor kill 위험
  • Serverless for Jobs: Databricks 가 위 두 설정을 자동 관리. 운영자 명시 불필요

 

부록 D. 한국어 형태소 품사 태그 레퍼런스

본문 §2·§4·§10 에서 등장한 NNG, JKS, EF, VCP 같은 태그를 이해하기 위한 레퍼런스입니다. 한국어 형태소 분석의 사실상 표준은 세종 코퍼스 태그셋이며, Kiwi (kiwipiepy) 는 세종 태그셋을 거의 그대로 채택하고 일부 확장 태그(Z_*, W_*, USER0~4) 를 추가로 출력합니다.

 

D.1 세종 표준 태그셋 (Kiwi 공통)

대분류 태그 한국어 명칭 의미 예시 단어 RAG/BM25 색인 활용도
체언 NNG 일반명사 사물·개념·동작의 일반 이름 항공기, 운항, 정비 권장 포함
체언 NNP 고유명사 사람·기관·지명 등 고유 이름 대한항공, 인천공항 권장 포함
체언 NNB 의존명사 단독으로 못 쓰는 명사 것, 수, 명, 킬로그램 선택 포함 (단위·수량성 일부)
체언 NR 수사 수를 나타내는 단어 하나, 둘, 셋 선택 포함
체언 NP 대명사 사람·사물을 대신 가리키는 말 나, 너, 우리, 그것 제외 권장
용언 VV 동사 움직임·작용을 나타내는 어간 가-, 먹-, 운항하- 선택 포함 (어간만)
용언 VA 형용사 상태·성질을 나타내는 어간 예쁘-, 길-, 빠르- 선택 포함 (어간만)
용언 VX 보조용언 본용언을 보조하는 용언 가다(보-), 싶-, 두- 제외 권장
용언 VCP 긍정지정사 "이다" 의 어간 이- 제외 권장
용언 VCN 부정지정사 "아니다" 의 어간 아니- 제외 권장
수식언 MM 관형사 체언을 꾸미는 단어 새, 헌, 모든, 첫 선택 포함
수식언 MAG 일반부사 용언·문장을 꾸미는 부사 매우, 빨리, 다시 제외 권장
수식언 MAJ 접속부사 문장·구절을 잇는 부사 그러나, 또한, 그리고 제외 권장
독립언 IC 감탄사 감정·부름·응답을 나타내는 말 아, 네, 어머 제외 권장
조사 JKS 주격조사 주어 표시 이/가 제외 권장
조사 JKC 보격조사 "되다/아니다" 앞의 보어 이/가 제외 권장
조사 JKG 관형격조사 소유·수식 제외 권장
조사 JKO 목적격조사 목적어 표시 을/를 제외 권장
조사 JKB 부사격조사 처소·도구·자격 등 에, 에서, 로 제외 권장
조사 JKV 호격조사 부름 표시 아, 야 제외 권장
조사 JKQ 인용격조사 인용 표시 라고, 고 제외 권장
조사 JX 보조사 의미 보강 은/는, 도, 만 제외 권장
조사 JC 접속조사 체언 연결 와/과, 하고 제외 권장
어미 EP 선어말어미 시제·존대 -었-, -겠-, -시- 제외 권장
어미 EF 종결어미 문장 끝맺음 -다, -까, -네 제외 권장
어미 EC 연결어미 절·구 연결 -고, -며, -지만 제외 권장
어미 ETN 명사형전성어미 용언 → 명사화 -음, -기 제외 권장
어미 ETM 관형형전성어미 용언 → 관형사화 -ㄴ, -는, -ㄹ 제외 권장
접사 XPN 체언접두사 명사 앞 접두사 맏-, 햇-, 헛- 제외 권장
접사 XSN 명사파생접미사 명사 뒤 접미사 -님, -들, -화 선택 포함
접사 XSV 동사파생접미사 명사 → 동사 -하-, -되-, -시키- 제외 권장
접사 XSA 형용사파생접미사 명사 → 형용사 -답-, -롭-, -스럽- 제외 권장
접사 XR 어근 단독으로 못 쓰는 어근 깨끗(-하다) 선택 포함
기호 SF 마침표·물음표·느낌표 종결 부호 . ? ! 제외 권장
기호 SP 쉼표·가운뎃점 등 분리 부호 , · : / 제외 권장
기호 SS 따옴표·괄호 묶음 부호 " ' ( ) [ ] 제외 권장
기호 SE 줄임표 말줄임 제외 권장
기호 SO 붙임표 물결·하이픈 ~ - 제외 권장
기호 SW 기타기호 화폐·수학기호 ₩ $ % + = 제외 권장
외국어 SL 외국어 라틴 알파벳 등 Databricks, ULD, AWB 권장 포함
한자 SH 한자 한자 표기 大韓航空, 運航 권장 포함
숫자 SN 숫자 아라비아 숫자 2026, 747, 23 권장 포함
분석불능 UN 분석불능 사전 미등재·OOV (오타·신조어) 선택 포함 (디버깅)

 

D.2 Kiwi 특수 확장 태그

태그 한국어 명칭 의미 예시 색인 활용도
Z_CODA 종성 (받침) 어절 말 종성 분리 잔존 "사랑해" → 해 + ㅇ 제외 권장
Z_SOT 어절 시작 (내부) 어절 시작 마커 제외 권장
W_URL URL 웹 주소 https://example.com 선택 포함
W_EMAIL 이메일 이메일 주소 user@dom.com 선택 포함
W_HASHTAG 해시태그 SNS 해시태그 #항공안전 권장 포함 (SNS 코퍼스)
W_MENTION 멘션 SNS 멘션 @koreanair 선택 포함
W_SERIAL 일련번호 다중-하이픈 패턴 (전화번호 등) — 항공 코드 (KE001·B777) 는 SL+SN 분리되어 W_SERIAL 안 됨 010-1234-5678 선택 포함
W_EMOJI 이모지 유니코드 이모지 제외 권장
USER0~USER4 사용자 정의 사용자 사전 TSV (load_user_dictionary) 정의 슬롯 도메인 용어 권장 포함

 

D.3 본 가이드의 화이트리스트 정책

SEARCH_TAGS = {"NNG", "NNP", "SL", "SN", "SH"}    # 본 가이드 기본 (§10.3)
OPTIONAL    = {"NNB", "VV", "VA"}                  # 도메인 평가 후 recall 확장 (단위명사·동사·형용사)
# 비포함 (검토 후 제외):
#   W_SERIAL   — 전화 다중-하이픈 패턴만 (항공 코드 KE001·B777 미작동, v4.0 실측)
#   W_HASHTAG  — A항공 매뉴얼 본문에 거의 등장 안 함
#   USER0~4    — TSV-only 정책에서 미사용 (사용자 사전 어휘는 NNG/NNP 슬롯)
#   MM·XR·XSN  — 검색 키워드 가치 낮음

출처: Kiwi 공식 품사 태그 문서, 국립국어원 21세기 세종계획 형태분석 말뭉치 태그셋(43종)


 

부록 E. Nori 기본 stoptags vs Kiwi 화이트리스트 비교

OpenSearch/Elasticsearch 의 표준 한국어 분석기 Nori 와 본 가이드 채택안 Kiwi 의 BM25 색인 토큰 정책을 비교합니다. Nori 는 mecab-ko-dic 기반에 Lucene 자체 구현을 더한 분석기로, OpenSearch/Elasticsearch 인덱스에 내장되어 동작합니다.

 

E.1 Nori 가 색인에 포함하는 품사

카테고리 태그 (한국어 명칭)
체언 NNG (일반명사), NNP (고유명사), NNB (의존명사), NR (수사), NP (대명사)
용언 (어간) VV (동사), VA (형용사), VX (보조용언), VCP (긍정지정사), VCN (부정지정사)
기타 XR (어근)
외래·기호 SL (외국어), SH (한자), SN (숫자)

체언 5종 + 용언 어간 5종 + 외래어·숫자·한자 + 어근(XR) 이 기본 색인에 살아남습니다.

 

E.2 Kiwi SEARCH_TAGS vs Nori 비교

태그 (한국어 명칭) Kiwi SEARCH_TAGS Nori 차이 해설
NNG (일반명사) 동일 — 검색 핵심
NNP (고유명사) 동일
NNB (의존명사) △ 사용자 사전 NNG 승격 시 ✓ 기본 Nori 가 더 관대 (단위명사 자동 색인)
NR (수사) Nori 는 "하나"/"둘" 토큰화
NP (대명사) Nori 는 대명사 색인
SL (외국어) 동일
SN (숫자) 동일
SH (한자) 동일
VV (동사) Nori 는 동사 어간 색인
VA (형용사) Nori 는 형용사 어간 색인
XR (어근) Nori 는 한자어 어근 색인
MAG (일반부사) / MAJ (접속부사) 동일 — 둘 다 제거
J (조사) 동일 — 둘 다 제거
E (어미) 동일 — 둘 다 제거
MM (관형사) 동일 — 둘 다 제거

 

E.3 파생·전성 명사 실측 비교 (Nori vs Kiwi)

§4.2 의 후처리 병합 동기를 실측으로 확인합니다. Nori 는 self-managed OpenSearch 2.19 + analysis-nori 플러그인(표준 nori 분석기 = nori_tokenizer decompound_mode=discard + nori_part_of_speech 기본 stoptags) 으로 측정했습니다. Amazon OpenSearch 관리형(2.19)에는 nori 가 미포함이며 한국어 분석기로 seunjeon(동일 mecab-ko-dic 계열) 을 제공합니다 — 본 실측은 nori 를 직접 설치해 측정한 값입니다.

입력 Nori 표준(discard) Kiwi 기본 Kiwi + merge_derived_nouns
알림 ['알리'] (VV 어간) [] (전부 탈락) ['알림']
할인권 ['할인','권'] ['할인'] ['할인권']
당뇨식 ['당뇨','식'] ['당뇨'] ['당뇨']
위탁수하물 ['위탁','수','하물'] ['위탁','수하물'] ['위탁','수하물']
모임 ['모임'] ['모임'] ['모임']

관찰:

  • 알림 통째 보유는 어느 쪽도 안 함 — Nori 는 알리(VV 어간) 로 분해해 색인(동사 어간을 stoptags 가 안 버림, E.1/E.2). Kiwi 는 명사 화이트리스트라 알림 이 0 토큰. 표면형 알림merge_derived_nouns 만 보존합니다. (Nori decompound_mode=none 이면 알림 surface 가 남으나 POS 는 여전히 VV 입니다.)
  • 할인권 — Nori 는 할인+(권=NNG) 으로 일관 분해. Kiwi 기본은 권/XSN 탈락으로 ['할인'], 병합으로 ['할인권'] 통째 보존.
  • 당뇨식 이 Nori 에선 NNG 라 당뇨+ 둘 다 색인되지만, Kiwi 에선 식/NNB(의존명사) 라 탈락하고 병합 대상도 아님(NNB 과병합 위험). Kiwi 측은 사용자 사전(§5.2) NNG 등록으로 해결합니다.
  • 복합명사 — Nori discard수하물+하물 까지 과분해하는 반면, Kiwi split_complex=True수하물 단일 명사를 유지해 도메인 어휘 보존에 유리합니다.

 

E.4 차이점 해설

  • 토큰 폭/recall vs precision — Nori 가 Kiwi SEARCH_TAGS 보다 훨씬 넓은 토큰 집합. RAG precision 측면 Kiwi 유리, recall 측면 Nori 유리.
  • 복합명사 분해 — Nori decompound_mode 3 모드 (none·discard·mixed) 대응. Kiwi split_complex=True 는 Nori mixed 와 유사 사상.
  • 사용자 사전 — Nori 는 인덱스 settings 단계 로드 + close/open 사이클. Kiwi 는 런타임 API 라 재배포만으로 충분.
  • 운영 위치 — Nori 는 OpenSearch/Elasticsearch 내장. Kiwi 는 Spark UDF / Model Serving in-process. Databricks Vector Search 에서 Nori 자체는 적용 불가.
  • 지정사 처리 — Nori 는 VCP·VCN 색인. Kiwi SEARCH_TAGS 는 제외.

 

📚 시리즈 — 한국어 RAG 검색 (Kiwi)

댓글