오픈 소스 Lucene(루씬)을 기반으로 하는 

대표적인 검색엔진인 Solr(쏠라)와 Elasticsearch(엘라스틱서치)는 Lucene을 기반으로 하지만 사용법은 전혀 다르다.

특히 데이터 수집(색인-Indexing)에 있어서, 

일반적인 문서 색인은 Solr가 쉽게 구현할 수 있고, 웹 로그와 같은 짧은 형태의 데이터는 Elasticsearch가 쉽게 구현할 수 있는 것 같다.

또, 검색(Search) 문법에 있어서 Solr는 Lucene의 문법을 그대로 따르고, Elasticsearch는 자신만의 문법을 구현한 차이가 있다.

이 두 가지 검색엔진의 차이를 이해하고, 

둘 다 익히기 위해 예제를 만드는 중으로 세부 기능을 구현하는데 시간이 많이 걸려 (언제 끝날지...)

일단 먼저 기본적인 색인 / 검색 기능을 구현하고 설치 / 사용하는 방법을 정리한다.

Solr와 Elasticsearch예제는 각각 Github 에서 다운로드 받을 수 있다.

 

    1. Solr 설치
    2. Solr 주요 설정과 코드
    3. Elasticsearch 설치
    4. Elasticsearch 주요 설정과 코드

 

먼저, Solr 예제 사용법을 정리한다.

예제는 그림과 같이 기존에 공유한 웹 프로젝트인 Project9에 검색 기능을 추가하는 형태로 작성했다.

Project9에 대한 설명은 기존 블로그에서 확인 할 수 있다.

Project9의 게시판 데이터를 대상으로 하고,

공지사항과 일반 게시판의 게시판 종류를 데이터 종류로 구분해서 통합 검색 기능을 구현하였다.

 

설치 방법은 Solr설치 (데이터 수집)와 Project9 설치(데이터 검색)로 구분된다.

Solr 설치

  1. Solr 다운로드 및 설치
  2. 실행: solr start
  3. 라이브러리 설치
  4. 데이터 구성
  5. 색인

 

Project9 설치

    소스 다운로드 및 설치

참고: Project9 설치는 기존 방식과 동일하기 때문에 여기에서 별도로 정리하지 않는다.

 

Solr 설치

 

1.Solr 다운로드 및 설치

Solr 다운로드 사이트에서 운영체제에 맞는 압축 파일을 다운로드 받아서, 압축을 해제 한다.

 

2. 실행: solr start

콘솔창(cmd)에서 Solr를 실행하고(start),  데이터를 저장할 코어(project9)를 생성한다(create).

주의: Solr를 사용하려면 JDK1.8 이상이 설치 되어 있어야 한다.

 

웹 브라우저에서 http://localhost:8983/solr/를 입력해서 Solr 관리창에 접속한다.

생성된 project9 코어르 확인하고, 색인을 하기 위해 선택한다.

 

3. 라이브러리 설치

웹 프로젝트인 Project9은 MariaDB에 데이터를 저장하기 때문에,

데이터를 수집 하기 위해서 MariaDB 드라이버를 설치하고,

수집된 한글 데이터를 색인하기 위해 아리랑 형태소 분석기를 추가한다.

먼저, MariaDB 공식 웹 사이트에서 드라이버를 다운로드 받거나  Project9을 설치하고 .m2 폴더에서 mariadb-java-client-x.x.x.jar파일을 복사해서 solr/dist 폴더에 넣어준다.

형태소 분석기는 아리랑을 사용하고, 커뮤니티 사이트의 다운로드 메뉴에서 적절한 버전을 다운로드 받아서

[solr설치경로]\server\solr-webapp\webapp\WEB-INF\lib 폴더에 넣어 준다.

 

4. 데이터 구성

이 작업을 하기 위해서는 Project9의 소스가 설치 되어 있어야 한다.

데이터 구성에 필요한 정보가 Project9의 소스의 solr 폴더에 있기 때문이다.

문서 작성의 단계상 다음 단계에 하는 것으로 정리했지만, Project9의 Solr 예제 소스를 다음과 같이 다운로드 받는다.

또는, Eclipse에서 다운로드 받는 것이 편리하고, Eclipse로 git 소스를 다운로드 받는 것은 기존 블로그를 참고하면 된다.

        git clone https://github.com/gujc71/Project9_Solr.git

 

다운 받은 Project9 소스 폴더에서 project9_solr\solr에 있는 모든 파일을 [solr설치경로]\server\solr\project9\conf 폴더에 복사한다.

 

지금까지 설정한(복사한) 내용들이 반영될 수 있도록 Solr를 재가동 한다.

         solr restart -p 8983

 

5. 색인

Solr의 색인은 DIH (Data Import Handler)를 이용하며 자세한 정리는 DIH 예제 분석에서 정리되어 있다.

생성한 project9 코어를 선택하고, Dataimport 메뉴에서 풀색인(full-import)을 실행한다(execute).

위 그림의 우측 녹색 배경에 출력된 내용처럼 41건의 데이터가 조회되면 잘 색인(Indexing)된 것이다.

주의: 실행 결과는 [Refresh Status] 버튼을 계속 클릭하거나, [Auto-Refresh Status]를 선택하면 된다.

 

[Query] 메뉴에서 실행(Execute Query] 버튼을 클릭해서 색인한 데이터를 조회한다.

검색과 관련된 자세한 사항은 인터넷 검색을 해보거나 기존 블로그 내용을 참고하면 된다.

 

Project9 설치

 

Project9의 설치는 기존에 공개한 웹 프로젝트로 기존 블로그에 정리되어 있다.

이 내용과 동일하고, Github 프로젝트 명만 project9이 아닌 project9_solr로 바꾸어 설치하면 된다.

그리고, 적당한 계정으로 로그인해서 [통합검색] 메뉴에서 다양한 검색 결과를 확인할 수 있다.

 

이상으로 예제를 실행하는 방법을 정리하였고

데이터를 수집해서 색인하고, 색인한 데이터를 프로그램에서 조회하는 주요 부분에 대한 코드 정리는 다음 블로그에서 정리한다.

 

 

검색 서버를 구축 하려면 필요한 사항이 많지만, 가장 중요한 부분은 색인(Indexing)과 검색(Search)이다.

Solr에서 색인은 DIH (Data Import Handler)로 처리하고

검색식을 Solr 검색엔진에 전송하고 결과를 받아서 보여주는 Java(SolrJ) 부분으로 나누어 구현하였다.

DIH에 대한 사항SolrJ 사용법은 각각 이전에 정리하였고

구현 방법을 모두 여기에 정리 할 수 없기 때문에, 색인과 검색에 관련된 몇 가지 주요한 사항만 정리한다.

주의: 용어나 옵션에 대한 상세한 설명을 정리하지 않기 때문에 Solr를 잘 모른다면 다음 내용이 어려울 수 있다.

    1. Solr 설치
    2. Solr 주요 설정과 코드
    3. Elasticsearch 설치
    4. Elasticsearch 주요 설정과 코드

 

정리할 주요 내용은 다음 그림과 같다.

 

색인

색인 작업은 Solr 검색 서버가 처리하는 것이고,

개발자가 구현하는 것은 Solr가 색인 할 수 있도록 데이터를 수집하는 작업을 의미한다.

데이터 수집 작업은 수집한 데이터를 저장하는 공간에 대한 정의와 데이터를 수집하는 방법에 대한 정의로 구분된다.

 

앞서서 코어(project9)를 생성하고, 설정에([solr설치경로]\server\solr\project9\conf) 몇 가지 파일을 복사했다.

이 복사한 파일 중 managed-schema 파일은 다음과 같이 코어에 저장한 데이터 구조를 지정하는 파일이다.

<field name="brdtype" type="string" indexed="true" stored="true"/>
<field name="bgno" type="string" indexed="true" stored="true"/>
<field name="brdno" type="string" indexed="true" stored="true"/>
<field name="brdwriter" type="string" indexed="true" stored="true"/>
<field name="brdtitle" type="text_ko" indexed="true" stored="true"/>
<field name="brdmemo" type="text_ko" indexed="true" stored="true"/>
<field name="brddate" type="string" indexed="true" stored="true"/>
<field name="brdtime" type="string" indexed="true" stored="true"/>

<field name="reno" type="string" indexed="true" stored="true" multiValued="false" required="false" />
<field name="rememo" type="string" indexed="true" stored="true" multiValued="false" required="false" />

<field name="fileno" type="string" indexed="true" stored="true" multiValued="false" required="false" />
<field name="filename" type="string" indexed="true" stored="true" multiValued="false" required="false" />
<field name="filememo" type="text_ko" indexed="true" stored="true" multiValued="false" required="false" />

managed-schema

게시판의 내용(brd*)과 댓글(reno, rememo), 첨부파일(file*)에 대한 정보를 저장한다.

검색엔진은 RDBMS와 다르게 데이터를 정규화해서 저장하지 않고, 위 코드와 같이 하나의 문서로 모든 내용을 저장한다.

 

데이터 타입(type)은 여러가지가 있지만

여기서는 형태소 분석기로 분석해서 색인할 데이터는 text_ko라는 사용자 정의 타입으로 지정하고,

그대로 색인할 데이터는 string으로 지정한다.

 

사용자 정의 타입인 text_ko는 managed-schema 파일 하단에 다음과 같이 정의하였다.

<fieldType name="text_ko" class="solr.TextField" >
    <analyzer type="index">
        <tokenizer class="org.apache.lucene.analysis.ko.KoreanTokenizerFactory"/>
        <filter class="solr.LowerCaseFilterFactory"/>
        <filter class="solr.ClassicFilterFactory"/>
        <filter class="org.apache.lucene.analysis.ko.KoreanFilterFactory" hasOrigin="true" hasCNoun="true"  bigrammable="false" queryMode="false"/>
        <filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="false" />
        <filter class="org.apache.lucene.analysis.ko.WordSegmentFilterFactory" hasOrijin="true"/>
        <!--filter class="org.apache.lucene.analysis.ko.HanjaMappingFilterFactory"/>
        <filter class="org.apache.lucene.analysis.ko.PunctuationDelimitFilterFactory"/-->
        <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt"/>
    </analyzer>
    <analyzer type="query">
        <tokenizer class="org.apache.lucene.analysis.ko.KoreanTokenizerFactory"/>
        <filter class="solr.LowerCaseFilterFactory"/>
        <filter class="solr.ClassicFilterFactory"/>
        <filter class="org.apache.lucene.analysis.ko.KoreanFilterFactory" hasOrigin="true" hasCNoun="true" bigrammable="false" queryMode="false"/>
        <filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="false" />
        <filter class="org.apache.lucene.analysis.ko.WordSegmentFilterFactory" hasOrijin="true"/>
        <filter class="org.apache.lucene.analysis.ko.HanjaMappingFilterFactory"/>
        <filter class="org.apache.lucene.analysis.ko.PunctuationDelimitFilterFactory"/>
        <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt"/>
    </analyzer>
</fieldType>

managed-schema

위 코드는 한글 형태소 분석기인 아리랑을 사용하기 위해 필요한 설정으로 상세한 설명은 인터넷에서 찾아보면 된다.

 

저장한 공간을 지정한 뒤에는 db-data-config.xml 파일에서 다음과 같이 수집할 데이터를 지정한다.

<dataSource driver="org.mariadb.jdbc.Driver" url="" user="" password="" readOnly="True"/>
<dataSource name="file" type="BinFileDataSource" basePath="d:\workspace\fileupload\" />
<document>
    <entity name="board" rootEntity="true" query="SELECT ">
       <field column="brdno" name="id" />
       필드들~~
       <entity name="reply" child="true" pk="reno" query="SELECT">
          <field column="reno" name="reno" />
           <field column="rememo" name="rememo" />
       </entity>

        <entity name="boardFileList" child="true" pk="fileno" query="SELECT">
          <field column="fileno" name="fileno" />
          <field column="filename" name="filename" />

           <entity name="boardFile" processor="TikaEntityProcessor">
             <field column="text" name="filememo" />
           </entity>
        </entity>
    </entity>
</document>

db-data-config.xml

데이터 수집은 SQL문으로 작성하고, DIH 예제 정리를 참고하면 된다.

단순한 구조는 DIH 예제에 정리한 것과 같이 데이터를 가지고 오는 SQL만 초기 전체 데이터와 증가된 데이터에 대해서 작성한다.

여기서 사용된 구조는 조금 복잡한데,

게시판 글이(board) 부모이고, 댓글(reply)과 첨부파일이(boardFileList) 자식인 구조로 작성되었다.

위 XML 코드를 자세히 보면 게시판(board) entity 안에 댓글(reply)과 첨부파일(boardFileList) entity 가 포함되어 있고,

게시판(board) entity는 root로, 댓글(reply)과 첨부파일(boardFileList) entity는 child로 설정한다.

이것을 nested documents라고 한다.

