[Milvus] 다국어 하이브리드 검색에서 multi analyzer는 효과가 있을까? (MIRACL 벤치마크)

11 분 소요

들어가며

  • Milvus 2.6.22에서 HNSW(dense) + BM25 하이브리드 검색을 하려고 한다. 클러스터는 언어권별로 따로 둔다.
    • KR 클러스터: 한국어 위주, 일본어·영어가 일부 섞인다.
    • JP 클러스터: 일본어 위주, 한국어·영어가 일부 섞인다.
  • 용도는 앱 검색이라 유저는 주로 키워드로 검색할 것으로 본다.
  • 고민한 질문: 문서 언어를 감지해 multi_analyzer_params로 언어별 토크나이저를 쓰면, 토크나이저 하나로 통일할 때보다 실제로 나은가? 애매한 쿼리는 여러 분석기로 fan-out해서 RRF로 합치는 것도 같이 검토했다.
  • 공개 데이터셋으로 직접 벤치마크를 돌려 확인했다.

결론부터

질문 결론
KR 클러스터에 multi analyzer가 필요한가? 필요 없다. 한국어 토크나이저(ko-dic)는 섞여 들어오는 일본어·영어도 충분히 처리한다. multi analyzer를 쓰면 일본어 쿼리는 오히려 소폭 낮았다(질문형 −0.012만 겨우 유의).
JP 클러스터에 multi analyzer가 필요한가? 필요하다. 일본어 토크나이저(ipadic)는 한국어를 처리하지 못한다. 한국어 위주 텍스트만 ko-dic으로 보내면 한국어 검색 품질이 크게 오르고, 일본어·영어 쪽 손해는 없다.
애매한 쿼리를 fan-out해야 하나? 하지 않는다. 이득이 없고, RRF로 합치면 오히려 떨어진다.
dense + BM25 랭킹은 RRF가 최선인가? 아니다. 이 데이터에서는 RRF가 dense만 쓸 때보다도 나빴다. 앱에서 직접 min-max 정규화한 뒤 가중합(dense 0.7)한 방식이 가장 좋았다. Milvus WeightedRanker(norm_score)는 dense만 쓸 때보다 나을 게 없었다(KR +0.003, JP −0.020).
리랭커(bge-reranker-v2-m3)를 붙이면? 효과가 크다. 상위 20개를 다시 정렬하면 nDCG@10이 KR +0.056, JP +0.072 올랐다. 키워드·짧은 쿼리에서 특히 크다. 대신 쿼리마다 모델 추론 비용이 든다.
텍스트 정규화가 필요한가? 넣자. NFKC + removepunct / asciifolding / 영어 stemmer. 이 데이터에서는 효과가 작았지만 손해도 없었다.

최종 권장 flow

  1. 텍스트 정규화: insert와 query 전에 앱에서 NFKC를 적용한다.
  2. BM25 분석기
    • KR: ko-dic 하나로 처리한다.
    • JP: 텍스트(문서 하나, 쿼리 하나)마다 우세한 언어를 정해서 multi analyzer로 보낸다(ko-dic / ipadic / english). insert와 query에 같은 규칙을 쓴다.
    • 공통 필터: lowercase, removepunct, asciifolding, 영어 stemmer
    • fan-out은 하지 않는다.
  3. 후보 검색: dense 검색과 BM25 검색을 각각 실행해서 상위 50~100개씩 가져온다.
  4. 합치기: 리스트마다 min-max 정규화를 한 뒤 0.7 × dense + 0.3 × BM25로 더한다. 가중치는 실제 검색 로그로 튜닝한다.
  5. 리랭킹: bge-reranker-v2-m3로 상위 20개를 다시 정렬한다. 지연시간과 GPU 비용은 따로 확인한다.

핵심 개념과 이유 정리

