AWS Opensearch(ES 호환) 한글 뽀개기(초성 추출)

임용근
Spoon Radio
Published in
12 min readNov 9, 2023
본 포스트에서는 ElasticSearch와 OpenSearch간의 차이점과는 무관한 내용에 대해 설명하므로, 
이하 OpenSearch와 ElasticSearch는 동일한 서비스의 의미로 사용합니다.

안녕하세요.

스푼라디오 Business Platform Team의 임용근(Whale)입니다.

몇개월전, 스푼라디오 사용자검색 개선과 관련해서 블로그를 올린적이 있는데요.

사용자 개선 당시, 검색 키워드를 생성하기 위해 Content Synchronizer 라는 Application에 Data 동기화에 앞서 키워드 전처리 작업을 진행하고 있습니다.

여전히 안정적으로 운영을 하고 있고, 서비스에 큰 문제점은 없습니다.

다만, 이벤트로 인한 피크시간대에는 CPU가 급격히 오르기도 하고, 분산 된 키워드 분석처리로 인해 Opensearch 설정만으로 데이타의 흐름을 바로 이해하기가 쉽지않다라는 단점이 있으며, 팀동료들의 의견도 마찬가지 였습니다.
또한, 비슷한 조건의 다른 프로젝트를 진행하게된다면, CS는 항상 따라다니게 될 것이니 비효율적일 수도 있겠죠.

그럼에도 불구하고, Opensearch 자체에서 원하는 문자처리가 쉽지 않았기에, 차선책으로 CS에 전처리작업을 녹였던 것입니다.

그나마 설치형 서비스인 Elasticsearch라면 plguin을 만들어서 처리할 수 있는데, AWS Opensearch에서는 plugin 설치가 불가능하며, 제공되는 plugin만으로 활용을 해야하는 제약이 있습니다. (까다롭네요 ^^;)

AWS opensearch plugIns

이번 포스트에서는 CS에 의존하지 않고 AWS Opensearch 에 기본 설치 된 plugin을 이용하여, 한글의 초성을 추출하는 방법에 대해 설명하고자 합니다.

(Elasticsearch는 손쉽게 plugin 설치가 가능하므로 원하는 기능을 사용하는데 큰 문제는 없을것입니다.)

시작

저의 Google 검색실력이 서투른 탓 일까요.. 한글 초성추출과 관련한 내용을 구글에서 검색하면, Custom Plugin 개발 및 설치 위주의 가이드는 많이 보이지만, Opensearch에서 직접 처리 하는 방법에 대한 내용은 찾기가 어려웠습니다.

비슷한 고민을 하시는분도 볼 수 있습니다.

개발자들에게 큰 빛이 되어 나타나주신 ChatGPT 에게도 물어보았습니다.

Elasticsearch에서 추출하는 방법 질문
Opensearch에서 추출하는 방법 질문

질문을 반말로 해서 그런가 아니면 질문내용이 좋지 않은것인지.. 유료인데 답변이 ‘Plugin 개발’ 혹은 ‘전처리 작업’을 하라고 하네요.

얘도 잘 모르는 것 같습니다.

얼핏 살펴본 바로는 ICU Analysis로 뭔가 될것같은데요.. 이것이 나의 간지러운 부분을 긁어줄것같은 느낌적인 느낌으로 파고 들어봤습니다.

ICU Analysis란?

출처 : https://www.elastic.co/guide/en/elasticsearch/plugins/current/analysis-icu.html#analysis-icu-install

대충, 아시아권 언어에 대한 정규화처리를 해준다.. 뭐 그런내용 같네요. (NFC / NFKC 등)

NFC / NFD / NFKC 정준분해란?
NFC는 단어를 정준분해 후 정준결합하고, NFD는 단어 자체를 정준분해(자음,모음 분리)를 한 상태를 나타냅니다.

관련해서 눈에 띄는 내용들이 있어서 공유드립니다.

https://wodonggun.github.io/study/%ED%95%9C%EA%B8%80-%EC%9D%B8%EC%BD%94%EB%94%A9%EC%9D%B4%EB%9E%80.html
https://www.clien.net/service/board/park/17595712

대략적인 내용을 파악해봤으니, 원하는 결과를 만들어봅시다.

제가 원하는 문자처리방법은 아래와 같이 3가지 입니다. (사용자 개선편에서 언급했던 내용과 같습니다.)

  1. 한글 → 초성 추출 (예 : 스푼 → ㅅㅍ) ( 엥? 초성이 좀; )
  2. 한글 → 영문 오타로 변환 (예 : 스푼 → tmvns)
  3. 특수문자/아스키문자 구분없이 검색 가능

위 순서대로 하나씩 진행 해보겠습니다!!! 💪

한글에서 초성 추출하기