데이터가 직관적으로 저장되지 않아서 개인적으로 Solr가 Elasticsearch보다 조금 부족한 부분이라고 생각하는데,

다음 그림과 같이 자식 데이터인 댓글이 부모와 따로 저장되어 있다.

Elasticsearch에서는 자식 데이터가 배열형처럼 처리되어 부모 글과 같이 조회된다.

주의: Solr에서 자식 문서(nested) 처리를 찾기 위해 제법 많은 시간을 허비했지만 찾지 못 한 것 일 수 있음.

 

마지막으로 첨부 파일은 Tika(TikaEntityProcessor)를 사용해서 텍스트로 변환해서 색인한다.

Tika로 추출된 텍스트(text)는 filememo라는 이름으로 Solr에 색인된다.

 

검색

 

검색은 기존 Prohect9 프로젝트에 구현되어 있으며

하나의 Java파일(gu.search.SearchCtr)과 Jsp(search.jsp) 파일로 작성하였다.

search.jsp 파일은 검색 결과를 Json으로 받아서 적절하게 보여주는 기능을 하고, 여기서 정리하지 않는다.

SearchCtr.java 파일은 SolrJ를 기반으로 작성하였고, 

검색식을 만드는 부분, SolrJ를 이용하여 Solr에 질의하는 부분, Solr에서 반환 받은 결과를 정리해서 Json으로 작성하는 부분으로 나눌 수 있다.

검색식을 만드는 기본 코드는 다음과 같고

사용자가 입력한 키워드와 검색 필드에 맞추어 Solr(Lucene) 검색식을 생성한다.

Solr(Lucene) 검색식은 기존 자료를 참고하고, 기본적으로 [필드명 : 키워드] 형태로 작성한다.

예를 들어 글 제목에서 JS 란 단어를 찾는 경우 brdsubject:JS가 된다.

private String makeQuery(String keyword, String[] fields) {
    String queryStr = "";
    
    for (int i=0; i<fields.length; i++) {
        if (queryStr.length()>0) queryStr += " OR ";
        if ("brdfiles".equals(fields[i]))
            queryStr += " {!parent which=\"brdtype:1\"}filememo:" + keyword;
        else 
        if ("brdreply".equals(fields[i]))
             queryStr += " {!parent which=\"brdtype:1\"}rememo:" + keyword;
        else queryStr += " " + fields[i] + ":" + keyword;
    }
    
    return queryStr;
}

SearchCtr.java

makeQuery()함수에서는 작성자(brdwriter), 글 제목(brdsubject), 글 내용(brdmemo) 등의 여러 필드(fields)가 searchRange 변수에 콤마(,)로 묶여서 넘어오기 때문에 콤마로 나누어서(split) 개수만큼 반복한다.

여러개의 필드에서 해당 키워드를 찾기 때문에 OR로 연결한다.

즉, 작성자(brdwriter)에 키워드가 있거나(OR)  글 제목(brdsubject)에 키워드가 있는(OR ...)  문서를 찾는 것이 된다.

중요: 기본 검색식 brdsubject:JS는 위 코드에서 마지막 else 문에 있는 코드이다.

        queryStr += " " + fields[i] + ":" + keyword;

앞서 사용한 if 문들은 nested 처리, 즉, 하나의 게시글에 대한 댓글, 첨부 파일 같은 자식글 검색을 위한 코드로

댓글, 첨부 파일 같은 자식글에서 검색이 되더라도 부모글이 조회되어야 하기 때문에 다음과 같이 별도의 코드를 사용한다.

      {!parent which="brdtype:1"}rememo:키워드

댓글(rememo)에 키워드가 검색되더라도 글종류(brdtype)가 1인 데이터를 조회하는 것으로

글 종류는 게시물 색인시 1로 저장하도록 작성해 두었다.

 

이렇게 작성 한 makeQuery()함수를 호출하는 검색식의 메인 부분은 다음 코드이다.

String searchKeyword = searchVO.getSearchKeyword();

String[] fields = searchVO.getSearchRange().split(",");	 			// 검색 대상 필드 - 작성자, 제목, 내용 등
String[] words  = searchKeyword.split(" ");
String queryStr = "";

for (int i=0; i<words.length; i++) {
	queryStr += makeQuery(words[i], fields);
}

String searchType = searchVO.getSearchType();
if (searchType!=null & !"".equals(searchType)) {
	queryStr = "(" + queryStr + ") AND bgno:" + searchType;  
}

if (!"a".equals(searchVO.getSearchTerm())) { // 기간 검색		
	queryStr = "(" + queryStr + ") AND brddate:[" + searchVO.getSearchTerm1() + " TO " + searchVO.getSearchTerm2() + "]";  
}

SearchCtr.java

사용자가 입력하는 키워드(searchKeyword)는 공백으로 구분되어 여러 개가 입력될 수 있기 때문에 searchKeyword.split(" ")로 배열을 생성해서, 개수 만큼(words) Solr 검색식을 만든다(makeQuery).

여러 개의 키워드를 입력한 경우도 OR 연산으로 처리하였다.

한 필드에 A 키워드가 있거나 B 키워드가 있으면 조회하도록 한 것이다.

개발하는 조건에 따라 AND로 구현 할 수 있다.

 

사용자가 입력한 키워드(searchKeyword)와 검색할 필드(searchRange)를 이용해 기본 검색식을 생성하고

날짜(searchTerm)에 대한 조건을 지정할 수도 있고

통합 검색을 흉내내기 위해 게시판 종류(searchType)를 지정할 수 있다.

다만, 기본식은 OR로 연결했지만 날짜와 게시판 종류는 AND로 조회한다.

예를 들면, 게시판 종류가 공지사항이고(AND) 날짜는 3월 1일부터 3월 14일까지의 데이터 중에서(AND)

글제목에 해당 키워드가 있거나 글내용에 해당 키워드가 있는 문서를 조회하기 때문이다.

 

다음 부분은 검색식을 Solr로 보내는 SolrJ 코드이다.

SolrQuery solrQuery = new SolrQuery();
solrQuery.setQuery(queryStr);
solrQuery.setFields("");
solrQuery.addSort("id", ORDER.desc);								// 정렬
solrQuery.setStart( (searchVO.getPage()-1) * DISPLAY_COUNT);		// 페이징
solrQuery.setRows(DISPLAY_COUNT); 
solrQuery.setFacet(true);											// 합계
solrQuery.addFacetField("bgno");
solrQuery.setParam("hl.fl", "brdwriter, brdtitle, brdmemo");		// 하이라이팅
solrQuery.setHighlight(true).setHighlightSnippets(1);
logger.info(solrQuery.toString());

// 실제 조회
QueryResponse queryResponse = null;
SolrClient solrClient = new HttpSolrClient.Builder("http://localhost:8983/solr/project9").build();
try {
	queryResponse = solrClient.query(solrQuery);
	solrClient.close(); 

	logger.info(queryResponse.toString());
} catch (SolrServerException | IOException e) {
	logger.error("solrQuery error");
}

SearchCtr.java

정렬, 페이징 처리, 하이라이팅할 필드 등을 지정하고 개수를 집계(facet)할 필드를 지정한다.

facet는 통합 검색 기능을 위한 것으로 게시판 종류(bgno)별로 총 개수를 반환한다.

 

Solr 서버에 접속하고(SolrClient), 작성한 검색식을(solrQuery) 전송한다(질의한다-query).

실행 결과는 queryResponse에 Json(아닌 부분도 있다.) 형태로 반환된다.

 

마지막 부분은 검색 결과를 정리해서 Json 형태로 만드는 코드이다.

HashMap<String, Long> facetMap = new HashMap<String, Long>();
List<FacetField> ffList = queryResponse.getFacetFields();
for(FacetField ff : ffList){
    List<Count> counts = ff.getValues();
    for(Count c : counts){
        facetMap.put(c.getName(), c.getCount());
    }
}

//Map<String, Map<String, List<String>>> highlights = rsp1.getHighlighting();
HashMap<String, Object> resultMap = new HashMap<String, Object>();
resultMap.put("total", queryResponse.getResults().getNumFound());
resultMap.put("docs", JSONUtil.toJSON(queryResponse.getResults()).toString()) ;
resultMap.put("facet", facetMap) ;
resultMap.put("highlighting", queryResponse.getHighlighting()) ;

SearchCtr.java

반환된 queryResponse가 완전한 Json이면 처리 할 것이 없어서 그대로 JSP로 전달하면 좋은데,

아쉽게 조금 부족해서 조금의 처리를 해야 한다.

 

위 코드의 아래 부분에 있는 resultMap이 반환을 위한 JSon 작성부분으로

total은 검색된 전체 데이터 개수로 numFound 값으로 Solr에서 반환된다.

docs는 검색된 문서들로 5개씩(DISPLAY_COUNT) 반환되는데,  getResults()함수로 가져오고 기본적으로 Json이지만 쓸데 없는 코드가 있어서 JSONUtil.toJSON() 함수로 변환해서 사용한다.

facet는 게시판 종류별 개수로 어쩡쩡한 구조로 반환되기 때문에 위 코드 앞부분에서 조금 복잡한 변환 작업을 한다 (facetMap 변수 부분 참조).

highlighting는 Json으로 반환되기 때문에 그대로 전달한다.

 

다른 인터넷 자료에서는 반환 결과를 VO 개체에 넣는 등 복잡한 처리를 하지만,

JSP에서 Javascript로 쉽게 Json을 사용 할수 있기 때문에 여기서는 반환 데이터를 Json으로 그냥 전송한다.

 

JSP까지 정리하면 검색과 관계없는 코드로 너무 길어질 것 같아서 여기까지 정리한다.

이상의 예제는 게시글/댓글,파일에 대한 색인과 검색에 대한 것으로

글이 수정되거나 삭제 된 경우에 대한 것도 처리가 필요하다.

일부 구현하였지만 게시판 프로그램 자체를 수정해야 해서 모두 구현하지 않았다.

이런 부분까지 구현하면 실사용에서도 무난하게 사용할 수 있을 것이다.

 

 

 

오픈 소스 Lucene(루씬)을 기반으로 하는

대표적인 검색엔진인 Solr(쏠라)와 Elasticsearch(엘라스틱서치)는 Lucene을 기반으로 하지만 사용법은 전혀 다르다.

특히 데이터 수집(색인-Indexing)에 있어서, 

일반적인 문서 색인은 Solr가 쉽게 구현할 수 있고, 웹 로그와 같은 짧은 형태의 데이터는 Elasticsearch가 쉽게 구현할 수 있는 것 같다.

또, 검색(Search) 문법에 있어서 Solr는 Lucene의 문법을 그대로 따르고, Elasticsearch는 자신만의 문법을 구현한 차이가 있다.

이 두 가지 검색엔진의 차이를 이해하고, 

둘 다 익히기 위해 예제를 만드는 중으로 세부 기능을 구현하는데 시간이 많이 걸려 (언제 끝날지...)

일단 먼저 기본적인 색인 / 검색 기능을 구현하고 설치 / 사용하는 방법을 정리한다.

Solr와 Elasticsearch예제는 각각 Github 에서 다운로드 받을 수 있다.

    1. Solr 설치
    2. Solr 주요 설정과 코드
    3. Elasticsearch 설치
    4. Elasticsearch 주요 설정과 코드

 

먼저, Elasticsearch 예제 사용법을 정리한다.

예제는 그림과 같이 기존에 공유한 웹 프로젝트인 Project9에 검색 기능을 추가하는 형태로 작성했다.

Project9에 대한 설명은 기존 블로그에서 확인 할 수 있다.

Project9의 게시판 데이터를 대상으로 하고,

공지사항과 일반 게시판의 게시판 종류를 데이터 종류로 구분해서 통합 검색 기능을 구현하였다.

 

설치 방법은 Elasticsearch설치와 Project9 설치로 구분된다.

Elasticsearch 설치

  1. 다운로드 및 설치 
  2. 실행
  3. 형태소 분석기 설치
  4. 사전 복사
  5. 저장소 생성

Project9 설치

    소스 다운로드 및 설치

참고: Project9 설치는 기존 방식과 동일하기 때문에 여기에서 별도로 정리하지 않는다.

 

Elasticsearch  설치

 

1. Elasticsearch 다운로드 및 설치 

Elasticsearch 다운로드 사이트에서 운영체제에 맞는 압축 파일을 다운로드 받아서, 압축을 해제 한다.

 

2. Elasticsearch 실행

압축을 해제한 폴더에서 bin 폴더에 있는 elasticsearch.sh(Linux)나 elasticsearch.bat (윈도우)를 실행한다.

그림과 같이 started가 보이면 잘 실행 된 것이다.

 

주의: Elasticsearch는 JDK 1.8에서도 실행되지만, JDK 11 이상에서 실행하도록 강력하게 추천한다.

그리고, jdk 폴더에 JDK 13 버전이 포함되어서 배포된다.

