[일본] 스푼라디오 사용자 검색개선

임용근
Spoon Radio
Published in
17 min readJul 5, 2023

안녕하세요.

스푼라디오 Business Platform Team에서 검색과 빌링업무를 담당하고 있는 Whale(임용근)입니다.
(한국편 작성할때에는 Discovery Team이었는데, 그 사이에 팀명이 바뀌었습니다~!)

1부에서 스푼라디오 한국 서비스 사용자 검색에 대한 개선점에 대해 포스팅 하였습니다. ( [한국] 스푼라디오 사용자 검색개선 )

사내에서도 따봉 몇개 받으니 기분좋네요~ 🙏

이번에는 스푼라디오 일본서비스의 사용자 개선에 대해 포스팅하고자 합니다.

한국어도 난관 이었지만, 일본어는 더 많은 고난이 예상 됩니다

한국과 일본은 가깝지만 참 먼 나라인것 같습니다.

지난 포스트에서 “진단하기” 에서도 설명 드렸지만, Nickname은 본인의 개성이자 스푼라디오의 문화로 자리 잡았습니다.

일본도 마찬가지더라구요.

라떼는 세이클럽에서 고작 ( :’’으앙’’: ) (^ — — — — — —-^) ( (((_(_(_( O.o ~* 리버 ) 등등.. 이런것만 할줄 알았는데.. 격세지감입니다.

다만, 다른 점은 컨텐츠의 종류와 프로필사진입니다. 검색개선과는 무관하지만 살짝 비교해볼까요~? 😀

한국 : 실사에 가까운 프로필 / 일본 : 주로 캐릭터

어떠신가요? 차이점이 조금은 보이시나요? 올해(2023년) 대만에도 서비스 오픈을 했는데, 어떤 양상이 될지 궁금합니다.

그럼 일본어는 어떻게 개선했는지 알아보겠습니다.

살짝 덧붙이자면 한국어는 순한맛이라면, 일본어는 매운맛입니다. 언어처리가 복잡하네요. 🤣

서비스환경과 Mapping / Query는 한국과 다르지 않습니다.

MAPPING

... 생략 ..
"properties": {
"follower_count": {
"type": "long"
},
... 중간 생략 ...
"tag": {
"type": "text",
"fields": {
"keyword": { "type": "keyword", "ignore_above": 256 }
},
"analyzer": "search_analyzer"
},
"username": {
"type": "text",
"fields": {
"keyword": { "type": "keyword", "ignore_above": 256 }
}
}
}
... 중간 생략 ...
"settings": {
"index": {
"analysis": {
"analyzer": {
"search_analyzer": {
"filter": [
"lowercase"
],
"type": "custom",
"tokenizer": "whitespace"
}
}
}
... 생략 끝.

검색QUERY

{
"_source": ["필드들"],
"query": {
... 필터 ...
"query_string": {
"fields": ["tag", "nickname"],
"query": keywordArray.push(`*${k}*`).join(' AND ')
}
},
"sort": [
{"is_live": {"order": "desc"}},
{"follower_count": {"order": "desc"}},
{"nickname": {"order": "asc"}}
],
"from": 0,
"size": 50
}

일본도 마찬가지로 검색대상 필드에 대한 분석처리wildcard 검색을 지양하는 방향으로 개선합니다.

사용자 검색에 개선해야할 항목

  1. 특수문자 / 아스키문자 구분없이 검색이 되어야 한다.
  2. 한자 / 히라가나 / 카타카나 / 로마자 구분없이 검색되어야 한다.
  3. 닉네임 전체검색이 되어야 함
  4. 태그 검색 시 1건만 나오도록 해야함
  5. 검색속도 올리기
※ 한국에서는 알파벳을 영문자라 표현하지만, 일본에서는 로마자라고 표현합니다. 
본문에서 다르게 표현되더라도 같은 의미로 받아들이시면 좋을 것같습니다. 🙏

한국과 다른부분은 두번째 항목입니다. 😩

가장 고민이 많았고, 어떻게 시작해야할지 막막했던 부분이기도 합니다.

일본어를 접해보신분이라면 잘 아실거라 생각합니다.

일본어는 문자 표기 방법이 4가지가 있습니다.
(정확히는 키보드로 입력시 나올 수 있는 가능성이 4가지입니다. )

예를들면 大学校(대학교)라는 단어가 있다고 가정할때, 이 단어는 4가지로 표기를 할 수 있습니다.

위와 같이, Nickname 중간에 大学校 라는 단어가 있고, 발음대로 로마자로 검색을 했을때 정상적으로 출력 되는것을 볼 수 있습니다.

1,2,3번 키워드도 마찬가지로 동작합니다. 😁

위 예제는 한자를 음독으로 발음할때 단어인데, 한자의 위치나 문맥에 따라 음독 또는 훈독으로 나뉘게 됩니다.
즉, 모든 읽는방법을 커버하기 위해서는 색인어 대상의 필드를 준비해야합니다.

예를들어 病(병)이라는 문자로, 음독과 훈독의 차이점을 알아보겠습니다.

또한 “生”과 같이, 읽는 방법의 수가 너무 많아 모두 처리하기는 사실상 불가능에 가까운 단어들도 있습니다.

그럼 일본어 처리는 어떻게 하는지 필드별로 구분해보겠습니다.

설정에 변화주기

※ 검색 Query 는 한국과 흡사하기 때문에 생략하겠습니다.

SETTING

"index": {
"number_of_shards": "XX",
"number_of_replicas": "XX",
"max_ngram_diff": 1000,
"analysis": {
"char_filter": {
"normalize": {
"type": "icu_normalizer",
"name": "nfkc",
"mode": "compose"
}
},
"analyzer": {
"replace_ngram_keyword_analyzer": {
"type": "custom",
"tokenizer": "keyword",
"filter": [
"lowercase",
"replace_SpaceToBlank",
"full_autocomplete_filter"
]
},
"clean_analyzer": {
"type": "custom",
"tokenizer": "keyword",
"filter": [
"lowercase",
"full_autocomplete_filter",
"unique"
]
},
"first_analyzer": {
"type": "custom",
"tokenizer": "keyword",
"filter": [
"lowercase",
"full_autocomplete_filter",
"unique"
]
},
"second_analyzer": {
"type": "custom",
"tokenizer": "kuromoji_tokenizer_normal",
"filter": [
"lowercase",
"autocomplete_filter",
"unique"
]
}
},
"tokenizer": {
"kuromoji_tokenizer_normal": {
"mode": "normal",
"type": "kuromoji_tokenizer"
}
},
"filter": {
"replace_SpaceToBlank": {
"type": "pattern_replace",
"pattern": " ",
"replacement": ""
},
"kuromoji_stemmer": {
"type": "kuromoji_stemmer",
"minimum_length": 2
},
"replace_filter": {
"type": "pattern_replace",
"pattern": "[^0-9a-zA-Z가-힣ㄱ-ㅎㅏ-ㅣぁ-ゔァ-ヴア-ヴー々〆〤一-龥]",
"replacement": ""
},
"autocomplete_filter": {
"type": "edge_ngram",
"min_gram": 1,
"max_gram": 1000
},
"full_autocomplete_filter": {
"type": "ngram",
"min_gram": 1,
"max_gram": 1000
}
}
}
}

MAPPING

"mappings": {
"user": {
"properties": {
... 생략 ...
"nickname": {
"type": "text",
"analyzer": "replace_ngram_keyword_analyzer"
},
"clean_nickname": {
"type": "text",
"analyzer": "clean_analyzer"
},
"kana": {
"type": "text",
"analyzer": "first_analyzer"
},
"hira": {
"type": "text",
"analyzer": "first_analyzer"
},
"romaji": {
"type": "text",
"analyzer": "first_analyzer"
},
"kana_reading": {
"type": "text",
"analyzer": "second_analyzer"
},
"hira_reading": {
"type": "text",
"analyzer": "second_analyzer"
},
"romaji_reading": {
"type": "text",
"analyzer": "second_analyzer"
},
... 생략 ...
}
}

위에서 언급했듯이, 문자변형에 따른 필드들이 추가됨에 따라 검색 대상필드도 많아지게 되었습니다.

keyword 추출해보기

이번에도 CONTENT-SYNCHRONIZER(이하 CS)에 포함된 라이브러리Extractor를 이용하여 keyword를 추출하여 키워드 확인을 해보겠습니다.

일본어 추출

CS 코드 발췌

테스트결과

{
"nickname" : "東❤️京都", --원본
"clean_nickname" : "東京都",
"kata" : "ヒガシキョウト",
"hira" : "ひがしきょうと",
"romaji" : "higashikyouto",
"kata_reading" : "トウキョウト",
"hira_reading" : "とうきょうと",
"romaji_reading" : "toukyouto"
}

※ 왜 필드가 한쌍씩 있는가?

위에서 언급했듯이, 일본어는 상황에 따라 읽는방법이 달라집니다. 예제로 사용된 단어는 가운데 특수문자로 인해 “ひがしきょうと”라고 변환이 됩니다. 하지만 사용자는 “とうきょうと”라고 읽을 수도 있겠죠.
❤️ 라는 문자가 가운데 포함됨으로서 “ひがしきょうと”로 읽혔지만, ❤️를 제거 한 후 두 문자를 붙이게 하여, 읽는 방법을 다르게 표현할 필요가 있습니다.

즉, ****_reading 이라는 필드는 nickname 필드의 단어를 concatenate 하여 표현해준 필드의 값입니다.

하나의 단어가 원하는 단어로 변환되지 않고, 패턴이 있습니다.

행 -> 열로 대입

한자 → 카타카나

  • Kuromoji Plugin 을 통해 추출할 수 있습니다.
  • 무슨 이유인지 한자 히라가나는 추출되지 않네요.

그외 ICU Plugin을 통해 변환합니다.

로마자 → 히라가나, 카타카나

  • 품질이 좋지 않아 추천하지 않습니다.
siro → せぃろ, セィロ
shiro → しろ, シロ

위 패턴을 사용하여 “東京都”의 변환과정은 아래와 같습니다.

東京都 → トウキョウト → とうきょうと → toukyouto

또한 키워드가 히라가나 / 카타카나 일경우 한자를 제외한 다른 문자로 변환 가능합니다!

くじら → クジラ → kujira
クジラ → くじら → kujira

TMI ✏️

언어처리를 CS에서만 처리할 수 있는것은 아닙니다.

본 포스트를 작성하는 과정에서도 “굳이 Filter / Analyzer에 설정할 필요가 있을까?” 해서 제거 된 설정들도 있습니다.

검색엔진은 보이지 않는 여러가지 Job으로 바쁠텐데, 색인작업비용 0.01초라도 덜어주고자 CS에서 처리하도록 한 것입니다.
(실제로 비슷한 건수로 INDEX Migration 과정에서, 일본이 한국보다 색인속도가 약 2배정도 느린것이 확인되었습니다. )

만약 Mapping 설정을 제거하지 않고 Keyword “東京都”가 입력 되었다면, 어떤 설정들이 필요했을까요?

제거된 항목 Mapping 설정

... 등등 ...

"hira-to-latin-transform": {
"type": "icu_transform",
"id": "Hiragana-Latin"
},
"hira-to-kata-transform": {
"type": "icu_transform",
"id": "Hiragana-Katakana"
},
"kata-to-latin-transform": {
"type": "icu_transform",
"id": "Katakana-Latin"
},
"kata-to-hira-transform": {
"type": "icu_transform",
"id": "Katakana-Hiragana"
}

... 등등 ...

"jp_shingle": {
"type": "shingle",
"min_shingle_size": 2,
"max_shingle_size": 100,
"output_unigrams": false,
"output_unigrams_if_no_shingles": true,
"token_separator": "",
"filler_token": ""
},
"katakana_readingform_filter": {
"type": "kuromoji_readingform",
"use_romaji": "false"
},
"romaji_readingform_filter": {
"type": "kuromoji_readingform",
"use_romaji": "true"
},
"hiragana_readingform_filter": {
"type": "icu_transform",
"id": "Katakana-Hiragana"
}

... 등등 ...

일부분만 발췌했습니다.

이제, 설정을 뚝딱뚝딱 고쳐봤으니, 원하는 결과를 얻을 수 있는지 확인해보겠습니다.

결과보기

한자

鯨 (고래) 키워드 검색

히라가나 (히라가나 → 한자)

れんあい : 恋愛 (연애) 키워드 검색

카타카나 (카타카나 → 한자)

ヤマイ : 病 (병) 키워드 검색

로마자 (로마자 → 한자, 히라가나, 카타카나)

sora : 空, ソラ (하늘) 키워드 검색

특수문자 / 아스키문자 구분없이 검색

fleur 키워드 검색

검색속도 올리기

a 키워드 검색

닉네임 전체검색

“🚃マキナ𝓟.✎💄 ⋆♔ᴿ༞🥀” 키워드 검색

최종결과

한국과 비슷한 방식으로 테스트를 진행했습니다.
특별히 이상한 부분없이(아직까지는?) 원하는 형태로 잘 나오는것을 확인할 수 있습니다.
직접 해보기 ( https://www.spooncast.net/jp )

개선고민

  1. 사용자 입력 검색 키워드도 구분없이 검색이 가능할까?

한자로 검색 할경우, 입력키워드를 “空 → そら” 등으로 변환하지 않고 “空”라는 단어로만 검색을 진행하고 있습니다.
즉, nickname 이 한자가 아닌 , “そら” 라는 이름으로 되어있을 경우, “空”로 검색을 하면, Hit 되지 않습니다.

색인어 생성시 “空 → そら,ソラ,sora” 등으로 변환하여, 검색을 좀더 유연하게 했는데, 사용자 입력 키워드를 변환하지 않는 이유는, 검색결과가 무분별하게 많아지기 때문입니다.

앗, 그러면 nickname을 “そら → 空 ”로 변환하면 되지 않을까?
아쉽게도 가나에서 한자로 변환할 수 있는 Tool은 없는듯 합니다.
(있다면 댓글로 추천 부탁드립니다~)
이해가 가는것이, “あ”만 하더라도 변환을 시도하면 엄청 나오거든요~

이게 끝이 아님

만약, 그러한 Tool이 있다 하더라도, 문장에서 사용되는 히라가나가 어처구니 없게 원하지 않은 한자로 변환된다면, 분명 검색결과에도 문제가 생길것같습니다.

2. Nickname에 사용되는 언어가 국가에 의존적이지 않은 현상

한국서비스에도 nickname에 한글이외 외국어를 포함하는 경우가 많습니다.

한국서비스에서 일본어로 된 nickname,
일본서비스에서 한글로 된 nickname…

현재는 영어 이외 외국어를 모두 특수문자로 취급하고 있습니다.
각 국가의 언어로 처리하기 위해서는 nickname 패턴과 사용되는 언어를 모두 파악해야 할 것같고, 무엇보다 언어체계를 이해하기가 어렵다는것입니다. (구글은.. 이런걸 어떻게 했을까요 ㅎㅎ)

현재 스푼라디오 서비스에서는 nickname에 국한 된 현상이지만, 엔지니어로서 한번쯤 고민해볼만한 주제라고 생각합니다.

마치며

한국과 일본의 사용자 검색 서비스 개선에 대해 포스트를 하였습니다.

스푼라디오는 한국/일본 이외에도 대만, 미국, 아랍에 서비스를 하고 있지만, 미국과 아랍 국가는, 결과에 크게 변동사항이 없어 포스팅은 생략하겠습니다. (Mapping 설정만 변경하였음)

다만, 대만의 경우 한자를 사용하는 국가이니만큼 한자(번체)/병음 처리등 일본어 못지 않게 변환작업을 진행합니다.
(대만은 BPT 동료분께서 열심히 진행해주고 계시니, 포스팅은 조만간 따로 기대해보면 좋을것같습니다~)

두 편에 걸쳐 사용자 검색 개선에 대해, 비기술적인 접근으로 어렵지 않게 작성해보았습니다.

기회가 된다면 AWS Elasticsearch (Opensearch) 를 어떻게 사용했고, 어떤 어려움이 있었고, 어떻게 해결해 나갔는지에 대해 포스트 작성을 해보고 싶습니다. (많은 분들이 함께 해주셔서 할말이 참 많습니다. ㅎ)

또한, 스푼라디오 서비스 중, CAST / LIVE / TALK 등 개선해야할 검색서비스가 많이 남았습니다.

만족하는 수준까지 지지고 볶고 하고싶네요~ :)

긴 글 읽어주셔서 감사합니다.

--

--