NodeJS

[NodeJS] NodeJS 에서 elasticsearch 를 다뤄보자 (basic) - 1

개발조하 2023. 10. 30. 19:36

기억하자

개요

  • 회사 프로젝트를 진행하다 보니 NodeJS 에서 Elasitcsearch를 사용하여 구현을 진행했다.
  • 나중에 많이 활용할 것 같아서 자주 사용할 것 같은 기본 사용 방법에 대해서 정리 하고자 한다.
  • 본 내용의 자세한 소스코드는 아래 링크에서 확인 가능합니다.

NodeJS 에서 Elasticseach 사용하기

  • 사용 모듈: @elastic/elasticsearch

    • 설치 명령어: npm install @elastic/elasticsearch
  • elasticsearch Client 기본 설정

    const client = new Client({
        node: 'http://localhost:9200',      // 엘라스틱서치 url
        // 만약 https 으로 셋팅 했다면 아래 auth 필드를 이용해서 엘라스틱서치의 접속 정보를 입력합니다.
        // auth: {
        //     apiKey: { // API key ID and secret
        //         id: 'foo',
        //         api_key: 'bar',
        //     }
        // }
        maxRetries: 5,                      /* 재시도 횟수를 설정할 수 있습니다. (default: 3) */
        requestTimeout: 60000,              /* 응답 요청 타임 아웃 시간을 정할 수 있습니다. (default: 30000) */
        sniffOnStart: true,                 /* 엘라스틱 서치의 sniffing 이라는 기능을 클라이언트 객체 생성때 진행합니다. */
    })

인덱스 관련 NodeJS 에서 사용할 수 있는 Elasticsearch API

  • Client.ping() : 설정한 Elasticsearch Client Cluster가 작동 중인지 확인하는 API
    async function yjyoon_elasticsearch_module_init() {
      try {
          const pingResult = await client.ping()  // true or false
          console.log('Elasticsearch Client Setting SUCCESS')
          console.log(`Elasticsearch Ping Result: ${pingResult}`)
      } catch (error) {
          throw error
      }
    }
  • Client.indices.exists({index: ${인덱스 이름}}): Elasitcsearch 에 입력한 인덱스가 있는지 확인하는 API
    const checkIndexExist = await client.indices.exists({index: indexName});
    if(!checkIndexExist) {
        res.status(400);
        res.send({
            "message": `입력하신 "${indexName}"은 이미 존재하는 인덱스 입니다.`,
        });
    }
  • Client.indices.create({index: ${인덱스 이름}, mapping?: ${mapping 정보}}): Elasticsearch에 새로운 인덱스를 생성하는 API, mapping 정보 없이도 만들 수 있다.
    await client.indices.create({index: indexName, mappings: mapping});

데이터 관련 NodeJS 에서 사용할 수 있는 Elasticsearch API

  • Client.index({index: ${데이터를 삽입할 인덱스 이름}, document: ${데이터}}}): 사용자가 원하는 인덱스에 데이터를 삽입하는 API

    await client.index({index: indexName, document: insertData});
  • Client.search({index: ${인덱스 이름}, query: ${검색 쿼리}, scroll: ${스크롤 데이터 유지 시간}}): 데이터를 검색하는 API.

    • search scroll 을 사용하기 위해서는 Client.scroll({ scroll_id: body._scroll_id,scroll: '30s'}) API 를 사용해야 한다.

      const allQuotes: SearchHit<unknown>[] = []
      const responseQueue: SearchResponse<unknown, Record<string, AggregationsAggregate>>[] = []
      const searchQuery: SearchRequest = {
      index: indexName, 
      query: query ? query: {}
      }
      if(scrollFlag) { searchQuery.scroll = '30s' }
      const searchResult = await client.search(searchQuery)
      responseQueue.push(searchResult);
      while(responseQueue.length) {
        const body = responseQueue.shift()!;
      
        body.hits.hits.forEach((hit) => {
            allQuotes.push(hit)
        });
        if(!scrollFlag) { break }
      
        let totalHits = 0; // totalHits를 기본값 0으로 초기화합니다.
      
        if (body.hits && body.hits.total) {
            // body.hits 또는 body.hits.total이 undefined가 아닌지 확인합니다.
            if (typeof body.hits.total === 'object') {
                // Elasticsearch 7.x 이상의 경우, total은 객체입니다.
                totalHits = body.hits.total.value;
            } else {
                // 이전 버전의 Elasticsearch에서는 total이 직접 숫자입니다.
                totalHits = body.hits.total;
            }
        } else {
            // body.hits 또는 body.hits.total이 없는 경우, 적절한 오류 메시지 또는 로깅을 수행할 수 있습니다.
            console.error("Unexpected response structure, 'hits' or 'total' field is missing.");
            throw new Error("Unexpected response structure, 'hits' or 'total' field is missing.")
        }
      
        if(totalHits === allQuotes.length) {
            break
        }
      
        responseQueue.push( await client.scroll({
            scroll_id: body._scroll_id,
            scroll: '30s'
        }));
      }
  • Client.delete({index: ${삭제할 데이터가 있는 인덱스 이름}, id: ${삭제할 도큐먼트 id}}): 데이터를 삭제하는 API

    app.delete('/es/delete/data', checkElasticsearchIndexInBody, async (req: Request, res:Response) => {
      const indexName = req.body.index;
      const id = req.body.id;
      if(!id) {
          res.status(400);
          res.send({
              message: '삭제할 도큐먼트 id 값을 입력해주세요.'
          })
      }
      try {
          const deleteResult = await client.delete({id: id, index: indexName});
          res.send({
              message: `${indexName} 인덱스의 ${id} id 삭제 성공하였습니다.`,
              data: deleteResult
          })
      } catch (error) {
          res.status(400);
          res.send({
              message: (error as Error).message,
          });
      }
    });
  • Client.update({index: ${업데이트할 데이터가 있는 인덱스 이름}, id: ${업데이트할 도큐먼트 id}, doc: ${업데이트할 데이터}): 데이터를 업데이트하는 API

    app.post('es/data/update', checkElasticsearchIndexInBody, async (req: Request, res: Response) => {
      const indexName = req.body.index;
      const id = req.body.id;
      const data = req.body.data;
      if(!id) { 
          res.status(400);
          res.send({
              message: '업데이트할 도큐먼트 id 값을 입력해주세요.'
          });
      }
      if(!data) {
          res.status(400);
          res.send({
              message: '업데이트할 데이터를 입력해주세요.'
          });
      }
      try {
          const updateResult = await client.update({index: indexName, id: id, doc: data});
          res.send({
              message: `${indexName} 인덱스의 ${id} id 도큐먼트의 데이터 수정 성공하였습니다.`,
              data: updateResult
          })
      } catch (error) {
          res.status(400);
          res.send({
              message: (error as Error).message
          });
      }
    })

참고 사이트