프로젝트/기술적 선택

[기술적 선택] Elasticsearch 도입 이유 및 성능 측정

배발자 2023. 6. 6.
반응형

 
 
ELK-Stack의 E는 Elasticsearch를 의미한다. 필자는 해당 기술을 사용해 로그 수집의 기능을 구현하였고, 또 한가지가 더 있다. 
 
Elasticsearch를 이용하여 프로젝트의 핵심 기능인 검색 서비스를 추가한 것이다. 
필자가 진행한 프로젝트는 '개발자 커뮤니티 사이트'이며 지금의 티스토리처럼 게시글을 업로드할 수 있다. 또한, 게시글을 업로드하게 되면 남들이 올린 게시글들이 모두 통합된 커뮤니티 페이지에서 보여진다. 해당 페이지에서 찾고자 하는 키워드로 검색을 하면 제목에 해당되는 글뿐만 아니라 내용에도 해당 키워드가 존재한다면 검색이 가능하다. 
 
그냥 관계형 데이터 베이스에서 like문을 활용하여 키워드 검색을 하면 되는 거 아니냐는 질문이 들어올 수도 있다. 하지만
키워드 검색을 위해 한 게시물에 100줄이 넘어가는 컨텐츠들을 풀스캔으로 like문을 사용한다는 것은 굉장한 시간을 초래할 수 있다. 
 
물론, 데이터가 매우 적다는 가정하에 like문을 사용하는 것도 가능하지만, 필자가 고려한 것은 먼 미래의 우리 서비스의 환경을 생각한다면 관계형 데이터베이스로 데이터들을 전달해주는 것은 잘못된 선택이라고 생각했다. 
 
그러므로 검색 기능을 최대화할 수 있는 Elasticsearch를 활용하여 "제목+내용" 안에 포함하는 검색 키워드를 빠른 시간안에 찾을 수 있는 기술을 적용하였다. 
 

Elasticsearch

 
 
Elasticsearch는 Apache Lucene 기반의 오픈소스 실시간 분산 검색 엔진이다. Apache Lucene은 내부적으로 Inverted File Index를 활용하여 색인 구조를 생성한다. 이를 기반으로 하는 Elasticsearch 또한 동일한 방식으로 색인 구조를 생성하여 데이터를 저장한다. 
 
 
 

역색인(Inverted Index)란? 

 
키워드를 통해 문서를 찾아내는 방식을 의미한다. 
책 뒷편의 색인된 키워드를 이용해 역으로 본문(혹은 문서)을 찾는 방식을 말한다. 이해가 안가는 분들은 주변에 책 한권을 집어서 맨 뒷장을 보면 키워드에 따른 페이지가 적혀있을 것이다. 
 
역색인 구조 생성 과정은 다음과 같다. 
 

문서 ID 문서 내용
doc1 Talented developer Bae
doc2 Passionate Developer Bae
doc3 Innovative Developer Lee

 
1. 원문에서 색인어를 추출하고 분석기에 따라 소문자로 변환하기도 한다. 소문자와 대문자는 다른 단어로 인식. 
 

문서 ID 색인어
doc1 talented, developer, bae
doc2 passionate, developer, bae
doc3 innovative, developer, lee

 
2. 색인어수 (문서빈도)를 카운트하고 정렬한다. 
 

색인어 색인어수(문서빈도)
talented 1
passionate 1
innovative 1
developer 3
bae 2
lee 1

 
3. 색인어별 역색인 벡터를 만든다. 
 

문서 ID 색인어 문서ID
talented 1 doc1
passionate 1 doc2
innovative 1 doc3
developer 3 doc1, doc2, doc3
bae 2 doc1, doc2
lee 1 doc3

 
 

역색인의 이점 

 
책에서 찾고자 하는 단어를 맨뒤의 색인을 통해서 몇페이지인지 바로 알아내는 것이 빠를까?
아니면 책을 펼쳐서 처음부터 끝까지 살펴보는 것이 빠를까?
전자가 훨씬 빠를 것이다. 
 
필자는 RDMS에서 like 키워드를 활용한 검색 성능과 Elasticsearch를 활용한 검색 성능을 측정하였다. 
RDMS 같은 경우 모든 열에 대한 LIKE 문이 수행되기 때문에 풀스캔이 이뤄진다. 그러므로 데이터가 많으면 많을수록 성능이 매우 떨어지게 된다. 
 
 
 

성능 테스트 - 7만건 더미 데이터 추가

 

 

공공데이터 포털

