Skip to content

Conversation

ezcolin2
Copy link
Collaborator

@ezcolin2 ezcolin2 commented Feb 18, 2025

🔖 연관된 이슈

📂 작업 내용

  • 문서 내용 뿐만 아니라 제목도 함께 검색
  • 한국어 형태소 분석기 mecab 설치 및 적용
  • websearch_to_tsquery의 '&' 연산자 모두 '|'로 변경

mecab 설치

postgres에서 검색 기본 설정은 영어입니다.

형태소 분석기도 영어를 기준으로 동작하기 때문에 한국어 형태소를 잘 분석하지 못 하는 현상이 발생했습니다.

다음은 기본 영어 형태소 분석기를 사용했을 때 tsvector입니다.

 '/questions/13672743/eventsource-server-sent-events-through-nginx':128 'ai':70 'ai에게':26,32 'chunk는':93 'divid':13 'event':133 'gpt':110 'histori':39 'history는':49 'id':63 'nginx':135 'node':62 'post':87 'query를':82 'rce':130 'rce-server-sent-events-through-nginx':129 'respons':105 'sent':132 'server':131 'sse':77 'stackoverflow.com':127 'stackoverflow.com/questions/13672743/eventsource-server-sent-events-through-nginx':126 '가기':53 '값':64 '개선':4,6,44 '검색':17,55,72 '검색이랑':21 '검색한다':57 '결정':91 '계획':2 '고민':58 '고정하면':37 '광범위하다':31 '구글':75 '구분이':22 '구현하기':78 '구현하기로':90 '근데':92 '글을':121 '기능':35,79 '나눠서':113 '내용':66 '
너무':30 '네이버':76 '네트워크를':103 '는':29 '답변을':111 '답변이':101 '당장':40 '뒤로':52 '때문에':86 '로그인하지':46 '리팩토링':71 '마스터':18 '모든':100 '방식으로':88 '번에':99 '범위':61 '보내야':84 '보면':112 '분리되서':94 '사용자에게':48 '사항':59 '상':81 '상황이다':109 '성능':3 '수':124 '스트리밍':15,89 '시도를':117 '시키는':34 '시킨다':28 '실제':96 '실패했고':119 '아래':120 '않은':47 '어떨까':38 '어떻게':50 '어렵다':23 '엔진':73 '여러':116 '예시':24,74 '오기는':114 '오는':108 '오지만':95 '온다':10
2 '요약을':33 '위치':69 '유지':12 '유지하도록':43 '으로':36 '이전':10 '일반':16,20,54 '있었다':125 '자체가':106 '작업을':27 '적용':14 '전':41 '정확도':5 '제공':60 '제공할
까':51 '제목':65 '주간':1 '질문':11,25 '질문은':42 '참고하여':122 '찾은':67 '컴포넌트':8 '크기':9 '클라이언트에서는':97 '키워드를':56 '키워드의':68 '특성':80 '프론트':7,4
5 '피드백':19 '하기':85 '하나에':107 '하는데':115 '한':98 '함께':83 '해결할':123 '해보았지만':118 '확인해보니':104

조사를 잘 걸러내지 못 하고 있습니다.

그래서 postgres 16버전을 base 이미지로 사용하여 그 안에 mecab 형태소 분석기를 설치하는 Dockerfile을 작성했습니다.

물론 저희 프로젝트는 벡터 기반 유사도 검색과 키워드 기반 검색을 합쳐서 hybrid search를 사용하기 때문에 pgvector extension을 설치하는 코드도 함께 있습니다.

# 베이스 이미지를 postgres:16으로 설정
FROM postgres:16

# 시스템 패키지 업데이트 및 설치
RUN apt update -y && \
    apt install -y \
    wget \
    build-essential \
    postgresql-server-dev-16 \
    automake \
    unzip \
    libmecab-dev

# pgvector 설치
RUN cd /tmp && \
    wget https://github.com/pgvector/pgvector/archive/refs/tags/v0.8.0.tar.gz && \
    tar -xvzf v0.8.0.tar.gz && \
    cd pgvector-0.8.0 && \
    make && \
    make install

# mecab-ko 설치
RUN cd /tmp && \
    wget https://bitbucket.org/eunjeon/mecab-ko/downloads/mecab-0.996-ko-0.9.2.tar.gz && \
    tar xvfz mecab-0.996-ko-0.9.2.tar.gz && \
    cd mecab-0.996-ko-0.9.2 && \
    ./configure CC=gcc CXX=g++ CFLAGS="-m64" CXXFLAGS="-m64" && \
    make && \
    make install