JAVA_HOME이 설정되지 않은 경우, 이 JDK 13에서 실행된다.

 

3. 형태소 분석기 설치

콘솔창(cmd)에서 Elasticsearch가 설치된 경로(bin)로 이동한 후,

다음 명령어를 실행해서 Elasticsearch에서 기본적으로 제공하는 형태소 분석기 nori를 설치한다.

            elasticsearch-plugin install analysis-nori

 

4. 사전 복사

비었지만 나중에 사용할 사용자 사전(userdict.txt), 불용어 사전(stopwords.txt), 유의어 사전(synonyms.txt) 파일을 복사한다.

저장소 생성시 지정해 두었기 때문에 복사해야 한다.

사전 파일과 저장소 정보를 지정한 파일을 얻기 위해서는 Project9 Elasticsearch 예제 파일을 먼저 다운로드 받아야 한다. 

문서 작성의 단계상 다음 단계에 하는 것으로 정리했지만, Project9의 Elasticsearch 예제 소스를 다음과 같이 다운로드 받는다.

또는, Eclipse에서 다운로드 받는 것이 편리하고, Eclipse로 git 소스를 다운로드 받는 것은 기존 블로그를 참고하면 된다.

        git clone https://github.com/gujc71/Project9_es.git

 

다운 받은 Project9 소스 폴더에서 project9_es\elasticsearch에 있는 3개의 사전 파일을 [elasticsearch설치경로]/config 폴더에 복사한다.

 

5. 저장소(Index) 생성

앞서 설치한 형태소 분석기와 사전을 Elasticsearch에 인식시키기 위해 Elasticsearch를 다시 실행한다.

저장소 구성 정보를 저장한 index_board.json 파일이 있는 project9_es 설치 경로로 이동해서

다음 명령어를 실행한다.

          curl -XPUT localhost:9200/project9 -d @index_board.json -H "Content-Type: application/json"

project9이라는 저장소(index)를 생성하고,

index_board.json 파일에 작성된 데로 저장 구조(Schema)를 생성한다.

 

Project9 설치

 

Project9의 설치는 기존에 공개한 웹 프로젝트로 기존 블로그에 정리되어 있다.

이 내용과 동일하고, Github 프로젝트 명만 project9이 아닌 project9_solr로 바꾸어 설치하면 된다.

그리고, 적당한 계정으로 로그인해서 [통합검색] 메뉴에서 다양한 검색 결과를 확인할 수 있다.

 

주의: Project9의 Elasticsearch 웹 예제를 실행하면, 1 분간격으로 데이터를 수집하도록 되어 있다.

Elasticsearch가 실행되어 있지 않으면 오류가 발생할 수 있고,

너무 빨리 통합 검색을 실행하면, 실행 결과가 없을 수 있다.

콘솔 창에서 Project9의 실행 로그 중에 데이터 수집 결과가 보이거나 조금 시간이 지난 뒤에 검색을 해야 한다.

 

이상으로 예제를 실행하는 방법을 정리하였고

데이터를 수집해서 색인하고, 색인한 데이터를 프로그램에서 조회하는 주요 부분에 대한 코드 정리는 다음 블로그에서 정리한다.

 

검색 서버를 구축 하려면 필요한 사항이 많지만, 가장 중요한 부분은 색인(Indexing)과 검색(Search)이다.

Elasticsearch에서는 색인과 검색을 모두 Java로 개발했다.

(Solr에서는 DIH - Data Import Handler를 이용하여 SQL문만 작성하여 색인한다.)

Logtash라고 데이터를 수집하는 별도의 프로그램이 있지만,

RDBMS의 부모 / 자식 관계, 첨부 파일 색인 처리에 어려움이 많아서 직접 개발하였다.

Logtash는 로그 데이터 수집에는 편리했지만 그 외에는 불편한 것이 많았다.

그래서 Log 가 붙은 것인지…

 

모든 기능을 아직 구현하지 않았고 (기약할 수 없지만 구현 중),

구현 방법을 모두 여기에 정리 할 수 없어서, 색인과 검색 개발에 관련된 몇 가지 주요한 사항만 정리한다.

주의: 용어나 옵션에 대한 상세한 설명을 정리하지 않기 때문에 Elasticsearch를 잘 모른다면 다음 내용이 어려울 수 있다.

    1. Solr 설치
    2. Solr 주요 설정과 코드
    3. Elasticsearch 설치
    4. Elasticsearch 주요 설정과 코드

 

정리할 주요 내용은 다음 그림과 같다.

 

색인

색인 작업은 Elasticsearch 검색 서버가 처리하는 것이고,

색인과 관련해서 구현해야 하는 것은 데이터를 수집해서 Elasticsearch 검색 서버에 전달하는 것이다(IndexingCtr.java).

데이터 수집 작업은 수집한 데이터를 저장하는 공간(저장소-Index)에 대한 정의와 데이터를 수집하는 방법에 대한 정의로 구분된다.

 

색인할 데이터를 저장할 저장소(Index)에 대한 정의는 다운받은 Project9 프로젝트의 project9_es 폴더에 있는 index_board.json 파일에 있다.

Elasticsearch 설치 과정에서 이 파일을 실행해서 스키마를 정의하였다.

이것은 Json 계층 구조로 작성하고, 가장 최상위는 다음과 같다.

{
  "settings": {
    "number_of_shards" : 1,
    "number_of_replicas": 0,  
    "index": {...}
  },
  "mappings": {
      "properties": {...}
  } 
}

index_board.json

최상위는 저장소에 대한 설정(settings)과 구조(mappings)로,

설정은(settings) 샤드(number_of_shards) 개수, 복제(number_of_replicas) 개수, 색인(index) 규칙에 대해서 정의하고,

구조(mappings)는 속성키(properties) 하위에 색인해서 저장하는 필드들에 대해서 정의한다.

 

설정(settings)의 색인(index)에서는 형태소 분석기(노리-Nori) 사용에 대한 설정을 하고,

복합어 처리(decompound_mode), 필터, 불용어 처리 등을 지정한다.

이 중에서 nori_analyzer는 형태소 분석기(노리-Nori) 사용에 대한 설정으로,

설정(settings)의 색인(index) 관련 부분을 모두 모아서 지정한다.

"analyzer": {
  "nori_analyzer": {
	"type": "custom",
	"tokenizer": "nori_user_dict",
	"char_filter": ["html_strip"],            
	"filter": [
	  "nori_posfilter", "stop_filtering", "lowercase", "stemmer", "nori_readingform", "synonym_filter"
	]
  }
},

index_board.json

내용에서 HTML 태그는 제거하고(html_strip),

품사로 불용어를 제거하거나(nori_posfilter) 불용어 사전으로 제거하는 (stop_filtering)등의 색인하는 과정을 정의한다.

보다 자세한 것은 인터넷 자료를 참고하고 여기서는 넘어간다.

 

구조(mappings)는 속성(properties)만 있고, 그 하위에는 저장하는 필드들에 대해서 정의한다.