왜 KR은 multi analyzer를 안 쓰고 JP만 쓰나

  • 한 줄로 정리하면 이렇다.
    • 영어는 어느 토크나이저를 써도 잘 잘린다. 그래서 이슈가 없다.
    • ko-dic은 일본어도 꽤 잘 처리한다. 한국어+일본어를 ko-dic 하나로 insert·query해도 큰 문제가 없다.
    • ipadic은 한국어를 처리하지 못한다. 그래서 JP에서는 규칙에 따라 multi analyzer로 한국어만 ko-dic으로 보낸다.
  • ko-dic → 일본어: 한자어 사전이 있어서 太陽誘電の創始者를 太陽 / 誘 / 電 / の / 創始 / 者처럼 잘게 쪼갠다. 문서와 쿼리를 똑같이 쪼개기 때문에 BM25 매칭이 유지된다. KR에서 multi analyzer를 써도 하이브리드 전체 차이는 ±0.004 이내였고, 일본어 쿼리만 보면 −0.008~−0.012로 소폭 낮았다(질문형만 겨우 유의하고 나머지는 유의하지 않음).
  • ipadic → 한국어: 비행장은, 제주도민들이처럼 조사가 붙은 어절을 통째로 토큰으로 만든다. 그래서 비행장으로 검색하면 매칭되지 않는다. JP에서 multi analyzer를 쓰면 한국어 쿼리가 하이브리드 +0.018~0.034, BM25 +0.10~0.16 올랐다.
  • 그럼 JP도 ko-dic 하나로 통일하면 되지 않나? ko-dic의 일본어 처리는 ipadic에 가깝지만 같지는 않다. JP 코퍼스 전체를 ko-dic으로 통일하면 일본어 쿼리가 −0.006~−0.019 떨어졌다. KR에서는 일본어 비중이 작아 이 손해를 무시할 수 있지만, JP에서는 주력 언어라 그럴 수 없다. 그래서 JP는 일본어는 ipadic, 한국어는 ko-dic으로 나눈다.

언어 지정 규칙 (JP): insert와 query에 똑같이 적용한다

  • KR은 multi analyzer를 쓰지 않으므로 insert·query 모두 ko 하나로 처리한다. 아래 규칙은 JP용이다.
  • 기준은 인스턴스의 주력 언어가 아니라 그 텍스트(문서 하나, 쿼리 하나)에서 많이 쓰인 언어다. 언어를 판별할 수 없는 경우(한자만 있음)에만 주력 언어로 보낸다.
텍스트 지정할 언어 이유
한국어 위주 (일본어·영어 단어가 섞여 있어도) ko ipadic은 조사가 붙은 한국어 문장을 거의 매칭하지 못한다.
일본어 위주 (한국어 고유명사·영어가 섞여 있어도) ja 가나는 일본어라는 확실한 신호다. 카카오톡, 네이버 웹툰 같은 한국어 고유명사는 ipadic과 ko-dic이 똑같이 자른다. ipadic은 영어도 chatgpt / で / 画像처럼 잘 자르지만, 영어 분석기는 일본어를 통째로 토큰 하나로 만든다.
영어만 있음 en 어간 처리(apps → app)와 불용어 처리를 받는다. 주력 언어 분석기와 차이가 작아서 어느 쪽이어도 큰 문제는 없다.
한자만 있음 ja (인스턴스 주력 언어) 일본어인지 중국어인지 구분할 수 없다. 중국어 문서는 거의 없으니 주력 언어로 보낸다. 참고로 KR에서는 한자 키워드를 ko-dic으로 처리한 결과가 ipadic보다 좋았다(BM25 0.578 vs 0.545). 다만 JP에서는 일본어 문서가 ipadic으로 저장되므로, 한자 쿼리도 같은 ipadic으로 잘라야 토큰이 맞는다.
  • “한글이 하나라도 있으면 ko”로 하면 안 되는 이유: 일본어 위주 문서에 한국어 단어 몇 개가 섞였다고 ko로 저장하면, 일본어 부분까지 ko-dic으로 잘린다. 그러면 ja로 처리된 일본어 쿼리와 토큰이 어긋난다.
    • カカオトーク: ko-dic은 한 덩어리 / ipadic은 カカオ / トーク
    • 画像: ko-dic은 画 / 像 / ipadic은 画像
  • 이 규칙에 깔린 가정: 유저는 문서의 주된 언어로 검색한다.
    • 한국어 위주 문서(ko로 저장)를 그 안에 섞인 일본어 복합어로 검색하면 토큰이 어긋날 수 있다.
    • 영어와 한국어 고유명사는 어느 분석기든 똑같이 잘라서 영향이 없다. 하이브리드에서는 dense가 상당 부분 메워 준다.
  • 한국어가 섞인 혼합 텍스트(한국어+일본어, 한국어+영어) 규칙은 직접 측정한 결과가 아니다. 토큰화 결과와 언어별 측정 결과로 판단한 것이다. 일본어+영어 혼합은 직접 측정했다(결과 1 참고).

  • insert와 query 규칙이 같아야 하는 이유: BM25는 토큰이 정확히 일치해야 점수가 난다. 같은 문자열이라도 insert 때 분석기 A, query 때 분석기 B로 자르면 토큰이 달라져 매칭이 깨진다. 그래서 같은 감지 함수를 양쪽에 쓴다.
  • Milvus에서 지정하는 위치
    • insert: multi_analyzer_params의 by_field로 지정한 필드(예: lang)에 언어 값을 넣는다.
    • query: 검색 파라미터에 analyzer_name으로 같은 값을 넘긴다.