# mecab-ko-dic 설치
RUN cd /tmp && \
    wget https://bitbucket.org/eunjeon/mecab-ko-dic/downloads/mecab-ko-dic-2.1.1-20180720.tar.gz && \
    tar xvfz mecab-ko-dic-2.1.1-20180720.tar.gz && \
    cd mecab-ko-dic-2.1.1-20180720 && \
    ./autogen.sh && \
    ./configure && \
    make && \
    make install

RUN cd /tmp && \
    apt install git -y && \
    git clone https://github.com/i0seph/textsearch_ko.git && \
    cd textsearch_ko && \
    make USE_PGXS=1 && \
    make USE_PGXS=1 install && \
    cp /tmp/textsearch_ko/ts_mecab_ko.sql /docker-entrypoint-initdb.d/

COPY services/postgres/init.sql /docker-entrypoint-initdb.d/z_init.sql

# PostgreSQL 컨테이너 기본 명령어 설정
CMD ["postgres"]

위 dockerfile로 이미지 생성 후 mecab 한국어 형태소 분석기를 적용했을 때 tsvector입니다.

 '/questions/13672743/eventsource-server-sent-events-through-nginx':131 'ai':28,33,71 'chunk':93 'divid':15 'gpt':111 'histori':39,48 'id':64 'node':63 'post':88 'queri':83 'respons':106 'sse':78 'stackoverflow.com':130 'stackoverflow.com/questions/13672743/eventsource-server-sent-events-through-nginx':129 '가':53 '값':65 '개선':6,8,44 '
검색':19,23,55,57,73 '결정':92 '계획':4 '고민':59 '고정':37 '광범위':32 '구글':76 '구분':24 '구현':79,91 '글':123 '기능':36,80 '나누':114 '내용':67 '너무':31 '네이버':77 '네트워크':103 '답변':101,112 '당장':40 '뒤':52 '때문':87 '로그인':46 '리팩토링':72 '마스터':20 '모든':100 '방식':89 '번':99 '범위':62 '보':113 '보내':85 '분리':94 '사용
자':47 '사항':60 '상':82 '상황':110 '성능':5 '수':127 '스트리밍':17,90 '시도':118 '시키':30,35 '실제':96 '실패':120 '아래':122 '어떻':38 '어떻게':49 '어렵':25 '없':2 '엔
진':74 '여러':117 '예시':26,75 '오':95,102,109,115 '요약':34 '위치':70 '유지':14,43 '이전':12 '일반':18,22,54 '있':128 '자체':107 '작업':29 '적용':16 '전':41 '정확':7 '제공':50,61 '제목':1,66 '주간':3 '질문':13,27,42 '참고':124 '찾':68 '컴포넌트':10 '크기':11 '클라이언트':97 '키워드':56,69 '특성':81 '프론트':9,45 '피드백':21 '하':86,116,119 '하나':108 '한':98 '함께':84 '해결':125 '확인':104

조사를 잘 걸러내는 모습을 확인할 수 있습니다.

키워드 기반 검색에 제목 추가

기존에는 키워드 검색할 때 제목 정보를 사용하지 않아서 키워드 기반 검색 성능이 좋지 않았습니다.

제목 정보도 함께 사용하기 위해 title에 대한 tsvector 컬럼을 생성했습니다.

page.entity.ts

  // dodcument 키워드 추출
  @Column({
    generatedType: "STORED",
    type: "tsvector",
    asExpression: `to_tsvector('korean', COALESCE(document, ''))`,
    nullable: true,
  })
  documentFts: string;

  // title 키워드 추출
  @Column({
    generatedType: "STORED",
    type: "tsvector",
    asExpression: `to_tsvector('korean', COALESCE(title, ''))`,
    nullable: true,
  })
  titleFts: string;

그리고 검색을 할 때 title tsvector와 document tsvector를 바탕으로 결과를 가져올 수 있도록 OR 조건으로 묶었습니다.

      where(
        "titleFts" @@ to_tsquery(replace(websearch_to_tsquery('korean', query_text)::text, '&', '|')) OR
        "documentFts" @@ to_tsquery(replace(websearch_to_tsquery('korean', query_text)::text, '&', '|'))
      )

키워드 기반 검색을 해서 결과를 가져온 뒤 순위를 재정렬 해야 하는데 문서 내용보다는 제목에 더욱 중요한 키워드가 담길 것을 고려하여 제목에 높은 가중치를 주었습니다.