우리는 한글을 자소단위로 ‘분해’를 할것이니, NFD (NFC or NFKC + DeComposition)를 이용할것입니다.

우선 간단하게 한글부터 쪼개볼까요~

간단하죠?

정상적으로 자음 / 모음이 분리된것을 볼 수있습니다.

그럼, 한글의 특징을 분석하여 초성만 추출하는 방법에 대해 생각해보자면 아래와 같을 것 같습니다.

  1. 한글은 초성 + 중성 + 종성(Optional) 으로 구성된다.
  2. 초성은 중성 앞의 문자만 추출하고, 나머지 한글은 모두 제거한다.

🌾 자음 / 모음을 자소로 표기(이미 분해되어있는)하는 문자들에 대해서는 초성추출 대상으로 하지 않습니다. (아래 ‘응용하기’ 편에서 다루겠습니다.)

동의 하시나요? 🙂

위 흐름대로 진행하기에 앞서, 우선 모음의 모양이 다양하므로, mapping filter를 이용하여 한개의 동일한 문자로 치환하도록 하겠습니다.
같은 문자로 치환하는 이유는 정규식을 이용하여 조금 더 편리하게 초성 이외 문자를 모두 제거할 목적입니다.

예제에서는 모음을 ‘+’ 문자로 치환 해보겠습니다.

결과가 왜이러냐 😱

테스트 결과가 충격적이네요. 예상했던 “ㅅ+ㅍ+ㄴㄹ+ㄷ+ㅇ+”가 나올 줄 알았는데 치환이 되지 않았습니다.

무엇이 문제인건가.. 한참 고민하던 중, NFD 처리 된 단어는 고유한 값을 가진다 라는 사실을 알게 되었으며, 분리된 단어를 찾아보았습니다.

What the..

값들이 전혀 다르게 나오는 것을 확인 할 수 있습니다.

심지어 같은 문자인 ‘푼’라는 단어 조차도 값이 다릅니다. 어떤 속성값을 가지고 있는지 확인 해보겠습니다.

mapping filter 설정에 직접입력 한 모음의 값이 다르다 보니, 치환이 되지 않은 것 같습니다.

icu normalizer로 추출한 자소(모음)의 UTF16 값을 기반으로 치환 테스트를 다시 진행해보았습니다.

Good~👍 잘 되네요 😗

지금부터는 ‘+’ 앞의 글자(초성)만을 놔두고 모두 지울수 있는 정규식만 만들면 되는데..

앗, 잠깐만!!!

‘초성’을 code로 분류할 수 있다면, 치환 된 모음을 신경쓸 것 없이 초성만 특정하고, 나머지는 모두 삭제하면 되지 않을까요?? (응?응?? 🤔)

그래서 한번 해봤습니다. 요렇게~

초성 추출 성공입니다!
하는김에 중성과 종성만 제거하고 다른 문자들은 살려볼까요.

의외로 설정이 간단해서 허무하네요. 🙂

이 기능 때문에, AWS Opensearch에 Plugin 설치하는 방법을 찾아 해맨 나날들이 문뜩 생각납니다 ㅎㅎ;;

영문자(한글 오타)로 변경

자소분리를 통해, 초성추출까지 해보았습니다.

여기서 응용을 해보면 영문자 오타까지 구현을 할 수 있을것같습니다.

예를들면 ‘스뿐라디오’ → ‘tmQnsfkeldh’ 이렇게요~
(쌍자음 변환결과도 보고싶어, ‘스뿐’ 이라고 테스트를 하였습니다. 귀엽게 보이려고 한거 아님.. 😁 )

이럴경우에는 정규식 사용이 어려울 것 같아, mapping filter로 처리해야 할 것 같습니다.

mappings가 길어, folding 하여 보이지 않으나 내용은 ‘+’로 치환한것처럼 설정

예상한대로 잘 동작합니다.

중요!
char_filter / filter 는 여러가지 filter들이 조합되는 기능이니만큼, 순서도 중요합니다.
filter는 설정된 순서대로 실행됩니다.

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

이제 요구조건에 충족하기 위한 마지막 관문입니다.

사실 이 기능은 CS에서 이미 ICU 라이브러리를 통해 개발한 기능과 같습니다. Kotlin Code → Opensearch Analyzer로 바꾼것 뿐이죠.

설정부터 보도록 하겠습니다.

‘𝒅𝒂𝒓𝒐’ 라는 스푼라디오 DJ분의 nickname을 잠시 빌려왔습니다.

해당 nickname은 얼핏 봐도 키보드로 입력한 ‘daro’와는 모양이 많이 다르네요. 그래서 찾아봤습니다.

응? MATHEMATICAL 이건 또 머냐 😅

_analyze 설정에서 조금 다른점 발견하신분은 눈썰미가 좋으신분입니다!