fan-out은 왜 효과가 없나

  • fan-out은 insert 언어는 정해 두고, query 언어가 애매할 때 후보 분석기 2~3개로 각각 검색해서 합치는 방식이다.
  • 이득이 나려면 같은 문자열을 분석기마다 다르게 자르고, 정답 문서가 다른 분석기로 저장돼 있어야 한다. 실제로는 이런 경우가 거의 없다.
    • 영어 부분은 ko-dic, ipadic, english 모두 소문자 + 단어 단위로 똑같이 자른다. 그래서 어느 분석기로 검색해도 같은 토큰이 나오고, 같은 문서가 걸린다.
      • 예: 문서 ChatGPTで画像生成できるアプリ를 ja로 저장하면 토큰은 chatgpt / で / 画像 / 生成 / できる / アプリ다.
      • 쿼리 ChatGPT는 ja, en, ko 어느 분석기로 잘라도 chatgpt가 되어 이 문서와 매칭된다. ja와 en으로 fan-out하면 같은 결과를 두 번 받는 셈이다.
      • 예외는 어간 처리뿐이다(아래 stemmer 참고).
    • 한국어+일본어 혼합 쿼리를 ja로 검색하면 조사가 붙은 한글 부분은 매칭되지 않는다(고유명사는 ko와 똑같이 잘린다). 결국 ko 하나로 검색하는 것보다 나아질 게 없다.
  • 그래도 매칭이 살아나는 경우는 있다. 문서와 쿼리가 서로 다른 분석기로 처리됐고, 그 분석기들이 같은 단어를 다르게 자를 때다.
    • 예: 문서 속 太陽誘電은 ja로 저장돼 한 토큰인데, 쿼리가 ko로 처리돼 太陽 / 誘 / 電이 되는 경우
    • 예: ko로 저장된 한국어 위주 문서 속 カカオトーク를 일본어 쿼리로 찾는 경우
    • 실제 측정(fan-out 대상 쿼리, BM25, 점수 기준 병합)에서 좋아진 쿼리는 KR 11개, JP 18개였다. 나빠진 쿼리는 KR 20개, JP 50개로 약 2~3배 많았다(같음: KR 160개, JP 469개).
  • 손해가 나는 이유: 틀린 분석기로 자른 쿼리는 엉뚱한 문서와 한두 글자만 겹친다. 그런데 RRF는 점수를 보지 않고 순위만 본다. 그래서 그 노이즈 결과의 1등이 정상 결과의 1등과 같은 가산점을 받는다.
  • 문서별로 BM25 점수가 높은 쪽만 남기는 방식으로 합치면, 노이즈는 점수가 낮아서 뒤로 밀린다. 그러면 손해는 사라지지만 이득도 없다. 쿼리 수만 늘어난다.