"bgno": {"type": "long"},
"brdno": {"type": "keyword"},
"brddate": {"type": "date"},
생략 ~~    
"brdtitle": {"type": "text","analyzer":"nori_analyzer"},
"brdmemo": {"type": "text","analyzer":"nori_analyzer"},
"brdreply": {
    "type": "nested",
    "properties": {
        "reno": {"type": "text"} ,
        "redate": {"type": "text"} ,
        "rememo": {"type": "text", "analyzer":"nori_analyzer"} ,
        "usernm": {"type": "text"} ,
        "userno": {"type": "text"} 
    }
생략 ~~    

index_board.json

필드 타입(type)이 long, keyword, date 형 등은 값 그대로 색인하는 필드이고,

nori_analyzer로 지정된 필드는 앞서 정의한 nori_analyzer 과정데로 형태소 분석을 하고 색인한다.

댓글(brdreply)과 첨부 파일(brdfiles)은 RDBMS의 부모/자식 관계에 있는 데이터를 저장하기 위한 구조로 nested 형으로 지정한다.

nested형으로 지정된 데이터는 문서내의 문서로 배열처럼 저장된다.

문서내의 문서이기 때문에 속성(properties)키 하위에 reno, rememo등 다시 문서에 대한 필드 정의를 한다.

개인적으로 Solr보다 직관적이고 사용하기 쉬운 것 같다.

 

데이터를 수집하는 부분은 Solr의 경우 DIH로 개발자는 SQL문만 작성하면 되지만 (Solr의 장점?)

Elasticsearch에서는 Elasticsearch가 제공하는 Java High Level REST Client로 색인을 구현한다.

gu.search.IndexingCtr에 다음과 같은 구조로 작성한다.

@Scheduled(cron="0 */1 * * * ?")
public void indexingFile() {
    RestHighLevelClient client = createConnection();

    게시글 이전 색인시 마지막 번호 로드
    
    색인할 게시글 리스트 
    게시글을 Json으로 작성후 색인 - INSERT

    마지막 게시글 번호 저장

    ------------------        
    댓글 이전 색인시 마지막 번호 로드
    
    색인할 댓글 리스트 
    댓글을 Json으로 작성후 색인 - UPDATE

    마지막 댓글 번호 저장

    ------------------        
    첨부파일 이전 색인시 마지막 번호 로드
    
    색인할 첨부파일 리스트 
    첨부파일에서 텍스트 추출(Tika)
    첨부파일 정보를 Json으로 작성후 색인 - UPDATE

    마지막 첨부파일 번호 저장
}

IndexingCtr.java

게시글, 댓글, 첨부파일  모두 동일한 방식으로 작성하였고,

첨부파일은  파일에서 색인할 텍스트를 추출하는 Tika를 사용한다.

셋 모두 이전에 마지막으로 색인한 글 번호 (Pk-Primary Key) 이후의 데이터들을 조회한다.

Solr에서는 날짜(brddat)로 구현했지만, Elasticsearch에서는 글번호로 구현했다.

고유한 값을 가지는 필드면 어떤 것이라도 사용할 수 있다.

마지막 색인 값으로 마지막으로 색인한 이후의 증가된 데이터만 색인하고,

마지막 색인 값이 없는(NULL) 경우는 모든 데이터가 대상이 되어 풀색인을 한다.

SQL문을 그렇게 하도록 다음과 같이 작성하였다.

<select id="selectBoards4Indexing" resultType="gu.board.BoardVO" parameterType="String">
    SELECT BGNO, BRDNO, CU.USERNM, TB.USERNO, BRDTITLE, BRDMEMO
           , LEFT(BRDDATE, 10) BRDDATE, RIGHT(BRDDATE, 8) BRDTIME 
     FROM TBL_BOARD TB
     INNER JOIN COM_USER CU ON CU.USERNO=TB.USERNO 
    WHERE TB.BRDDELETEFLAG='N' 
       AND BRDNO > #{brdno}
    ORDER BY BRDNO 
      LIMIT 20
</select>
    

board.xml

추출한 색인 대상들은 Java의 VO개체에 담겨서 동적 배열(List)형태로 관리되는데,

List boardlist = (List) boardSvc.selectBoards4Indexing(brdno);
List replylist  = (List) boardSvc.selectBoardReply4Indexing(lastVO);
List filelist     = (List) boardSvc.selectBoardFiles4Indexing(lastVO);

IndexingCtr.java

반환 된 개수 만큼 반복하면서 (for), VO에 있는 필드의 값을 Json 형태로(키:값) 저장한다.

게시판은 source로 지정해서 작성했고,

IndexRequest indexRequest = new IndexRequest(INDEX_NAME)
      .id(el.getBrdno())
      .source("bgno", el.getBgno(),
              "brdno", brdno,
              "brdtitle", el.getBrdtitle(),
              "brdmemo", el.getBrdmemo(),
              "brdwriter", el.getUsernm(),
              "userno", el.getUserno(),
              "brddate", el.getBrddate(),
              "brdtime", el.getBrdtime()
              ); 

IndexingCtr.java

댓글과 첨부 파일은 Map 구조로 작성했다.

            Map<String, Object> replyMap = new HashMap<String, Object>();
            replyMap.put("reno", reno);
            replyMap.put("redate", el.getRedate());
            replyMap.put("rememo", el.getRememo());
            replyMap.put("usernm", el.getUsernm());
            replyMap.put("userno", el.getUserno());

            Map<String, Object> singletonMap = Collections.singletonMap("reply", replyMap);

UpdateRequest updateRequest = new UpdateRequest()
             .index(INDEX_NAME)
             .id(el.getBrdno())
             .script(new Script(ScriptType.INLINE, "painless", 생략~

IndexingCtr.java

모두 키와 값으로 구성된 Json 방식으로 둘 중 어느 방식을 사용해도 된다.

 

가장 중요한 처리로 게시글은 IndexRequest로 색인하고(저장하고), 댓글과 첨부 파일은 UpdateRequest로 색인한다.

게시글을 먼저 처리해서 저장하고(Insert)

댓글과 첨부 파일은 해당 게시글을 찾아서 수정하는(Update) 방식으로 구현한다.

 

더우기 RDBMS의 부모/자식, 1 : n (일대다)의 관계,

즉 하나의 게시물이 여러개의 댓글과 첨부 파일을 가지기 때문에 다음과 같이 다소 복잡한 코드로 저장한다.

.script(new Script(ScriptType.INLINE, "painless",

      "if (ctx._source.brdreply == null) {ctx._source.brdreply=[]} ctx._source.brdreply.add(params.reply)", singletonMap));

painless라는 Elasticsearch에서 제공하는 스크립트 언어로

게시물 문서에서 brdreply 라는 필드를 배열( [ ] )로 초기화하고,

생성된 댓글이나 첨부 파일 하나(Json)를 다음과 같이 배열에 추가(add)하는 방식으로 작성한다.

{
                "_index": "project9",
                "_type": "_doc",
                "_id": "38",
                "_score": 1,
                "_source": {
                    "bgno": "3",
                    "brdno": "38",
                    "brdtitle": "거침없이 배우는 Jboss",
                    "brdmemo": "거침없이 배우는 Jboss",
                    "brdwriter": "이종무",
                    "userno": "24",
                    "brddate": "2020-03-01",
                    "brdtime": "17:42:42",
                    "brdreply": [
                        {
                            "reno": "1",
                            "usernm": "관리자",
                            "redate": "2020-03-08 09:43:27.0",
                            "rememo": "윈도우",
                            "userno": "1"
                        }
                    ]
                }
},

이 코드 덕분인지 Solr보다 직관적인 모습으로 색인되어 저장된다 (Solr Nested 참고).

 

이렇게 작성된 코드는 스프링 스케쥴러를 이용해서 1분마다 실행되게 작성했다.

            @Scheduled(cron="0 */1 * * * ?")

실제로 사용할 경우에는 사용자가 없는 새벽 1시에 일괄 색인하는 경우가 많다.

웹 브라우저에서 게시판을 접속해서 게시글을 추가하거나 댓글을 작성하면 잠시 뒤에 검색된다.

 

검색

 

검색도 기존 Prohect9 프로젝트에 구현되어 있으며

하나의 Java파일(gu.search.SearchCtr)과 JSP(search.jsp) 파일로 작성하였다.

search.jsp 파일은 검색 결과를 Json으로 받아서 적절하게 보여주는 기능을 하고, 여기서 정리하지 않는다.

SearchCtr.java 파일은 High Level REST Client를 기반으로 작성하였고, 

검색식을 만드는 부분과 Elasticsearch에 질의하는 부분으로 구분할 수 있다.

반환 된 결과는 Json으로 작성되어 있어서 그데로 jsp로 전달하고 종료한다.

 

검색식을 만드는 기본 코드는 다음과 같이 makeQuery()함수에서 작성한다.

private BoolQueryBuilder makeQuery (String[] fields, String[] words, FullTextSearchVO searchVO) {
	BoolQueryBuilder qb = QueryBuilders.boolQuery();
	
	String searchType = searchVO.getSearchType();
	if (searchType!=null & !"".equals(searchType)) {
		qb.must(QueryBuilders.termQuery("bgno", searchType));
	}
	
	if (!"a".equals(searchVO.getSearchTerm())) {							// 기간 검색
		qb.must(QueryBuilders.rangeQuery("brddate").from( searchVO.getSearchTerm1()).to( searchVO.getSearchTerm2()) );	
	}

	for( String word : words) {			// 검색 키워드
		word = word.trim().toLowerCase();
		if ("".equals(word)) continue;
		
		BoolQueryBuilder qb1 = QueryBuilders.boolQuery();
		for( String fld : fields) {							// 입력한 키워드가 지정된 모든 필드에 있는지 조회
			if ("brdreply".equals(fld)) {		// 댓글은 nested로 저장되어 있어 별도로 작성
				 qb1.should(QueryBuilders.nestedQuery("brdreply", QueryBuilders.boolQuery().must(QueryBuilders.termQuery("brdreply.rememo", word)), ScoreMode.None));
			} else 
			if ("brdfiles".equals(fld)) {		// 첨부 파일은  nested로 저장되어 있어 별도로 작성
				 qb1.should(QueryBuilders.nestedQuery("brdfiles", QueryBuilders.boolQuery().must(QueryBuilders.termQuery("brdfiles.filememo", word)), ScoreMode.None));
			} else {
				qb1.should(QueryBuilders.boolQuery().must(QueryBuilders.termQuery(fld, word)));
			}
		}
		
		qb.must(qb1);			// 검색 키워드가 여러개일 경우 and로 검색
	}
	
	return qb;
}

SearchCtr.java

Elasticsearch이 기본적인 검색은 필드:키워드 구조로 Solr와 유사하지만,

다음과 같이 보다 더 Json 구조로 직관적인 구조로 작성한다.

{  
   "query":{  
      "match":{  
         "brdtitle":"js"
      }
   }
}

Solr는 필드:키워드가 and, or, ()로 표현되자만,

Elasticsearch은 위와 같은  Json 구조에 must(and), should(or), []로 표현된다.

따라서 위 코드의 마지막 Else문이 기본 검색으로, 이상의 json을 표현한 것이다.

    qb1.should(QueryBuilders.boolQuery().must(QueryBuilders.termQuery(fld, word)));

댓글(brdreply)과 첨부파일(brdfiles)은 해당 필드에 값이 있으면 게시글이 조회 되어야 하기 때문에 nestedQuery 를 이용해서 따로 작성한다.

QueryBuilders.nestedQuery("brdreply", 
           QueryBuilders.boolQuery().must(QueryBuilders.termQuery("brdreply.rememo", word)), ScoreMode.None)
QueryBuilders.nestedQuery("brdfiles",
           QueryBuilders.boolQuery().must(QueryBuilders.termQuery("brdfiles.filememo", word)), ScoreMode.None)

사용자가 여러개의 키워드를 입력할 수 있기 때문에 공백 단위로 잘라서 개수 만큼 반복하고

이 키워드들을 조회할 필드들 개수 만큼 반복해서 검색식을 만들기 때문에 두개의 반복문을 실행한다.

for ( String word : words) {
     for ( String fld : fields) {

날짜를 기간으로 검색하기 위해 rangeQuery를 사용하고,

공지사항과 일반 게시판 중 하나를 선택해서 조회할 수 있도록 게시판 종류(bgno)를 지정할 수 있게 하였다.

 

SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.highlighter( makeHighlightField ( searchRange ) );		// 검색 대상 필드에 대해서 하이라이트(댓글, 첨부파일은 제외)
searchSourceBuilder.fetchSource(INCLUDE_FIELDS, null);
searchSourceBuilder.from( (searchVO.getPage()-1) * DISPLAY_COUNT);							// 페이징
searchSourceBuilder.size(DISPLAY_COUNT); 
searchSourceBuilder.sort(new FieldSortBuilder("_score").order(SortOrder.DESC));				// 정렬
searchSourceBuilder.sort(new FieldSortBuilder("brdno").order(SortOrder.DESC));

TermsAggregationBuilder aggregation = AggregationBuilders.terms("gujc").field("bgno");		// 그룹별(게시판) 개수
searchSourceBuilder.aggregation(aggregation);

searchSourceBuilder.query( makeQuery ( searchRange, searchVO.getSearchKeyword().split(" "),  searchVO ));	// 검색식 작성

SearchRequest searchRequest = new SearchRequest();
searchRequest.indices(INDEX_NAME);
searchRequest.source(searchSourceBuilder);

RestHighLevelClient client = null;
SearchResponse searchResponse = null;

client =  createConnection();
searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);

SearchCtr.java

이렇게 작성한 검색식은 query로 실행하지만

하이라이팅(highlighter), 페이징(from, size), 정렬(sort)의 설정을 추가해서 실행한다.

특히, 다양한 데이터를 조회하는 통합 검색 기능을 흉내내기 위해

여기서는 공지사항과 일반 게시판의 게시판 종류(bgno) 기능을 구현하였고,

게시판 종류(bgno)별 개수를 확인하기 위해 Elasticsearch에서 제공하는 집계(aggregation) 기능을 이용한다.

TermsAggregationBuilder aggregation = AggregationBuilders.terms("gujc").field("bgno"); // 그룹별(게시판) 개수
searchSourceBuilder.aggregation(aggregation);

이렇게 실행된 결과는 Json으로 반환되고, 이 값은 그대로 JSP로 전달해서 처리한다.

 

JavaScript가 Json을 쉽게 사용할 수 있기 때문이지만,

JSP까지 정리하면 검색과 관계없는 코드로 너무 길어질 것 같아서 여기까지 정리한다.

 

이상의 예제는 게시글/댓글,파일에 대한 색인과 검색에 대한 것으로

글이 수정되거나 삭제 된 경우에 대한 것도 처리가 필요하다.

게시판 프로그램 자체를 수정해야 해서 구현하지 않았다.

이런 부분까지 구현하면 실사용에서도 무난하게 사용할 수 있을 것이다.

 

 

 

 

 

구글에서 제공하는 챗봇 플랫폼인 Dialogflow는 챗봇을 쉽게 만들수 있도록 도와준다.

Dialogflow 가이드 문서와 인터넷 검색을 통해 다양한 예제와 설명을 구할 수 있지만

처음 시작하는 사람에게는 조금 부담되는 개념들이 많다.

더우기, 대부분의 블로그나 설명이 챗봇 생성을 따라 하면서 익히도록 되어 있는데 ,

여기에서는 챗봇 생성을 따라 하면서 익히는 것이 아니고

구글이 제공하는 챗봇을 실행 시켜보면서 전체 개념을 잡을 수 있도록 정리하였다.

간단하게 체험하면서 전체 개념을 머릿속에 그려 보고, 인터넷 자료나 서적을 보면 좀 더 쉽게 접근 할 수 있을 것이다.

 

구글에서는 호텔 방 예약 예제를 제공하고, 사용법은 다음과 같이 단순하다.

다음 문장은 구글 [API로 인텐트 관리]에 있는 문장을 그대로 가져온 것이다.

에이전트로 예제 파일 가져오기

가져오기를 수행하면 에이전트에 인텐트와 개체가 추가됩니다. 기존 인텐트 또는 개체가 가져온 파일에 있는 인텐트 또는 개체와 이름이 같으면 대체됩니다.

파일을 가져오려면 다음 단계를 따르세요.

    1. RoomReservation.zip 파일을 다운로드합니다.

    2. Dialogflow 콘솔로 이동합니다.

    3. 에이전트를 선택합니다.

    4. 에이전트 이름 옆의 설정 settings 버튼을 클릭합니다.

    5. Export and Import(내보내기 및 가져오기) 탭을 선택합니다.

    6. ZIP 파일에서 가져오기를 선택하고 다운로드한 ZIP 파일을 가져옵니다.

Dialogflow를 사용해 본 사람은 위 순서를 따라서 쉽게 실행해 볼 수 있다.

여기에서는 처음 사용하는 사람을 위해 위 내용을 그림과 함께 조금 더 상세하게 정리하였다.

영어라 조금 아쉽지만, 영어로 제작하나  한글로 제작하나 기본적인 사용법은 동일하다.

 

먼저, Dialogflow에 접속한다.

화면 중앙에 있는 [Sign up for free]을 눌러서 회원 가입을 한다.

다음과 같이 구글 Gmail 계정으로(Sign-in with Google) 한번에 가입할 수 있다.

 

 

 

회원 가입후 로그인 하면 시작 화면은 다음과 같다.

챗봇을 만드는 하나의 프로젝트를 Agent라고 하는데,

챗봇 생성을 위해 왼쪽 메뉴에서 [Create Agent]를 눌러서 새로운 Agent를 생성한다.

 

챗봇 Agent 이름을 RoomReservation으로 입력하고, 생성(Create) 버튼을 클릭한다.

언어는 영어가 기본 설정인데, 예제가 영어라 그냥 넘어간다.

 

조금의 시간이 흐른뒤, RoomReservation이 생성된다.

왼쪽 메뉴에서 톱니 바퀴 모양의 설정 아이콘을 클릭한다.

참고: Agent가 생성되면서 왼쪽 메뉴에 Intents가 기본 선택되고,

화면 중앙에는 기본 제공되는 두개의 Intents가 생성된다. 

하나는 사용자의 말을 이해하지 못했을 때, 다시 말해달라는 것이고 (Default Fallback Intent)

다른 하나는 대화를 시작하면서 환영 메시지를 출력하기 위한 것이다 (Default Welcome Intent).

챗봇은 이 Intent의 집합 또는 흐름이라고 볼 수 있다.

 

[Export and Import] 탭에서 [IMPORT FROM ZIP] 버튼을 클릭한다.

 

예제 파일 다운로드는 구글 [API로 인텐트 관리]에서 다운로드 받거나

호텔 예제 파일 다운로드를 클릭해서 RoomReservation.zip 파일을 직접 다운로드 받는다.

 

다음과 같이 파일 업로드 팝업 창에서 다운로드 받은 호텔 예약 챗봇 예제 파일(RoomReservation.zip)을 추가한다.

주의 : 하단 입력창에 "IMPORT"라고 입력하지 않으면 [IMPORT] 버튼이 그림과 같이 활성화 되지 않는다.

[IMPORT] 버튼을 클릭해서 가져오기 작업을 마무리한다.

 

가져오기 작업을 한 후, 왼쪽에 있는 Intents 메뉴를 클릭하면 추가된 Intents을 볼 수 있다.

Intents에 대한 설명은 따로 정리되어 있다.

여기서는 우측에 있는 마이크의 입력창[Try it now]에서 테스트를 시작한다.

호텔방 예약 챗봇을 실행해 본다.

 

예의바르게 인사부터 하면 (hi), 챗봇이 자신의 역할에 대해서 소개한다 (왼쪽).

내가 입력한 문장은 [USER SAYS]에 출력되고, 챗봇의 대답은 [DEFAULT RESPONSE]에 출력된다.

예약하께(reserve a meeting room)라고 하니, 언제(What date?)라고 챗봇이 대답한다 (위 그림 중앙).

내일(tomorrow)이라고 하자 몇시냐고(What time will the meeting start)라고 묻는다 (위 그림 오른쪽).

 

3시에(3pm) 예약한다고 하자,  며칠 있을 거냐고(How long will it last?)라고 묻고 (아래 그림 왼쪽)

3일이라고(3day) 하자, 몇명이 참석하냐고 물어본다 (How many people are attending?) (아래 그림 중앙).

3명이라고(3) 하자, 방을 고르라고(Choose a room please) 한다 (아래 그림 오른쪽).

 

스위트 룸(A)라고 하자, 예약에 대한 총 상황을 설명하고 맞는지 다시(confirm below) 물어본다 (아래 그림 왼쪽).

맞다고 하니(OK), 예약이 완료(All set!) 된다 (아래 그림 오른쪽).

이상으로 챗봇으로 간단하게 호텔 예약을 진행했다.

테스트 대화창에서 좀더 대화를 하면서 실행창의 [DEFAULT RESPONSE] 하단의 CONTEXTS, INTENT, ACTION 등을 잘 살펴 보기 바란다.

 

이상의 방법은 개발하는 과정에 대화하는 테스트를 정리한 것이고,

이것을 실제 챗봇처럼 기존 SNS에 연동해서 서비스 할 수 있다.

Integrations 메뉴를 선택하면 다양한 연동 리스트들이 출력되는데,

여기에서는 [Web Demo]를 선택해서 간단하게 테스트 한다.

연동 방법등을 정리하는 것은 이 글의 범위를 벗어나므로, 다른 자료를 참고 하면 된다.

 

[Web Demo]를 선택하면, 생성한 챗봇을 다른 웹 사이트등에서 사용할 수 있는 URL등이 제공된다.

URL을 클릭하거나, 복사해서 웹 브라우저에서 실행하면

다음 그림과 같이 그럴듯한 채팅창이 실행된다.

앞서의 대화를 그대로 진행하면서 테스트 할 수 있다.

 

단순한 클릭 몇 번으로 그럴 듯한 챗봇을 제작(?)해 봤다.

챗봇과 이런 저런 다른 대화를 나눠보면, 조금의 신기함과 답답함이 느껴질 수 있다.

이 챗봇을 만드는 방법에 대한 조금 더 상세한 설명은 다른 페이지에서 정리하였다.

 

 

 

 

'챗봇' 카테고리의 다른 글

5분만에 둘러보는 챗봇 만들기 - Dialogflow 조금 더  (1) 2020.03.01

앞서서 실행해 본 호텔 예약 챗봇 예제를 Intent와 Entity의 두 용어(메뉴)를 중심으로 정리하였다.

여기에서는 이 두 가지가 어떻게 구성되어 있는지(만드는지) 간단하게 정리하였다.

이 문서는 Dialogflow로 챗봇을 만드는 방법이 아니라 전체 개념과 몇 가지 용어에 익숙해지기 위해

앞서서 실행해 본 호텔 예약 챗봇 예제를 구성하는 요인에 대해서 정리한 문서이다.

 

Entity

먼저, Entity는 데이터 베이스의 데이터 타입와 비슷한 개념으로 개체 종류를 의미한다.

Entities 메뉴를 실행해 보면 [CUSTOM] 탭에 @room이라는 Entity가 등록되어 있다.

@room를 클릭하면 A, B, C가 등록되어 있다.

A, B, C는 싱글룸, 더블룸, 스위트룸 같은 호텔 방 종류가 room이라는 개체(Entity)로 정의되어 있다.

하단의 [Add a row]를 눌러서 추가할 수 있다.

 

다시 Entities 메뉴를 실행하고, 이번에는 [SYSTEM] 탭을 선택한다.

[CUSTOM]은 사용자가 등록한 개체고,

[SYSTEM]은 Dialogflow가 제공하는 것으로 숫자(number), 날짜(date), 위치(location) 등의 개체들이 정의 되어 있다.

 

Intent

왼쪽 메뉴에서 Intents를 선택한다.

Dialogflow가 기본 제공하는 2개 intent와 가져오기로 추가된 1개의 intent가 있다.

 

1. Default Fallback Intent

이 리스트에서 [Default Fallback Intent]를 클릭해 보면,

대부분 빈 내용이고 하단에 있는 [Response](대답)에 무슨 말인지 모르겠으니 다시 말해달라는 문장이 다양하게 적혀있다.

말 그대로 고객의 말을 못 알아 들었을 때, 

사용하는 문장들로 하나만 사용하면 지겨워서(?) 여러 가지를 등록한다고 한다.

 

2. Default Welcome Intent

Intents 리스트에서 [Default Welcome Intent]를 선택하면,

Training phrases(학습문장)과 Response(대답)만 채워져 있고 다른 부분은 비어있다.

[Add user expression]로 표시된 입력창에서 문장을 추가 할 수 있다.

Training phrases(학습문장)은 고객이 입력하는 문장으로,

고객이 이상과 같이 입력하면(Training phrases), 다음과 같이 Response(대답)하게 된다.

Response(대답)에서는 [Enter a text response variant]에 새로운 문장을 입력해서 추가한다.

내용을 추가하거나 수정 한 뒤에는 우측 상단의 [SAVE]버튼을 눌러서 저장한다.

 

3. room.reservation

가장 중요한 Inent인 실제 예약 (room.reservation)을 클릭해서 앞서의 Inent처럼 상세 내용을 보거나,

∧와 ∨ 아이콘을 눌러서 하위 Inent을 확인 할 수 있다.

room.reservation 하위에 4개의 Intent가 더 있다.

 

이 구조를 다르게 표현하면 다음과 같다.

예약에 필요한 위치(location), 날짜(date) 등의 정보를 대화를 통해 수집하고 (room.reservation),

호텔 방 종류를 선택하면 (room.reservation - choose room)

예약이 맞는지 확인하고 예약(yes)거나 취소(no, cancel)하는 구조이다.

 

방 선택과 예약 확인은 상세 내용을 보면 쉽게 이해 할 수 있지만

정보를 수집하기 위해 대화하는 과정은 이 예약 챗봇의 핵심으로 조금 복잡하고 어려울 수 있다.

 

리스트에서 room.reservation를 선택하면, 

핵심 부분이라 제법 많은 내용들이 출력되는데,

전체 구조는 Contexts, Event, Training phrases, Action and parameters, Responses, Fulfilment로 구성되어 있다.

이중에서  Event, Fulfilment는 여기에서 사용하지 안았다.

Contexts는 Intent의 순서들을 의미한다.

현재의 Intent를 실행하기 위해 먼저 실행해야 할 Intent와 (input context)

현재의 Intent를 실행한 뒤에 실행할  Intent(output context)을 지정한다.

즉, 앞서의 트리 구조를 지정하는 곳으로

예약 정보(room.reservation) 수집 전에는 먼저 실행할 Intent가 없고,

예약 정보가 수집되었으면 방선택(room.reservation - choose room) Intent을 실행한다.

(고객이 인사를 하면 환영인사 후에 실행 될 수 있고, 고객이 바로 예약을 진행 할 수 있다)

 

Training phrases(학습 문장)에는 예약과 관련 74개의 다양한 문장이 입력되어 있다.

Training phrases(학습 문장)은 고객이 하는 말이고,

Responses(대답)는 챗봇이 하는 말을 의미한다.

환영인사(Default Welcome Intent)에서는 이 두 대화 문장만 있었지만,

대화를 통해 정보를 수집하기 위한 Action and parameters가 작성되었다.

 

tomorrow와 같은 색상이 있는 단어를 클릭하면, 그림과 같은 메뉴가 나타난다.

tomorrow는 날짜(date)로 지정되어 있다.

내일 회의가 있어서 회의실을 예약 할거야(I have a meeting tomorrow let's book a conference hall)란 문장에서

내일(tomorrow)이 예약에 필요한 핵심 키워드로 추출되고, 내일(tomorrow)은 날짜형으로 보관된다.

사람은 날짜를 인식할 수 있지만, 챗봇은 알 수 없기 때문에 이렇게 미리 지정해 둔다.

 

다음 주 월요일 11시에 예약할거야(I need to book a meeting room on next Monday at 11 am)에서는

다음 주 월요일(next Monday)과 11시(11 am)가 지정되어 있다.

 

다른 문장들도 이렇게 지정되어 있으며, 같은 색상은 같은 타입을 의미하고

이렇게 예약에 필요한 위치(location), 날짜(date), 시간(time), 체류기간(duration), 인원수(guests)를 수집한다.

참고: 개인적으로는 이 부분의 대화 양이 챗봇의 성능을 판단하는 기준이 되는 것 같다. 얼마나 많은 대화가 잘 정리되어 등록되는 냐에 따라서 사람(?) 같이 대화하는 것 같다.

 

이상처럼 수집되는 정보들에 대한 정의 / 종류를 지정하는 부분이 Action and parameters이다.

수집해야 할 위치(location), 날짜(date), 시간(time), 체류기간(duration), 인원수(guests)에 대한 속성을 ENTITY로 지정한다. 

예로 인원수는 숫자(number)라고 지정하고,

3명과 같이 고객이 숫자를 입력하면 인원수를 입력한 것으로 처리한다.

 

해당 정보를 얻기 위해 챗봇이 해야할 질문은 PROMTS에 등록한다.

PROMTS를 클릭하면 다음과 같은 팝업창이 실행된다.

대화중에 3명이라고 이야기 한 경우에는 넘어가지만,

정보가 수집되지 않으면 "몇명이 묵을 겁니까?(How many people are attending)"라고 물어서 정보를 수집한다.

 

예약에 필요한 위치(location), 날짜(date), 시간(time), 체류기간(duration), 인원수(guests)를 수집했으면,

"방을 선택하세요(Choose a room please)"라고 응답하고(Resposes)

앞서 Context에서 output으로 지정한 [room.reservation - choose room] Intent로 넘어간다.

참고: 이상에서 PROMPT로 수집한 정보를 어떤 자료에서는 Intent로 만들어서 처리하기도 한다.

 

4. room.reservation - choose room

방 선택(room.reservation - choose room)은

그림처럼 room.reservation이 끝나면 실행되고(input context), 끝나면 확인 Intent(output context)가 실행된다.

학습 문장(Training phrases)에는 A, B, C만 등록되어 있다.

방 종류를 의미하고, 이외의 문장을 입력하면 방 선택을 하라고 한다.

 

방 종류(A, B, C)가 노란색으로 표시 되어 있고,

Action and parameters에도 @room만 노란색으로 활성화 되어 있다.

방 종류(A, B, C)는 @room 개체로 앞서서 Entity로 지정한 타입이라는 의미이다.

즉, 여기서는 방 종류에 대한 정보만 수집한다.

참고: room.reservation에서 Prompt로 처리한 것을 이렇게 Intent로 처리 할 수 있다.

 

방 종류를 선택하면 다음과 같이 수집한 정보를 채워서 고객에게 출력하고(Response)

다음 Intetnt로 넘어가 응답을 기다린다.

 

예약을 확인후 고객의 대답에 대한 처리는 정리하지 않는다.

이상과 같은 구조로 되어 있기 때문에 상세 내역을 확인하면 쉽게 이해 할 수 있을 것이다.

모두 고객의 예상되는 대답(Traning phrases)과 그것에 대한 챗봇의 대답(Response) 구조로 되어있다.

이 대화 속에 필요한 정보를 추출한다.

 

이상으로 챗봇 제작에 필요한 Entity와 Intent에 대해서 정리했다.

이 내용을 처음부터 하나 하나 생성하는 방법은

구글 Dialogflow 문서에서 [에이전트를 처음부터 빌드하기]를 따라하거나 다른 자료를 참고하면 된다.

이 문서는 만드는 방법이 아니라 전체 개념과 몇 가지 용어에 익숙해지기 위해 정리한 문서이다.

이 외에도 많은 예제가 제공되니 읽어보는 것이 도움이 될 것이다.

 

추가적으로 날짜를 yesterday(어제)를 입력해도 예약이 된다.

방 종류를 모르면 예약을 할 수 없다.

이외에도 많은 문제점이 있고,

이 문제점들은 프로그램과 연동이 필요한 것으로 Dialogflow만으로는 완벽한 챗봇을 만들 수 있는 것이 아니다.

챗봇을 만들기 위해 이제 첫 걸음을 내민 것이다.

 

 

 

 

 

 

 

 

 

 

'챗봇' 카테고리의 다른 글

5분만에 둘러보는 챗봇 만들기 - Dialogflow  (0) 2020.03.01

회사에서 오픈 소스 검색엔진인 Lucene을 기반으로 하는 Elasticsearch 도입을 진행하면서,

이전에 조금 다루었던 Solr (Lucene을 기반으로 하는 또 다른 검색엔진)를 개인적으로 정리 하고 있다.

둘 다 조금 부족한 형태소 분석기 (정확하게는 사전)를 이용하는데,

부족한 부분을 채우기 위해 이것 저것 시도하면서 찾은 데이터들 중에서 다른 이들에게도 중요 할만한 데이터들을 공유한다. 

특히, 검색 엔진, 형태소 분석기 등의 프로그램에 대한 자료는 많이 공개되어 있어서 쉽게 구할 수 있는데, 

이들을 제대로 사용할 수 있게 하는 사전에 대한 자료가 부족한 것 같다 (못 찾아서?).

이하의 데이터들을 여러 가지로 적용해서 활용 방법을 정리할 계획으로, 언제 끝날지 알 수 없어 가치 있어 보이는 데이터 리스트부터 정리한다.

유사한 맥락에서 심심풀이로 진행하는 챗봇(Dialogflow와 Rivescript/ChatScript) 제작에 도움 될 것 같아서 찾은 한국어 대화 데이터를 구할 수 있는 정보도 공유한다.

 

한글형태소 사전(NIADic) 

  • 국립 국어원에서 제공하는 데이터로, 약 100만건의 단어 사전 제공
  • “중소기업, 연구자, 일반인 등이 쉽게 NIADic을 활용하여 텍스트 분석을 수행할 수 있도록 KoNLP의 기초 형태소 사전으로 추가하여 제공”한다고 하는데 자연어 처리쪽에서 많이 사용하고, 검색엔진에 적용된 것을 보지 못해서 방법을 찾고 있다 (정보가 있으신 분 공유를 부탁드립니다).
  • 최소한 검색엔진에서 사용자 사전용으로 사용할 수 있을 것 같다.
  • 빅데이터 사이트에서 직접 다운로드 받으면 된다.

 

유의어 사전

  • 한국언론재단(빅카인즈)에서 정리하여 제공하는 시소러스 및 텍사노미 사전
  • 2016년에 제작된 pdf 파일은 구글 검색으로 쉽게 구할 수 있지만, PDF 파일이라 활용하기 어렵고,
  • 홈페이지 하단에 있는 이메일(bigkinds@kpf.or.kr)로 연락하면, 친절한 안내와 함께 2017년에 개정된 엑셀 파일을 받을 수 있다.
  • 시소러스 사전은 조금 가공해서 유의어 사전으로 사용하고,
  • 텍사노미 사전은 복합어 사전으로 유용할 것 같아서 가공해서 사용할 계획이다.

[경희대]를 Lucene 기반 검색엔진에서 어떤 형태소 분석기로 분석하면 [경희]와 [대]로 색인.
[경희대]를 사용자 사전에 추가하면 [경희대]로 색인은 되지만, [경희대학교]가 [경희대]와 [학교]로 색인.
[경희대학교]를 사용자 사전에 추가하면, 공백 차이로 [경희 대학교]가 색인되지 않음(검색되지 않음)
따라서, [경희대]를 사용자 사전에 추가하고, [경희대학교]와 [경희 대학교]를 복합명사 사전에 등록해야
[경희대], [경희대학교], [경희 대학교](실제로는 [경희], [대학교])로 색인.
=> 모든 학교와 조직들을 이런식으로 사전에 등록할 수 없는데, 텍사노미 사전에 이러한 내용이 일부 포함되어 있어서 가공하여 사용할 계획이다.

 

불용어 사전

  • 검색엔진 설정(SEO)을 도와주는 업체에서 제공하는 사전으로
  • 40여개 언어의 불용어가 정리되어 있으며, 약 700개의 한국어 불용어가 있다.
  • 웹 페이지의 내용을 복사해서, 검색엔진 불용어 사전 파일에 그대로 붙여넣으면, 바로 사용 할 수 있다.
  • Geitgey라는 사람이 개인적으로 작업하여 23개 언어, 70개 단어로 정리한 한국어 불용어 사전도 있다.
  • 형태소 분석기에서 품사에 따라 불용어 처리 기능을 제공하지 않는 경우 유용하다.

 

한국어 대화 데이터

  • 챗봇 개발에 유용한 데이터로 어떻게 활용할 것인지 검토 중인 데이터로, 대화 시나리오 구성 등에 참고용으로도 가치가 있어 보인다.
  • "소상공인 및 공공민원 10개 분야에 대한 1만건 이상의 대화(Dialog) 데이터를 구축"했다고 하고 상세한 내용은 사이트를 참고하면 된다.
  • 회원 가입하고 데이터 다운로드를 신청하면 되는데, 연구 목적 등을 적지 않으면 반려되고, 안내사항이 메일로 수신된다.
  • 연구 목적이 아니라도 조금 상세한 이용 계획을 소속과 함께 적어서 신청하면 무난하게 데이터를 다운받을 수 있다.
  • 이외에도 AI허브(aihub.or.kr)를 지향하는 사이트 답게 온갖 데이터가 제공된다.

 

기타 사전

금융기관용 사용자 사전 구축에 용이한 금융용어사전, 국방과학에 유용한 국방과학기술용어사전 등이 있다. 

특히 국방과학기술용어를 제공하는 사이트에서는 (왼쪽 트리형식의 메뉴에)

기타 사전으로 세종과학기술전문용어집, 항공기술용어집, 민간물류용어집 등 

다양한 산업에 사용할 수 있는 사전을 검색할 수 있고 엑셀로 다운 받을 수 있도록 되어 있다.

마지막으로, 위키에 정리된 비속어 사전도 나름 유용하다.

 

오픈소스 루씬(Lucene)을 기반으로 하는 대표적인 검색엔진인 Solr와 Elastic Search 중에서 

Elastic Search(이하 ES)가 더 폭넓게 사용되고, Solr는 다소(?) 위축된 것 같다.

이 사이트에 따르면 2012년 이후로 ES가 대세가 되었다고 한다.

온라인 서점에서 검색해 보면, ES 관련 서적은 매년 출판되어 2019년에도 몇 권이 출판되었다.
(Elastic Search, 엘라스틱서치, 일래스틱서치 등으로 검색)

Solr는 Solr 4 (최신 버전은 8) 버전이 2014년에 세 권 출판된 것이 전부다 (못 찾은 것일 수 있음).

 

하지만 일반적인 문서 검색은 Solr가 쉽게 구현할 수 있고, 웹 로그와 같은 소형 데이터는 ES가 좋다고 생각한다.

새로 이직한 회사에서 ES로 검색 서버를 구축하면서

외부 데이터를 검색 서버에 넣어주는(색인하는-Indexing) DIH (Data Import Handler) 관련 기술이 필요했다.

ElasticSearch는 Logtash를 이용해서 웹 로그나 메일 같은 외부 데이터를 쉽게 색인 할 수 있는데,

RDBMS의 Parent / Child 데이터를 처리하는 방법을 찾기 어려웠다.

게시판을 예로 들면, 게시글(Parent)과 댓글(Child)을 색인 하기 어려웠다. 

더욱이 첨부 파일이 있는 경우에는 게시글에 맞춰서 색인해야 하기 때문에 더 어려웠다.

방법을 찾던 중에 몇 년 전 교육 받은 Solr에서 쉽게 처리했던 기억이 났다.

같이 교육 받았던 직원이 며칠 만에 구현했다며 나에게 준 소스를 찾았다.

Solr에서는 쉬운 것이, ES에서는 자료를 첮지 못해 결국 색인 프로그램을 개발해야 했다.

 

찾은 자료를 정리하는 차원과 이 좋은 Solr도 많이 사용되길 바라면서 정리한다.

자세한 장단점은 이 사이트를(영어) 읽어보면 되고,

여기에서는 Solr를 쉽게 익히기 위해  Solr에서 제공하는 예제를 중심으로 정리한다.

Solr 예제는 Solr 사이트의 시작하기에 정리되어 있는데,

개인적으로 처음 시작하는 사람에게는 어려운 것 같아서 이 내용에 개인적인 해석을 넣어서 정리한다.

  1. 설치
  2. 기술제품과 검색식
  3. 스키마(Schema)
  4. DIH (Data Import Handler)

먼저 Solr 사이트에서 압축 파일을 다운 받아 압축을 풀어서 설치를 완료한다.

         http://lucene.apache.org/solr/downloads.html

이 글을 정리하는 시점에는 8.4.0버전이 최신 버전이고,

운영체제에 맞는 압축 파일(tgz/zip)을 받아서 사용하면 된다.

좀더 상세한 설명은 이전 블로그 글을 참고해도 되고, Solr 설치 매뉴얼을 참고 해도 된다.

 

압축 파일을 푼 다음,

다음 그림과 같이 콘솔 창에서 실행 파일이 있는 bin 폴더로 이동해서 

solr start를 입력해서 Solr를 실행한다.


경고 메시지는 일단 무시하고,

마지막의 “Started Solr server on port 8983.” 문장이 출력되면 

Solr 검색 엔진 서버가 잘 실행된 것이다.

웹 브라우저에서 8983포트로 접속해서 Solr 관리자가 잘 실행되는지 확인한다.

       http://localhost:8983

관리자 페이지에는 서버만 실행한 것이기 때문에 별다른 내용이 없다.

예제를 실행하면,

이 관리자 페이지에서 다양한 Solr 설정이나 기능을 확인할 수 있다.

 

예제에는 클라우드 예제를 먼저 정리하고 있는데, 여기에서는 클라우드 관련 내용은 정리하지 않는다.

서버나 네트워크 전문가가 아니라서 잘 모르는 것도 있고, 개인적으로 클라우드의 필요성을 잘 모르기 때문이다.

어느 정도 규모에 어떻게 적용할지를 모르면 낭비가 발생하고 (서버 규모 === 돈)

그냥 중소 규모의 기업에서 발생하는 데이터는 단독모드로 사용해도 된다고 생각한다.

더우기 검색 엔진을 잘 모르는데, 클라우드 등 다양한 개념이 시작하는 사람에게 도움이 되지 않는다고 생각하기 때문이다.

 

두 번째 예제인 기술상품(Index the Techproducts Data)부터 정리한다.

모든 예제는 설치 경로의 예제(example) 폴더에 있고, Solr 예제에서는 이 폴더의 내용을 지정해서 설치한다.

Solr 예제에 있는 방식으로 실행하면 오류가 발생한다.

데이터를 저장할 techproducts 코어를 생성하라는 설명 없이, 데이터를 저장하는 (post) 명령어만 설명되어 있다.

        solr create -c techproducts

따라서 콘솔에서 위와 같은 명령어를 실행해서 코어를 생성하고, 

Linux/Mac 일때는
        bin/post -c techproducts example/exampledocs/*

Windows는
        java -jar -Dc=techproducts -Dauto example\exampledocs\post.jar example\exampledocs\*

으로 실행하라고 한다.

여기에서 실행 경로는(example\exampledocs) Solr 설치 경로의 하위이지 bin 경로의 하위가 아니다.

각자 다르겠지만, 여기에서는 그림처럼 D:\dev\solr-8.4.0가 Solr 설치 경로이다.

앞서 코어를 생성한 것은 bin 폴더에서 실행한 것이니, 폴더 경로를 잘 맞추어 실행해야 한다.

Solr 설치 경로에서 작업할 경우에는 

        bin/solr create -c techproducts          <= Linux/Mac
        bin\solr create -c techproducts         <= Windows

처음에는 Solr 파일들을 보기 위해 bin에서 시작했고, 이후로는 편의를 위해 Solr 설치 경로를 기준으로 정리한다.

즉, solr start가 아닌 위와 같이 bin\solr start와 같이 정리한다.

그리고, 별도의 표시가 없으면 윈도우 기준으로 정리한 것이다.

 

코어를 생성하고, 데이터를 저장하는 (post) 명령어를 실행해면 뭔가 잘되는 것 같다가 오류가 발생한다.

example\exampledocs 폴더에는 XML 등 다양한 파일이 있는데, post.jar 등의 파일을 색인 할 수 없다는 오류 메시지가 출력된다.

명령어에서 모든 파일(example/exampledocs/*)을 색인 하라고 지정해서 발생한 것으로,

별도의 설정을 하면 되겠지만,

여기서는 빼고 example/exampledocs/*.xml로 수정해서 xml 파일만 색인 하도록 한다.

또는 example/exampledocs/*.json으로 바꿔서 한번 더 실행해도 된다.

 

문서에서 제시하는 방법으로 하지 말고, 다음과 같이 실행해도 된다.

먼저 실행 중 인 Solr가 있으면 다음 명령어로 중지한다.

            bin\solr stop –all

코어를 생성하지 않고 다음과 같이 예제를 실행하는 e 옵션으로 다시 실행한다.

           bin\solr start -e techproducts

기술 제품(techproducts) 예제를 실행하라는 명령어이다.

위 문장들을 읽어보면

techproducts 코어 (Created new core 'techproducts')를 생성하고

D:\dev\solr-8.4.0\example\exampledocs 폴더에 있는 XML 파일을 색인했다는 의미이다 (14 files indexed).

그리고, 마지막에 예제를 잘 실행했으니 관리자 페이지에서 결과를 확인하라고 출력한 것이다.

 

코어(core)는 색인한 데이터를 저장하는 장소로,

RDBMS로는 테이블, 엑셀로는 시트(sheet)와 비슷한 의미이다.

기술 제품을 저장할 코어(techproducts)를 생성하고, 14개의 XML 파일을 읽어서 이 저장소에 저장했다는 의미이다.

저장하다는 것을 검색 엔진에서는 색인 - Indexing 한다고 한다.

 

웹 브라우저로 관리자 페이지에 접속하면,

[Core Selector]에 생성한 techproducts 코어가 추가된 것을 확인할 수 있다.

이 코어를 선택하면,

다음 그림과 같이 하단에는 코어에서 사용할 수 있는 메뉴들이 나오고

우측에는 저장된 데이터 수(Num Docs), 저장된 데이터의 위치(data)등의 개요(Overview)가 출력된다.

기술 문서 데이터가 32건 저장된 것을 확인할 수 있다.

Replication(복제)은 데이터에 오류가 생겼을때 복구하기 위해 생성하는 것으로 기본 2개가 생성되어 있다.

복제도 클라우드와 마찬가지로 여기서 정리하지 않는다.

 

이 방법이 쉽고 간단한데, Solr를 껏다 켠 경우(solr start)  techproducts 코어가 없다고 나온다.

일반적으로 코어를 생성시키면 solr설치 경로\server\solr에 코어 이름으로 폴더가 생성된다.

bin\solr start -e techproducts로 코어를 생성한 경우, solr설치 경로\example에 코어가 생성된다.

따라서, 계속 bin\solr start -e techproducts로 실행해야 하는 단점이 있다.

어차피 예제는 몇번보고 말것이니 이렇게 사용해도 괜찮을 것 같다.

 

이상의 예제 설치 방법에서 처음 것은 Solr 예제 사이트에서 설명한 방식이고,

두 번째 간단한 것은 예제(example) 폴더의 README.txt 파일에 설명된 방식이다.

조금 부족하고 이상하지만 각자에 맞는 방식으로 연습하면 될 것 같다.

그리고, 선택한 방식에 따라 저장된 데이터의 개수에 차이가 있다.

여기서는 두 번째 방식(xml만 색인)으로 색인한 예제를 대상으로 정리한다.

 

코어를 선택하고 "Query" 메뉴를 클릭하면 다양한 데이터 조회(질의-Query)를 할 수 있는 화면(중앙부분)이 나타난다.

중앙부분 하단에 있는 파란 색 버튼 - Execute Query를 클릭하면,

다음 그림과 같이 우측에 저장된 기술 제품 예제 데이터가 Json 형태로 출력된다.

중앙 부분이 검색식을 입력하는 부분이고, 우측 부분은 실행 결과 화면이다.

우측 실행 결과 화면의 상단에 있는 URL을 클릭하면 

웹 브라우저의 주소가 해당 URL로 바뀌면서 다음 그림과 같이 실행된다.

              http://localhost:8983/solr/techproducts/select?q=*%3A*

이 결과는 이전 페이지의 JSon 결과와 같은 것으로,

Solr 검색을 URL로 실행할 수 있다는 것을 의미한다 (RESTful).

즉, URL의 내용을 수정해서 다른 웹브라우저나 탭, 프로그램curl, wget, PostMan등에서 사용할 수 있다.

 

URL의 내용을 정리하면,

http://localhost:8983/solr은 Solr 검색 엔진 서버 주소이고

techproducts는 데이터를 저장하는 코어

select는 데이터 조회를 의미한다.

q는 Query, 즉 검색식을 의미하며 *:* (%3A = : )

콜론(:) 앞의 *는 모든 필드를, 뒤의 *는 모든 값을 의미하는 것으로 모든 데이터를 조회한다는 의미가 된다.

(뒤의 * 대신에 찾고자 하는 값을 지정해서 실행하면, 모든 필드에서 지정한 값을 찾는 검색이 된다.)

저장된 데이터가 32건이니 모든 데이터는 32건이 출력될 것 같지만 10개만 출력된다.

전체 데이터를 조회하는 경우에는 알아서 10개만 반환된다.

 

이 URL은 앞서의 Query 화면에서 (http://localhost:8983/solr/#/techproducts/query)

Execute Query 버튼을 클릭하면서 자동으로 생성된 것으로,

"Query" 메뉴는 검색식을 잘 모르는 초보자들이 검색 조건을 쉽게 만들어서 테스트 해 볼 수 있는 메뉴이다.

개발자들이 각 검색 조건에 값을 지정하고 실행하면, 즉시 실행 결과를 확인할 수 있고

Java와 같은 개발 언어에서 RESTful로 호출해서 사용할 수 있는 URL을 알려주는 것이다.

위 그림에서 동그라미로 표시된 q *:*를 보면

좌측의 q *:*이 조건식을 입력한 것이고

우측 상단의 url이 Query 페이지에서 자동으로 생성된 실행 명령어(URL - q *:*)이고

우측 중앙에 있는 responseHeader에

Solr가 실행한 결과를 반환하면서 무엇을 실행했는지(params, q *:*)가 표시되어 있다.

 

response에 numFound가 찾은 전체 개수이고, start가 몇번째 것 부터 가지고 온 것인지 표시한 것이다.

좌측의 검색 조건 입력부분에서 start 값을 변경하면 response의 값도 동일하게 바뀐다.

즉, 검색한 데이터 중 몇 번째 부터(start), 몇 개(rows) 를 가지고 오라고 지정하는 것이다.

페이징(Paging)처리를 위한 것이다.

docs 다음의 배열( [ ] )은 찾은 데이터의 필드 이름과 필드 값들이 Json 형태로 출력된다.

id, name, features, price, price_c 등의 필드 값이 출력된다.

 

검색 조건들을 지정하는 부분에는 q, start, rows외에도 fq(Filter Query), Sort(정렬),  fl (반환할 필드 리스트), df (default search field 기본 검색 필드), wt (writer type 결과 표시 방법 Json, XML등) 등의 설정을 지정해서 검색 할 수 있다.

보다 자세한 내용은 Solr 문서를 읽어보길 바라고 (값 넣고 실행해 보면 대충 파악 가능), 다른 블로그에 정리된 내용을 참고 해도 된다.

여기에서는 Solr 예제와 관련된 내용을 중심으로 하나씩 정리할 예정이다.

 

 

앞서서 Solr의 기술 제품 예제를 실행시켜봤다.

여기서는 Solr 예제 사이트에 있는 몇 가지 검색(Search - Query) 방법을 정리한다.

 

  1. 설치
  2. 기술제품과 검색식
  3. 스키마(Schema)
  4. DIH (Data Import Handler)

 

 

앞서 정리한 것과 같이, 다음 명령어로 Solr 예제 중 기술 제품을 실행한다.

         bin\solr start -e techproducts

 

첫 번째 검색은 한 단어 검색(Search for a Single Term)이다.

Solr 관리자 페이지에서 techproducts 코어를 선택하고, Query 메뉴를 선택해서 검색을 할 수 있도록 한다.

익숙해지면 웹 브라우저, curl, Postman 등에서 URL로 지정해서 검색하는 것이 편리하다.

 

다음 그림과 같이 Query 메뉴를 실행한 화면에서

q 입력상자에 electronics을 입력하고 실행 버튼(Execute Query)을 클릭해서 검색을 실행한다.

q의 기본 입력값은 *:*로,  콜론(:) 앞의 *는 모든 필드, 뒤의 *는 모든 값을 의미한다.

따라서 모든 필드에 대해서 특정한 값을 조회하면 *:electronics 으로 입력해야 할 것 같지만, electronics 만 사용한다.

 

위 그림에서 id, name, menu, cat 등의 필드 명이 출력되는 것을 볼 수 있다.

이번에는 전체 필드가 아닌, 이 필드 중 cat 필드에 있는 값을 검색한다.

특정한 필드의 특정한 값을 검색할 경우(Field Searches)에는 cat:electronics와 같이 필드명을 콜론으로 구분해서 사용한다.

      curl http://localhost:8983/solr/techproducts/select?q=cat:electronics

electronics으로 검색했을 때는 14건이 검색되었지만,

cat:electronics으로 cat필드에 대해서 검색했을 때는 12건이 검색된다.

 

여러 개의 단어로 검색 하는 구절 검색(Phrase Search)은 쌍따옴표("")를 이용한다.

먼저, SDRAM Unbuffered 로 검색하면, 4건이 검색된다.

      http://localhost:8983/solr/techproducts/select?q=SDRAM%20Unbuffered

 

그리고, "SDRAM Unbuffered"로 검색하면 3건이 검색된다.

      http://localhost:8983/solr/techproducts/select?q=%22SDRAM%20Unbuffered%22

 

쌍따옴표("" %22) 없이 검색하면

구절내의 단어를 분리해서 입력한 단어 2개 중 하나라도 있으면 출력한다.

일종의 OR 검색이 실행된 것이다.

쌍따옴표를 사용할 경우에는 해당한 구절이 있는 문서만 출력한다.

 

Solr 예제의 마지막은 결합 검색(Combining Searches)이다.

electronics music 로 검색하면

      http://localhost:8983/solr/techproducts/select?q=electronics%20music

32건의 문서가 검색된다.

앞서의 예제처럼 electronics이 있거나(OR) music 이 있는 문서가 검색 된 것이다.

 

+electronics +music으로 검색하면 1건이 검색된다.

      http://localhost:8983/solr/techproducts/select?q=%2Belectronics%20%2Bmusic

electronics과 music이 있는 문서만 검색 된 것이다.

즉, electronics가 있고(AND)  music이 있는 문서가 검색된 것으로 일종의 AND 검색이 된 것이다.

 

이번에는 electronics -music 로 검색하면 13건이 검색된다.

electronics가 있지만 music 이라는 단어가 없는(-) 문서만 검색된다.

 

이상의 내용은 Solr의 기본 예제에 있는 내용으로,

전체 필드를 대상으로 한 결합 검색식을 필드에 적용해서 응용할 수 있다.

 

cat:electronics cat:music 으로 검색하면, 12건의 결과가 검색된다.

cat 필드에 electronics가 있거나 music 이 있는 데이터가 조회되는 것이다.

cat:electronics cat:musiccat:electronics OR cat:music 으로 사용할 수 있다.

      http://localhost:8983/solr/techproducts/select?q=cat%3Aelectronics%20cat%3Amusic

      http://localhost:8983/solr/techproducts/select?q=cat%3Aelectronics%20OR%20cat%3Amusic

 

즉 앞서서 필드를 지정하지 않고, 전체 필드로 사용했던 검색식을 필드를 대상으로로도 사용할 수 있다.

 

+cat:electronics +cat:music 은 cat 필드에 electronics가 있고 music 이 있는 데이터로 1건이 검색된다.

+cat:electronics +cat:musiccat:electronics AND cat:music 로도 사용할 수 있다.

      http://localhost:8983/solr/techproducts/select?q=%2Bcat%3Aelectronics%20%2Bcat%3Amusic

      http://localhost:8983/solr/techproducts/select?q=cat%3Aelectronics%20AND%20cat%3Amusic

 

cat:electronics -cat:music 은 cat 필드에 electronics가 있지만 music 이 없는 데이터로 11건이 검색된다.

      http://localhost:8983/solr/techproducts/select?q=cat%3Aelectronics%20-cat%3Amusic

 

이 외에 DMBS의 LIKE 문처럼 cat:elect* 을 사용해서 cat필드의 내용 중 elect으로 시작하는 단어가 있는 문서 14건을 검색할 수 있다.

이 예제는 와일드카드(*)로 부분 문자열 검색을 나타내기도 하지만, 앞서 설명에서 빠진 중요한 개념을 가진 예제이다.

앞서의 첫 이미지를 자세히 보면

cat 필드는 문자열 값을 여러 개 가진 배열 타입인 것을 알 수 있다.

측, cat 필드는 ["electronics", "hard drive"]로 두 개의 문자열(“”)을 가진 배열[]이다.

다른 필드는 필드에 하나의 값을 가지지만 cat과 features는 여러 개의 값을 가진 배열이다.

(필드 타입에 배열이 있다는 점을 기억해야 한다.)

 

그리고, 이상에서 사용한 검색식들은 cat 필드의 값 중에서 검색하고 하는 문자와 일치하는 것만 조회 했다.

cat: electronics은 배열에 저장된 값이 정확하게 electronics일 때만 조회 된 것이다.

cat:elect*으로 조회할 경우에는 "electronics and computer1", "electronics and stuff2"가 있는 것도 조회 되었다.

cat: electronics은 일치하는 값만 조회하기 때문에 이 결과가 조회되지 않았다.

{
        "id":"3007WFP",
        "name":"Dell Widescreen UltraSharp 3007WFP",
        "manu":"Dell, Inc.",
        "manu_id_s":"dell",
        "cat":["electronics and computer1"],
        "features":["30\" TFT active matrix LCD, 2560 x 1600, .25mm dot pitch, 700:1 contrast"],
        "includes":"USB cable",
        "weight":401.6,
        "price":2199.0,
        "price_c":"2199.0,USD",
        "popularity":6,
        "inStock":true,
        "store":"43.17614,-90.57341",
        "_version_":1654893957917704192,
        "price_c____l_ns":219900
},
{
        "id":"VA902B",
        "name":"ViewSonic VA902B - flat panel display - TFT - 19\"",
        "manu":"ViewSonic Corp.",
        "manu_id_s":"viewsonic",
        "cat":["electronics and stuff2"],
        "features":["19\" TFT active matrix LCD, 8ms response time, 1280 x 1024 native resolution"],
        "weight":190.4,
        "price":279.95,
        "price_c":"279.95,USD",
        "popularity":6,
        "inStock":true,
        "store":"45.18814,-93.88541",
        "_version_":1654893957930287104,
        "price_c____l_ns":27995}]
  }

 

Solr 예제에서는 설명되지 않았지만 루씬 검색식에 있는 예제에 정리된 범위(Range)가 있다.

범위는 많이 사용하는 검색식(Query)으로

price:[* TO 100]은 가격(price)이 100($) 이하인 것,

price:[100 TO 500]은 100~500 사이인 것,

price:[100 TO *] 100보다 비싼 것을 검색하는 검색식이다.

보다 자세한 것은 루씬 검색식을 참고 하면 된다.

 

이러한 검색식들은 구글의 검색식과 비슷하게 제공되고,

다양하게 제공되니 예제를 익히고 나서 Solr 문서의 내용을 잘 읽어보길 바란다.

 

Solr 예제를 설치해서 검색하는 방법에 대해서 정의했고,

이번에는 검색을 위해 데이터를 저장하는 방법 중 구조(Schema)를 구성하는 것에 대해서 정리한다.

Solr 예제에서 영화 정보(films)를 이용하여 구조에 대해서 설명하기 때문에, 여기에서도 영화 정보를 이용하여 정리한다.

  1. 설치
  2. 기술제품과 검색식
  3. 스키마(Schema)
  4. DIH (Data Import Handler)

데이터를 저장하기 위해서는 저장하는 장소, 즉 스키마(Schema)에 대해서 정의해야 한다.

대부분의 데이터는 다음 엑셀 그림처럼 name, directed_by, genre, type, id, initial_release_date 같은 컬럼이 있고, 각각의 데이터가 행으로 저장된다.

각각의 컬럼이 어떤 데이터를 가지는 지를 정의하는 것이 스키마로 데이터 베이스(DBMS)의 용어와 동일한 의미로 사용된다.

그림을 보면 배포일자(initial_release_date)는 년월일의 년도 형 데이터 구조를 가지고 있다.

이 외의 모든 컬럼은 일반 (텍스트, 문자열) 형 데이터 구조를 가지고 있다.

숫자가 있었다면 숫자형 데이터를 가지게 된다.

이렇게 구조를 미리 지정해서 저장하는데, Solr에서는 이 스키마를 미리 정의하지 않고도 사용할 수 있다. 

Elasticsearch도 동일한데, 이것을 Schemaless라고 한다.

하지만, 가급적 정의해서 사용하는 것이 좋고, 그 이유를 Solr 예제에서 제공하고 있다.


Solr를 실행하고(solr star), 다음 명령어로 films 코어를 생성한다.

      bin/solr create -c films             <- Linux, Mac

      bin\solr delete -c films           <- Windows

 

예제 폴더(example)에는 앞서 정리한 기술제품(techproducts)외에도 영화 정보(films) 예제 파일이 있다.

이 파일의 내용을 생성한 films 코어에 저장한다.

다음 명령어로 생성한 films 코어에 데이터를 저장한다.

      bin/post -c films example/films/films.json                                                         <- Linux, Mac

      java -jar -Dc=films -Dauto example\exampledocs\post.jar example\films\*.json     <- Windows

영화 정보(films) 폴더에는 films.csv, films.json, films.xml 3개의 파일이 있다.

모두 동일한 영화 정보 데이터를 가지고 있고, 이 중에서 json 파일의 내용을 films 코어에 저장하는 명령어이다.

films 코어를 생성하고, 데이터를 저장하면 위 그림과 같이 오류가 발생한다.

그리고, Solr 관리자 화면에서 데이터를 조회(Query)하면, 5개의 데이터가 저장되어 있다.

화면을 스크롤 해서 이름(name) 필드의 값을 보면, 모두 실수형이고 배열 ([])이다.

  {
    "id":"/en/45_2006",
    ~~ 생략
    "name":[0.45],
  {
    "id":"/en/9_2005",
    ~~ 생략
    "name":[9.0],
  {
    "id":"/en/69_2004",
    ~~ 생략
    "name":[69.0],
  {
    "id":"/en/300_2007",
    ~~ 생략
    "name":[300.0],
    "_version_":1655399262942396417},
  {
    "id":"/en/2046_2004",
    ~~ 생략
    "name":[2046.0],
    "_version_":1655399262943444992}]
  }

Solr 관리자 화면의 스키마(Schema) 화면은 선택된 코어의 필드 구조를 관리 하는 페이지로,

name 필드를 선택하면 type이 pdoubles(실수) 로 생성되어 있다.

(이 화면에서 데이터가 저장되면서 자동으로 생성된 모든 필드들을 볼 수 있다.)

영화 예제 폴더 중에서 csv 파일을 엑셀로 열면, name (A)컬럼의 앞에 있는 5개 데이터가 숫자이다.

그리고, 6번째 데이터부터 약 1000개의 데이터는 문자이다.

Solr (실재로는 Lucene)에서는 첫 데이터로 컬럼의 구조를 정의하는데,

위와 같이 첫 데이터가 숫자면 컬럼은 숫자(실수)로 정의한다.

즉, 앞서의 데이터 저장시 발생한 오류는 숫자 컬럼에 문자가 입력되어 발생한 오류이다.

 

name 필드를 삭제하고 새로 생성해서, 이 오류를 해결한다.

Schema 페이지(앞서의 이미지)에서 name 필드를 선택하면 활성화 되는 하단의 [delete field] 버튼을 클릭해서 name 필드를 삭제한다.

그리고, 상단의 [Add Field] 버튼을 클릭하여 name 필드를 다시 생성한다.

그림과 같이 생성할 필드명 name을 입력하고, field type에는 문자열(text_general)을 지정하고 필드를 생성한다 (Add Field).

name 필드가 생성된 것을 확인하고, 앞서 실행했던 데이터 저장을 다시 실행하면(post),

그림과 같이 오류 없이 실행되는 것을 확인 할 수 있다.

Solr 관리자 페이지 Query 에서 데이터를 조회하면, 1100개의 데이터가 저장되어 있다.

Solr나 Elasticsearch에서는 이 스키마를 미리 정의하지 않고도 사용할 수 있지만(Schemaless)

이와 같이 데이터의 구조를 제대로 고려하지 않고 사용하면 문제가 발생하기 때문에 스키마를 정의해서 사용하는 것이 좋다.

이 문제 외에도 긴 문장의 경우에는 형태소 분석 등을 하게 된다.

이런 필드와 그냥 저장하는 필드 등을 미리 정의해야 해서 스키마를 미리 정의해서 사용하는 것이 좋다.

또다른 필드 추가 방법

Solr 관리자 화면에서 필드를 지우거나 생성해도 되고, 다음과 같이 RESTful로 처리해도 된다.

Linux: curl -X POST -H 'Content-type:application/json' --data-binary '{"delete-field" : { "name":"name" }}' http://localhost:8983/solr/films/schema
Windows: curl -X POST -H "Content-type:application/json" --data-binary "{\"delete-field\" : { \"name\":\"name\" }}" http://localhost:8983/solr/films/schema

curl 사용시 리눅스는 홑따옴표(')를 사용하고, 윈도우는 쌍따옴표(")를 사용하는 차이가 있다

윈도우는 쌍따옴표(")를 사용하기 때문에 Json 키/값 표현에 사용되는 쌍따옴표(")는 \"로 표현해야 한다.

필드를 삭제(delete-field) 할 때에는 삭제할 필드명(name)을 name으로 지정한다.

 

Linux: curl -X POST -H 'Content-type:application/json' --data-binary '{"add-field": {"name":"name", "type":"text_general", "multiValued":false, "stored":true}}' http://localhost:8983/solr/films/schema
Windows: curl -X POST -H "Content-type:application/json" --data-binary "{\"add-field\": {\"name\":\"name\", \"type\":\"text_general\", \"multiValued\":false, \"stored\":true}}" http://localhost:8983/solr/films/schema

필드를 추가(add-field) 할 때에는 필드명(name), 필드 타입(type), 다중값 여부(multiValued, 배열), 저장여부(stored)등을 지정해서 생성한다.

이와 관련된 정리는 다른 블로그에 잘 정리되어 있다.

{
    "add-field": {
                    "name":"name", 
                    "type":"text_general", 
                    "multiValued":false, 
                    "stored":true
                }
}        

이상과 같이 RESTful 방식으로 스키마를 생성하는 자세한 방법은 Solr 도움말을 참고 하면 된다.

 

스키마에 대해서 조금 더 정리하면,

적당한 편집기로 example\techproducts\solr\techproducts\conf 폴더에 있는 managed-schema 파일을 연다.

example\techproducts\solr\techproducts 폴더는 앞서 정리한 기술제품(techproducts)의 코어가 있는 폴더이고

managed-schema 파일은 코어의 스키마에 대해서 정의하는 XML 파일이다.

   <field name="id" type="string" indexed="true" stored="true" required="true" multiValued="false" /> 

   <field name="pre" type="preanalyzed" indexed="true" stored="true"/>
   <field name="sku" type="text_en_splitting_tight" indexed="true" stored="true" omitNorms="true"/>
   <field name="name" type="text_general" indexed="true" stored="true"/>
   <field name="manu" type="text_gen_sort" indexed="true" stored="true" omitNorms="true" multiValued="false"/>
   <field name="cat" type="string" indexed="true" stored="true" multiValued="true"/>
   <field name="features" type="text_general" indexed="true" stored="true" multiValued="true"/>
   <field name="includes" type="text_general" indexed="true" stored="true" termVectors="true" termPositions="true" termOffsets="true" />

   <field name="weight" type="pfloat" indexed="true" stored="true"/>
   <field name="price"  type="pfloat" indexed="true" stored="true"/>
   <field name="popularity" type="pint" indexed="true" stored="true" />
   <field name="inStock" type="boolean" indexed="true" stored="true" />

   <field name="store" type="location" indexed="true" stored="true"/>

앞서 정리했던, 기술제품(techproducts)의 필드들이 정의되어 있다.

필드명(name), 색인여부(indexed), 저장여부(indexed), 필수 입력(required), 다중값여부(multiValued)등의 속성을 지정했다.

이와 관련된 정리는 다른 블로그에 잘 정리되어 있다.

영화 정보의 managed-schema 파일 (\server\solr\films)에는 이러한 정의가 없다 (Schemaless).

 

 

'서버 > 검색엔진' 카테고리의 다른 글

1. Solr 예제 분석 - 설치  (1) 2020.01.12
2. Solr 예제 분석 - 기술제품과 검색식  (3) 2020.01.12
4. Solr 예제 분석 - DIH  (0) 2020.01.12
1. Solr로 만드는 단순 게시판: 각종 설치  (18) 2017.08.24
2. SolrJ 사용법  (0) 2017.08.24

+ Recent posts