tsvector에 A, B, C, D로 가중치를 부여할 수 있습니다. (A가 가장 높음)

title tsvector에는 가장 높은 A를 주었고 docment tsvector에는 가장 낮은 D를 주었습니다.

        row_number() over(order by ts_rank_cd(setweight("titleFts", 'A') || setweight("documentFts", 'D'), to_tsquery(replace(websearch_to_tsquery('korean', query_text)::text, '&', '|'))) desc) as rank_ix

websearch_to_tsquery 수정

기존에는 websearch_to_tsquery로 사용자의 요청을 tsquery로 변환하였습니다.

websearch_to_tsquery를 형태소를 추출한 뒤 모두 &로 묶는 함수입니다.

"모든 회의록을 날짜 별로 요약해줘"와 같은 요청을 tsquery로 변환한 결과는 아래와 같습니다.

SELECT websearch_to_tsquery('korean', '모든 회의록을 날짜 별로 요약해줘');
             websearch_to_tsquery             
----------------------------------------------
 '모든' & '회의록' & '날짜' & '별로' & '요약'
(1 row)

키워드 검색에는 유용하지만 저희 서비스와 같은 자연어 요청이 들어오게 되면 변환한 tsquery를 만족하는 문서는 거의 존재하지 않습니다.

그래서 websearch_to_tsquery의 결과에서 '&' 연산자를 '|'로 바꾸어서 단 하나의 키워드를 가지고 있어도 문서를 검색할 수 있도록 구현했습니다.

상당히 많은 결과를 가져오지만 재정렬을 수행하고 순위가 높은 것들을 잘라서 가져오기 때문에 사용자의 요청과 유사한 문서를 가져올 수 있습니다.

to_tsquery(replace(websearch_to_tsquery('korean', query_text)::text, '&', '|'))

LLM 프롬프트 수정

기존에는 검색한 문서들을 단순하게 new line으로 연결해주었습니다.

이렇게 LLM에게 문서들을 전달했더니 각 문서들을 구별하지 못 했습니다.

아래는 2월 10일 회의록을 요약해달라고 요구한 결과입니다.
image

실제 2월 10일에 AI 기능 구체화에 대한 회의는 이루어지지 않았습니다.

AI가 이렇게 인식한 이유는 문서들을 구별하지 못 했기 때문입니다.

"2월", "10일" "회의록"과 유사한 문서에는 다른 날짜 회의록도 함께 가져오고 단순하게 new line으로 연결했더니 모든 회의록을 하나의 문서로 인식한 것이었습니다.

문서의 구분을 위해 각 문서마다 제목, 내용이 무엇인지 알려주는 프롬프트와 각 문서들을 '====='으로 구분하였습니다.

    const docsContent = retrievedDocs
      .map((doc) => `제목 : ${doc.title}\n내용 : ${doc.document}\n==========\n`)
      .join('\n');

이제 문서들을 잘 구분하고 조회한 여러 개의 문서들 중 2월 10일 회의록 문서만 요약을 해주게 되었습니다.

image

📑 참고 자료 & 스크린샷 (선택)

https://www.solanara.net/solanara/postgresql
https://www.postgresql.org/docs/current/textsearch-controls.html

@ezcolin2 ezcolin2 linked an issue Feb 18, 2025 that may be closed by this pull request
3 tasks
@ezcolin2 ezcolin2 changed the title Refactor be #92 키워드 기반 검색 및 hybrid 검색 성능 개선 Feb 18, 2025
@ezcolin2 ezcolin2 added 🐧🚀😶‍🌫️ BE 백엔드 관련 이슈/PR 추적 라벨 ♻️ Refactor 기능 추가 외 코드 변경, 코드 품질과 관련된 경우 labels Feb 18, 2025
@ezcolin2 ezcolin2 marked this pull request as draft February 18, 2025 05:24
@ezcolin2 ezcolin2 marked this pull request as ready for review February 18, 2025 06:23
@ezcolin2 ezcolin2 merged commit 5589eb2 into develop Feb 18, 2025
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🐧🚀😶‍🌫️ BE 백엔드 관련 이슈/PR 추적 라벨 ♻️ Refactor 기능 추가 외 코드 변경, 코드 품질과 관련된 경우

Projects

None yet

Development

Successfully merging this pull request may close these issues.

키워드 기반 검색 성능 개선

1 participant