min-max 정규화 가중합

  • dense 점수(cosine)는 대략 0.3~0.9, BM25 점수는 0~수십이다. 범위가 달라서 그대로 더할 수 없다.
  • min-max 정규화: 쿼리마다 각 결과 리스트 안에서 (점수 − 최솟값) / (최댓값 − 최솟값)으로 0~1로 맞춘다.
  • 그다음 0.7 × dense + 0.3 × BM25로 더한다. 한쪽 리스트에 없는 문서는 그쪽 점수를 0으로 본다.
  • 예: dense 점수가 0.40~0.80, BM25 점수가 2~22 범위일 때
    • 문서 A: dense 0.80 → 1.0, BM25 12 → 0.5 → 0.7×1.0 + 0.3×0.5 = 0.85
    • 문서 B: dense 0.60 → 0.5, BM25 22 → 1.0 → 0.7×0.5 + 0.3×1.0 = 0.65
    • A가 B보다 위에 온다.
  • 이번 벤치마크에서는 리스트마다 상위 100개를 가져왔다. 꼭 100개일 필요는 없지만, 보여줄 개수보다 넉넉히(상위 10개를 보여준다면 50~100개) 가져와야 한쪽 리스트에만 있는 좋은 문서가 빠지지 않는다.
    • 가져오는 개수가 바뀌면 min-max의 최솟값(리스트의 마지막 문서 점수)도 바뀐다. 그러니 가중치도 실제로 쓸 개수 기준으로 튜닝한다.
  • RRF(1 / (k + 순위)의 합)는 점수를 버리고 순위만 쓴다. 그래서 두 리스트를 늘 같은 비중으로 섞게 된다.
  • Milvus 2.6의 WeightedRanker는 min-max가 아니라 고정 함수(BM25는 2·atan(s)/π)로 정규화한다. 그래서 min-max를 쓰려면 dense 검색과 BM25 검색을 따로 호출하고 앱에서 합쳐야 한다.

NFKC 정규화

  • 유니코드 정규화 방식 중 하나다. 모양만 다르고 의미가 같은 문자를 표준 형태로 바꾼다.
    • 전각 영문·숫자 → 반각: ChatGPT15 → ChatGPT15
    • 반각 가타카나 → 전각: アプリ → アプリ
    • 원문자 숫자 → 숫자: ① → 1
  • 일본어 텍스트에는 전각 영문이 흔하다. 그대로 두면(정규화·필터 없이) chatgpt와 chatgpt가 다른 토큰이 된다.
  • Python에서는 unicodedata.normalize("NFKC", text) 한 줄이다. insert와 query 양쪽에서, 텍스트를 Milvus에 넘기기 전에 적용한다.

정규화 설정 (analyzer filter)

  • lowercase: 영문을 소문자로 바꾼다.
  • removepunct: lindera가 내보내는 공백·구두점 토큰(' ', ?)을 지운다.
  • asciifolding: 악센트나 전각 라틴 문자를 ASCII로 접는다(é → e, c → c).
  • stemmer (english): 영어 단어를 기본형으로 줄인다(apps → app, running → run, edited → edit). 넣어도 画像, 번역 같은 일본어·한국어 토큰은 그대로라 부작용이 없었다. 필요한 경우는 두 가지다.
    • 같은 분석기 안에서 형태가 다를 때 (더 흔하다): 문서 photo editing apps와 쿼리 photo edit app은 stemmer가 없으면 apps / editing과 app / edit으로 갈려 매칭되지 않는다. en 분석기에는 stemmer가 원래 들어 있지만 ja·ko 분석기에는 없다. 그래서 일본어·한국어 문서 속 영어 단어는 이 문제가 그대로 생긴다.
    • 저장과 검색 분석기가 다를 때: en으로 저장된 apps는 app이 된다. 그런데 쿼리를 ja로 자르면 apps 그대로라 매칭되지 않는다. ja·ko에도 stemmer를 넣으면 양쪽 모두 app이 된다.
  • NFKC를 따로 해야 하는 이유: 필터는 토큰을 자른 뒤에 적용된다. 그런데 토크나이저가 전각 숫자 15를 1 / 5로 먼저 쪼개 버린다. 그래서 자르기 전에 앱에서 NFKC로 바꿔 두는 것이 확실하다.