지금까지 테스트 할때에는 ‘NFC+DeCompose’ 로 진행했으나, 이번에는 ‘NFKC+Compose’입니다.

NFC, NFD, NFKC에 대해 읽어보셨으면 이해 하셨겠지만, 아주 짧은지식으로 간단하게 요약하자면,

  • NFC : 문자를 분리(정준분해) 후 합친다(정준결합)
  • NFD : 문자를 분리한다(정준분해)
  • NFKC : 문자를 분리(호환분해) 후 합친다(정준결합) (with Unicode)

참고 : 유니코드 등가성

그렇다면 해당 nickname을 NFC(정준분해 → 정준결합)을 하게 되면 어떻게 될까요?

띠로리~🫥

예상대로 분해했을때 해당하는 단어의 호환성을 찾지못해 그냥 사라졌네요. (nfc + decompose 도 마찬가지입니다.)

설정에 대해 잠깐 짚어보고 갈까요.

  1. 분석 대상이 되는 키워드
  2. ‘ ‘(space)를 ‘’(empty)로 치환
    (‘스 푼 라 디 오’ 라는 Nickname이 있을때 ‘ ‘(space)가 존재하므로 이를 제거하여, 단어들 사이의 공백을 제거합니다.)
  3. 대상 키워드를 nfkc 처리를 합니다. ‘daro’로 변환
  4. 위 설정에는 언급된 단어는 아니지만, ‘ᵴ’ 이렇게 생긴 문자들을 ascii 문자로 치환합니다.
  5. 해당 pattern 이외의 문자들은 모두 제거합니다.
    (깜짝퀴즈!!! 만약 대상키워드 “”이고, asciifolding filter가 없을경우 결과는 무엇이 될까요? 😏 )
  6. 모든 영문자는 소문자로 변경합니다.
    (이는 검색시, 대소문자를 구분하지 않기 위함입니다.)

기타, 그밖에 필요에 따라 Filter는 추가하면 됩니다.

정리를 해봅시다.

사용자 검색의 필드값은 모두 CS에서 결정되고 있습니다.

앞으로 수정 될 Mapping 설정에서는 CS에서 데이타를 조작하지 않고, Opensearch에서 하도록 구현을 하는것이 목적이니, 같은 값이 나오도록 Analyzer 설정을 해야 할 것입니다.

CS의 Extractor, 키워드 전처리 작업

Test로 사용할 사용자 nickname은 아래와 같습니다.

위 3개의 Nickname 이외에도, ᴹᵒᵒⁿ , ᒍᗩᑕK❄ᖴᖇOᔕT , 𝑺𝒔𝒆𝒓𝒊🎧¿♡¿ , 𝓮𝓴 , Tǟʟʟʊʟǟɦ ꪜ … 등 30여개의 nickname을 테스트 해본 결과 CS와 Opensearch간의 결과값이 일치함을 확인할 수 있었습니다.

응용하기’입니다.

테스트는 ‘ㅅ ㅑ린'으로 했지만 ‘ㅅㅡㅍㅡㄴ’으로 진행하면 ‘스푼'이라는 단어로 결합하게 됩니다.

결과확인하기

모든 설정을 완료 했으니, 검색 테스트를 진행해보겠습니다.

그 외 사용자 검색도 잘 나옵니다.

이제 서비스에 활용할 수 있도록 analysis / mapping 설정을 깔끔하게 정리 해야겠네요~

마치며

  • 위 내용들이 정답이라고 할 수는 없습니다. 아마도 알게 모르게 수많은 현장에서 더욱 좋은 방법으로 서비스를 만들어가고 있지 않을까 생각합니다.
    사내 보안상, 내부 코드를 보여줄수 없어 Sample로 작성된 코드로 부족함이 없지 않을까 걱정됩니다.
  • 우연히 icu analysis에 대해 읽어보다가, ‘초성 추출 가능하겠는데?’ 라는 생각으로 시작한 업무이고, 결과는 아주 만족스럽습니다. 아직은 CS에 의존하고 있지만, 사용자 검색이 여러 파트에서 사용하고 있기에, 하나씩 반영해보고자 합니다. (RANKING 사용자 검색 / 팬 사용자 검색 등, 활용범위가 다양합니다.)
  • 사내에서 굉장히 흥미로운 제도가 생겼습니다.
    기술블로그 한편당 Team 비용 10만원을 지원 받을 수 있는데요, 2023년말 팀 동료들과 배부른 연말 보낼 수 있도록, 이 포스트가 통과되길 기대해봅니다!!
    (결코 돈에 눈이 멀어 이 글을 쓰는것은 아닙니다. 겸사겸사. 🤣 )
  • 일본어 처리도 재미있을 것 같으니, 기대하셔도 좋을것같습니다.

--

--