국가에서 보유하고 있는 다양한 데이터를『공공데이터의 제공 및 이용 활성화에 관한 법률(제11956호)』에 따라 개방하여 국민들이 보다 쉽고 용이하게 공유•활용할 수 있도록 공공데이터(Datase

www.data.go.kr

 
 
공공데이터 포털 사이트에서 도서 목록과 관련된 데이터들을 다운받아 실제 Database 데이터를 삽입하고 성능 측정을 해보려고한다. 
 
어떠한 테스트를 진행을 할거냐면, 게시물들을 특정 키워드로 검색을 했을 경우 기본적인 RDB에서 검색 속도와  Elasticsearch를 활용했을 때 검색 속도를 비교하는 것이다. 
 
 

 
 
제목과 내용을 담은 적절한 데이터를 만들기 위해 공공데이터를 조금 조작해보려고한다. 

 

@Service
public class CsvService {

  @Autowired
  private BoardRepository boardRepository; 

  @Autowired
  private BoardDocumentRepository boardDocumentRepository; 

  static int like = 0;
  public List<String[]> readCsv(String fileName) {
    int n = 20;
    while(n-->0){

      List<String[]> records = new ArrayList<>();

      try {
        String filePath = Paths.get("C:\\Users\\BAE\\Desktop", fileName).toString();
        Charset eucKrCharset = Charset.forName("EUC-KR");
        InputStreamReader reader = new InputStreamReader(new FileInputStream(filePath), eucKrCharset);

        CSVParser csvParser = new CSVParser(reader, CSVFormat.DEFAULT);
        int cnt = 0;

        StringBuilder sb = new StringBuilder();
        int checkTime = 0;
        for (CSVRecord csvRecord : csvParser) {
          int recordSize = csvRecord.size();
          String[] record = new String[2];
          record[0] = csvRecord.get(3);
          record[1] = csvRecord.get(4);

          sb.append(record[0]).append(" ").append(record[1]).append(" ");
          checkTime++;
          if(checkTime==100){
            Board board  = new Board();
            board.setTitle(record[1]);
            board.setContent(sb.toString());
            board.setLikes(like++);
            sb = new StringBuilder();
            checkTime = 0;
            Board savedBoard = boardRepository.save(board);

            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSS");

            BoardDocument boardDocument = BoardDocument.builder()
                .id(savedBoard.getBoardSeq().toString())
                .title(savedBoard.getTitle())
                .content(savedBoard.getContent())
                .imageUrl(savedBoard.getImageUrl())
                .createTime(savedBoard.getCreateTime().format(formatter))
                .modifiedTime(savedBoard.getModifiedTime().format(formatter))
                .likes(savedBoard.getLikes())
                .build();

            boardDocumentRepository.save(boardDocument);
          }
          records.add(record);
        }
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
    return new ArrayList<>();
  }
}

 

 
 
이 코드는 CSV 파일에서 데이터를 읽어, 100개의 레코드마다 한 번씩 데이터베이스에 저장하는 기능을 수행하는 Java Spring Service 클래스이다.
 

  1. readCsv(String fileName) 함수 : 주어진 파일 이름의 CSV 파일을 읽고, 데이터를 처리하는 메서드이다. 이 메서드에서는 20회 반복해서 CSV 파일을 처리한다. CSV 파일은 공공데이터 포털에서 다운받은 파일이다. 
  2. CSV 파일 처리 과정 : CSV 파일은 한 줄씩 읽어 CSVRecord로 처리하고, 각 레코드에서 특정 인덱스(3과 4)의 값을 가져와 String 배열에 저장한다. 그 후 StringBuilder에 레코드의 데이터를 추가한다. 100개의 레코드가 처리될 때마다, StringBuilder에 저장된 데이터를 사용해 Board 객체를 생성하고 저장한다. 왜냐하면 실제 커뮤니티 사이트를 보면 제목과 100줄 이상의 내용들을 포함하고 있다. (대략) 제목과 100줄 정도의 내용을 담은 데이터로 추가하려고 한다. 
  3. Board 객체 저장 : 100개의 레코드가 처리될 때마다, 새로운 Board 객체를 생성하고 **boardRepository**를 사용해 데이터베이스에 저장한다.
  4. BoardDocument 객체 생성 및 저장 : Board 객체가 저장된 후, BoardDocument 객체를 생성하고, **boardDocumentRepository**를 사용해 데이터베이스에 저장한다.(RDB와 Elasticsearch 데이터 저장) 

 

"title" : "거버, 피터",
"content" : "탐정이 된 과학자들 피터스, 마릴리 코스모사피엔스 : 우주의 기원 그리고 인간의 진화 = Cosmosapiens 핸즈, 존 코드 브레이커 : 제니퍼 다우드나, 유전자 혁명 그리고 인류의 미래 아이작슨, 월터 최종 경고 : 6도의 멸종 라이너스, 마크 지구는 괜찮아, 우리가 문제지 : 곽재식의 기후 시민 수업 곽재식 지구가 너무도 사나운 날에는 : 위기의 지구를 위한 특별한 과학 수업 가치를 꿈꾸는 과학교사모임 중학수학 개념사전 93 : 개념 씹어먹고 수학문제 풀어 봤니 조안호 절기따라 만나는 생태이야기 신경아 전략가, 잡초 이나가키 히데히로 적도에 펭귄이 산다 쟈코민, 세레나 자연의 시간 황경택 이런 물리라면 포기하지 않을 텐데 : 광쌤의 쉽고 명쾌한 물리학 수업 이광조 이끼와 함께 : 작지만 우아한 식물, 이끼가 전하는 지혜 키머러, 로빈 월 원소 : 세상을 이해하는 가장 작지만 강력한 이야기 볼, 필립 우주는 계속되지 않는다 : 천체물리학자가 바라본 우주의 종말 맥, 케이티 우주 쓰레기가 온다 : 지속 가능한 평화적 우주 활동을 위한 안내서 = Space debris 최은정 우아한 우주 : 커다란 우주에 대한 작은 생각 샌더스, 엘라 프랜시스 우리에겐 과학이 필요하다 : 거짓과 미신에 휘둘리지 않고 과학으로 세상을 이해하는 힘 아이그너, 플로리안 우리는 모두 별에서 왔다 : 138억 년 전 빅뱅에서 시작된 별과 인간의 경이로운 여정 윤성철 오늘의 천체관측 : 밤하늘을 여행하는 초보 별지기를 위한 가이드북 심재철 오늘은, 별자리 여행 지호진 어, 지금 땅 움직였지 : 과학영재고 선생님의 지진 이야기 김도형 알쏭달쏭 고래 100문 100답 미드, 제임스 안녕하세요, 비인간동물님들! : 고단한 동료 생명체를 위한 변호 남종영 아시모프의 코스모스 아시모프, 아이작 실재란 무엇인가 : 양자물리학의 의미를 밝히는 끝없는 여정 베커, 애덤 식물의 책 : 식물세밀화가 이소영의 도시식물 이야기 이소영 빛이 매혹이 될 때 : 빛의 물리학은 어떻게 예술과 우리의 세계를 확장시켰나 서민아 별은 사랑을 말하지 않는다 : 밤하늘과 함께하는 과학적이고 감성적인 넋 놓기 김동훈 반反종차별주의 : 인간, 동물, 자연의 새로운 관계 맺기 카롱, 에므리크 모든 공룡에게는 그들만의 이야기가 있다 : 지금도 살아 있는 공룡의 경이로운 생명의 노래 마루야마 다카시 동물은 어떻게 슬퍼하는가 킹, 바버라 J 동네에서 자연을 관찰하는 9가지 방법 최성용 도시를 바꾸는 새 : 새의 선물을 도시에 들이는 법 비틀리, 티모시 다정함의 과학 : 친절, 신뢰, 공감 속에 숨어 있는 건강과 행복의 비밀 하딩, 켈리 내 마음의 들꽃 산책 : 이유미 송기엽의 식물 산책 에세이 이유미 나무 이야기 : 나무는 어떻게 우리의 삶을 바꾸었는가 홉스, 케빈 나무 신화 : 나무로 본 유럽 민속의 기원과 효능 라우데르트, 도리스 꽃도감 : 꽃집에서 인기 있는 꽃 469종 나카무라 히로미 과학자의 흑역사 : 세계 최고 지성인도 피해 갈 수 없는 삽질의 기록들 양젠예 과학을 달리는 십대 : 환경과 생태 소이언 과학을 달리는 십대 : 스마트 테크놀로지 구정은 2도가 오르기 전에 : 기후위기의 지구를 지키기 위해 알아야 할 모든 것 남성현 1 더하기 1은 2인가 : 가장 단순한 수식으로 묻는 수학의 본질 배로, 존 흔들리지 않는 학급운영의 비밀 : 학급긍정훈육법으로 운영하는 멍멍샘의 교실 정호중 확률적 사고의 힘 다부치 나오야 혁신학교 이야기 : 행복한 배움을 위한 교사들의 도전기 도란도란 교육희망을 일궈가는 사람들 하루를 48시간으로 사는 마법 : 방송국 헤르미온느 이재은의 삶을 빛나게 하는 마법의 주문 이재은 픽사 스토리텔링 : 고객의 마음을 사로잡는 9가지 스토리 법칙 룬, 매튜 평화는 처음이라 이용석 판을 까는 여자들 : 환멸나는 세상을 뒤집을 '이대녀'들의 목소리 신민주 (내 안의 의지 근육을 깨우는) 파워 아워 허버트, 에이드리엔 최소한의 선의 문유석 초등자치, 이렇게 해요! : 읽으면 즐겁고 곁에 두면 든든한 학생자치 길잡이 김영훈 (한 해의 주제 수업을 고민하는 교사들을 위한) 초등 그림책 수업 그림책사랑교사모임 초등 교사 영업 기밀 : '우리 아이 이번에 초등학교 가요'라는 말에 책가방보다 먼저 사줘야 할 책 윤지선 창업가의 답 : 혁신을 이룬 스타트업은 어떻게 데스밸리를 넘었나 성호철 질병, 낙인 : 무균사회와 한센인의 강제격리 김재형 지식산업센터로 월세통장 만들기 : 부동산 투자의 마지막 골든존 단희쌤 좋아하는 것은 나누고 싶은 법 : 아이들과 함께하는 시 수업 이야기 최지혜 존버씨의 죽음 : 갈아넣고 쥐어짜고 태우는 일터는 어떻게 사회적 살인의 장소가 되는가 김영선 존리의 왜 주식인가 : 시간에 투자하는 대가의 생각 = Why Invest in Stocks 리, 존 접객의 일류, 이류, 삼류 시치조 치에미 저쪽이 싫어서 투표하는 민주주의 : 반대를 앞세워 손익을 셈하는 한국 정치 김민하 잘 살아라 그게 최고의 복수다 권민창 (작가와 함께 하는) 그림책 토론 수업 그림책사랑교사모임 (시대의 경계에서) 일인칭으로 말 걸기 김진경 일의 격 : 성장하는 나, 성공하는 조직, 성숙한 삶 신수정 인싸를 죽여라 : 온라인 극우주의, 혐오와 조롱으로 결집하는 정치 감수성의 탄생 네이글, 앤절라 인권 감수성을 기르는 그림책 수업 이태숙 (14만 초등 학부모의 멘토 이은경 쌤과 함께하는) 초등학교 입학준비 이은경 이 장면, 나만 불편한가요 : 미디어로 보는 차별과 인권 이야기 태지원 윤석열 X파일 : 검찰공화국을 꿈꾸는 윤석열 탐사 리포트 열린공감TV (동화와 동요가 살아 숨쉬는) 유치원 교실놀이 100 김연희 원전 마을 : 월성원전 인근 주민들의 투쟁 이야기 김우창 우리편 편향 : 신념은 어떻게 편향이 되는가 스타노비치, 키스 E ('몫'과 '권리'를 가진 사람) 우리는 청소년-시민입니다 박지연 우리는 난민입니다 : 나의 여정, 그리고 세계 곳곳 여성 청소년 난민의 목소리 유사프자이, 말랄라 우리가 잠들지 못하는 11가지 이유 : 모든 게 터지기 일보 직전인 4050 여성들을 위한 인생 카운슬링 칼훈, 에이다 우리 안의 파시즘 2.0 : 내 편만 옮은 사회에서 민주주의는 가능한가 임지현 용서하지 않을 권리 : 피해자를 바라보는 적정한 시선과 태도에 관하여 김태경 외인구단 리부팅 : 야구장 속 여성의 자리는 어디인가 턱괴는여자들 왜요, 제 권리인데요 : 알면 보이는 모두의 인권 오승현 왜요, 그 뉴스가 어때서요 : 뉴스 똑똑하게 보는 법 김청연 오징어 게임과 콘텐츠 혁명 : 세계를 열광시킨 K-콘텐츠의 비밀 정길화 오르는 주식을 사들이는 차트매매법 : 황족의 한 권으로 끝내는 차트투자 황족 오늘의 법정을 열겠습니다 : 시민력을 키우는 허승 판사의 법 이야기, 세상 이야기 허승 오늘부터 다시 스무 살입니다 김미경 오늘부터 나는 세계 시민입니다 공윤희 연구자의 탄생 : 포스트-포스트 시대의 지식 생산과 글쓰기 김성익 여자의 재능은 왜 죄가 되었나 : 칼로에서 멘디에타까지, 라틴아메리카 여성 예술가 8인 유화열 (엄마와 아이가 반드시 알아야 할) 슬기로운 고등생활 : 사춘기를 극복하고 입시를 똑똑하게 준비하는 고등생활의 모든 것 김지영 어린 시민 : 어리다고 견뎌야 할 말은 없습니다 아거 어떻게 경제적 자유를 얻을 것인가 : SK바이오투자센터장 이동훈의 투자 수업 이동훈 아직도 그런 말을 하세요 : 마땅히 불편한 말들 무르지아, 미켈라 아웃사이더 : 세상에 없던 방식으로 성공한 사람들의 비밀 링크너, 조시 신양반사회 : 586, 그들이 말하는 정의란 무엇인가 김은희 시간을 찾아드립니다 : 루틴을 벗어나, 나만의 속도로 사는 법 윌런스, 애슐리 슬기로운 중학교 입학 준비 이은경 스토리의 기술 : 감정 전달 게임에서 승리하는 법 거버, 피터 ",

 
하나의 데이터는 위와 같이 저장되어있다. 
즉, 제목과 100줄 정도 되는 내용들이 하나의 레코드에 저장되어 있으며, 약 7만 건의 데이터들이 Elasticsearch와 RDB에 각각 저장되어있다. 
 

JPA+ JPQL VS Elasticsearch

 

JPA + JPQL like 문으로 특정 키워드 검색 

 

@Test
  void testSearchByKeyword2() {
    String keyword = "여섯"; // 검색할 키워드

    // 시간 측정 시작
    long startTime = System.currentTimeMillis();

    List<Board> results = boardService.searchByJpaKeyword(keyword);
    System.out.println("size : " + results.size());
    int cnt = 1;
    for(Board s : results){
      System.out.println(s.getCreateTime());
      cnt++;
      //System.out.println(s.getContent());
    }
    // 시간 측정 끝
    System.out.println();
    long endTime = System.currentTimeMillis();
    long elapsedTime = endTime - startTime;

    System.out.println("JPA 검색 성능 측정: " + elapsedTime + "ms");

  }

 

Hibernate: select count(board0_.board_seq) as col_0_0_ from board board0_ where board0_.title like ? escape ? or board0_.content like ? escape ?
size : 18
2023-05-02T16:17:36.438147
2023-05-02T16:17:36.823627
2023-05-02T16:17:37.085559
2023-05-02T16:17:38.457857
2023-05-02T16:17:38.859284
2023-05-02T16:17:40.812412
2023-05-02T16:17:40.951051
2023-05-02T16:17:48.928037
2023-05-02T16:17:54.455180
2023-05-02T16:17:56.927577
2023-05-02T16:17:57.039147
2023-05-02T16:18:00.390627
2023-05-02T16:18:00.762554
2023-05-02T16:18:02.425391
2023-05-02T16:18:02.775344
2023-05-02T16:18:02.897717
2023-05-02T16:18:03.189839
2023-05-02T16:18:03.598685

JPA 검색 성능 측정: 2059ms

 

 
위의 시간들은 데이터가 삽입된 시간들이다. 출력 내용과 무관하므로, 행의 개수를 표현한 것이라고 생각해보자. 
 
 

Elasticsearch로 특정 키워드 검색

 

@Test
  void testSearchByKeyword() {
    String keyword = "여섯"; // 검색할 키워드
    // 시간 측정 시작
    long startTime = System.currentTimeMillis();
    List<BoardDocument> results = boardService.searchByKeyword(keyword);
    // 시간 측정 끝
    System.out.println("size : " + results.size());
    int cnt = 1;
    for(BoardDocument s : results){
      System.out.println(s.getCreateTime());
      cnt++; 
    }
    System.out.println();
    long endTime = System.currentTimeMillis();
    long elapsedTime = endTime - startTime;

    System.out.println("Elasticsearch 검색 성능 측정: " + elapsedTime + "ms");
  }

 

2023-05-02 17:42:07.409  INFO 21136 --- [           main] com.example.demo.service.BoardService    : Searching by searchTerm: 여섯
size : 18
2023-05-02 16:44:02.543525
2023-05-02 16:44:18.761004
2023-05-02 16:44:34.568645
2023-05-02 16:44:51.126364
2023-05-02 16:45:07.959558
2023-05-02 16:46:04.762400
2023-05-02 16:46:05.002532
2023-05-02 16:46:20.896610
2023-05-02 16:46:21.178392
2023-05-02 16:46:37.714687
2023-05-02 16:46:37.996873
2023-05-02 16:46:55.715343
2023-05-02 16:46:56.035629
2023-05-02 16:48:39.217869
2023-05-02 16:48:39.611739
2023-05-02 16:47:14.243640
2023-05-02 16:48:17.001645
2023-05-02 16:48:17.343423

Elasticsearch 검색 성능 측정: 259ms

 

 
JPA 검색 성능 측정: 2059ms
Elasticsearch 검색 성능 측정: 259ms

크게 차이나는 것을 확인할 수 있다.

 

반응형

댓글