벤치마크 구성

  • 데이터: MIRACL dev (ko / ja / en). 사람이 판정한 정답과 hard negative가 들어 있다.
    • KR용: 한국어 문서 43k + 일본어 5k + 영어 2.5k
    • JP용: 일본어 문서 40k + 한국어 5k + 영어 2.5k
  • 쿼리 형태: 원래 질문형(q)에 더해, 앱 검색을 흉내 내려고 정규식으로 키워드형(kw)과 2단어형(short)을 만들었다. 토크나이저로 만들면 특정 토크나이저에 유리해지기 때문에 정규식만 썼다.
    • 예: 森上亜希子の出身はどこ → 森上亜希子 出身
    • 예: 룩셈부르크의 수도는 어디인가? → 룩셈부르크 수도
  • 임베딩: bge-m3 dense (1024차원), HNSW (M=16, efConstruction=200)
  • 비교 구성
    • C0: 기본 언어 토크나이저 하나 (KR은 lindera ko-dic, JP는 lindera ipadic)
    • C1: multi analyzer. insert할 때 언어를 감지해 lang 필드에 넣고, query할 때는 감지된 언어 하나로 analyzer_name을 지정
    • C2: C1 + 언어가 애매한 쿼리를 후보 분석기 여러 개로 fan-out. 두 클러스터에 같은 규칙을 썼다.
      • 한자만 있는 쿼리 → ja / zh (zh는 jieba 분석기로, fan-out 후보로만 썼다)
      • 한글과 가나가 섞인 쿼리 → ko / ja (이번 데이터에는 해당 쿼리가 없었다)
      • 일본어+영어 쿼리의 ja / en fan-out은 JP에서 따로 측정했다.
  • 지표
    • nDCG@10: 상위 10개 결과에 정답 문서가 얼마나 위쪽에 있는지를 0~1로 나타낸 점수다. 높을수록 좋다.
    • 95% 신뢰구간 (paired bootstrap): 같은 쿼리에서 비교 대상(기준 구성)과의 점수 차이를 구한 뒤, 쿼리를 무작위로 다시 뽑는 시뮬레이션을 1만 번 돌려 차이의 범위를 추정했다.
      • +0.034 [+0.017, +0.054]처럼 구간이 0을 포함하지 않으면 실제로 차이가 있는 것이다.
      • −0.008 [−0.017, +0.000]처럼 0을 포함하면 차이가 있다고 말할 수 없다.

결과 1. multi analyzer

하이브리드(min-max 정규화 가중합, dense 0.7) 기준 C0 대비 nDCG@10 변화.

쿼리 KR 클러스터 JP 클러스터
한국어 (질문 / 키워드 / 2단어) ±0.000 +0.018 / +0.034 / +0.020
일본어 (질문 / 키워드 / 2단어) −0.012 / −0.008 / −0.008 (질문형만 소폭 유의, 나머지는 유의하지 않음) +0.001 / 0.000 / 0.000
영어 (질문 / 키워드 / 2단어) 차이 없음 차이 없음
  • BM25 단독으로 보면 JP 클러스터 한국어 쿼리 차이가 더 크다(+0.10 ~ +0.16). 하이브리드에서는 dense가 일부를 메워서 차이가 줄어든다.
  • 비대칭이 생기는 이유와 JP를 ko-dic으로 통일하지 않는 이유(일본어 쿼리 −0.006~−0.019)는 위 “왜 KR은 multi analyzer를 안 쓰고 JP만 쓰나”에 정리했다.
  • 언어 지정 규칙과 그 이유는 위 “언어 지정 규칙 (JP)”에 정리했다.
  • 일본어+영어 혼합은 직접 측정했다(JP, 쿼리 119개). ja 하나로 처리한 결과가 ipadic 단일과 같았다.

결과 2. fan-out은 효과가 없다

  • 한자만 있는 키워드 쿼리를 ja/zh로 fan-out해서 RRF로 합치면 −0.02 ~ −0.07 떨어졌다(하이브리드). 점수 기준(max) 병합은 C1과 거의 같았다(≈0). 손해는 사라지지만 이득도 없다.
  • 일본어+영어 쿼리(JP, 119개)를 ja/en으로 fan-out하면 RRF는 −0.063, max 병합은 +0.002로 사실상 차이가 없었다.
  • 원인은 위 “fan-out은 왜 효과가 없나”에 정리했다.

결과 3. RRF보다 가중합

nDCG@10 (C1 기준. KR은 C0(ko-dic 단일)와 하이브리드 차이가 ±0.004 이내다)

방식 KR JP
BM25만 0.493 0.424
dense(bge-m3)만 0.701 0.703
RRF (k=60) 0.661 0.619
RRF (k=20) 0.673 0.640
Milvus WeightedRanker (dense 0.7, norm_score=True) 0.704 0.683
min-max 정규화 후 가중합 (dense 0.7) 0.725 0.718
  • RRF는 dense만 쓸 때보다 나빴다. 두 리스트를 같은 비중으로 섞는데, 이 데이터에서는 BM25가 dense보다 훨씬 약하다.
  • Milvus WeightedRanker의 정규화가 BM25 점수를 뭉갠다. norm_score=True이면 BM25 점수를 2·atan(s)/π로 정규화한다(v2.6). 그러면 BM25 10점은 0.94, 20점은 0.97이 되어 점수 차이가 거의 사라진다.
  • 하이브리드가 dense만 쓸 때보다 의미 있게 나았던 건 앱에서 직접 min-max 정규화한 가중합뿐이었다(KR +0.024, JP +0.015). WeightedRanker는 KR +0.003, JP −0.020으로 사실상 이득이 없었다.
  • 가중치 0.7은 이 데이터 기준이다. MIRACL은 위키 질문이라 dense에 유리하다. 앱 이름처럼 고유명사가 많은 검색에서는 BM25 비중을 더 높여야 할 수도 있으니, 실제 검색 로그로 다시 튜닝해야 한다.

결과 4. 리랭커 (bge-reranker-v2-m3)

  • C1(multi analyzer) + min-max 가중합 구성의 상위 20개를 cross-encoder 리랭커로 다시 정렬했다. KR의 권장 구성(ko-dic 단일, C0)은 리랭킹 전 점수가 0.729로 C1(0.725)과 거의 같아서, 결과도 사실상 같을 것으로 본다.
쿼리 KR JP
전체 0.725 → 0.781 (+0.056) 0.718 → 0.790 (+0.072)
일본어 키워드 / 2단어 +0.082 / +0.072 +0.105 / +0.104
한국어 키워드 / 2단어 +0.042 / +0.085 +0.039 / +0.064
영어 키워드 / 2단어 +0.036 / +0.046 +0.039 / +0.037
질문형 (ko / ja / en) +0.010(유의하지 않음) / +0.066 / +0.039 +0.018 / +0.056 / +0.036
  • KR 한국어 질문형(+0.010, 유의하지 않음)을 빼면 모든 구간에서 의미 있게 올랐고, 키워드·짧은 쿼리에서 특히 컸다. 키워드 검색이 주력이라면 analyzer 설정을 바꾸는 것보다 효과가 훨씬 크다.
  • 비용: Mac(M4 Pro, MPS)에서 상위 20개 재정렬에 쿼리당 약 0.5초가 걸렸다(여러 쿼리를 묶어 처리한 평균). 운영 GPU에서는 훨씬 빠르겠지만, 지연시간과 GPU 비용은 따로 재 봐야 한다.

덤: analyzer 설정에서 챙길 것

  • 위 “정규화 설정”의 필터를 모두 넣은 분석기 예시다. MIRACL에서는 NFKC + 필터의 효과가 +0.001 ~ +0.003으로 미미했지만 손해도 없었다(위키피디아에는 전각 영문이 드물다). 앱 데이터에서는 효과가 더 클 수 있다.
{
  "tokenizer": {"type": "lindera", "dict_kind": "ipadic"},
  "filter": ["lowercase", "removepunct", "asciifolding",
             {"type": "stemmer", "language": "english"}]
}
  • 컬렉션에 데이터를 넣자마자 벤치마크를 돌리면 인덱스가 아직 다 만들어지지 않은 상태에서 검색된다. 실제로 처음 잰 수치가 그래서 틀어졌다. describe_index의 indexed_rows가 전체 행 수와 같아질 때까지 기다린 뒤 측정하자.

한계

  • bge-m3(그리고 bge-reranker-v2-m3도 가능성이 높다)는 학습 데이터에 MIRACL train 셋이 들어 있다. 평가에 쓴 dev 쿼리와는 다르지만 같은 출처라서 dense 점수와 리랭커 개선 폭(+0.056 / +0.072)이 실제보다 좋게 나왔을 수 있다. 그래서 “dense가 BM25보다 훨씬 강하다”는 결과와 가중치 0.7은 실제 데이터에서 BM25 쪽으로 바뀔 수 있다.
  • MIRACL은 위키피디아 데이터다. 키워드 쿼리도 정규식으로 만든 합성 데이터라서 실제 앱 이름·설명 데이터와는 분포가 다르다.
  • 한국어+일본어, 한국어+영어가 섞인 쿼리는 데이터셋에 거의 없어서 직접 측정하지 못했다.
  • 중국어 문서가 의미 있게 들어오는 환경이라면 ja/zh fan-out 결론이 달라질 수 있다.
  • 결론을 확정하려면 실제 앱 카탈로그와 검색 로그 일부로 같은 비교를 돌려 보는 것이 가장 확실하다.

댓글남기기