Recommanded Free YOUTUBE Lecture: <% selectedImage[1] %>

Contents

<!> 4.7 버전에 맞추어 새로 작성 중

Solr

Solr은 Apache Lucene프로젝트에 기반을 둔 검색엔진으로 기업 대상으로 개발을 했다. 현재(@DATE) 최신 버전은 Apache solr 4.7.0 버전이다.

Solr의 기능들

Solr는 단독 애플리케이션 서버 형태로 작동하며, REST 형식의 API를 제공한다. 문서들은 HTTP를 이용해서 XML, JSON, CSV 혹은 바이너리 형태등으로 색인요청을 할 수 있다. 검색 역시 HTTP GET 으로 요청하며, 검색결과는 XML, JSON, CSV, 바이너리 형태로 가져올 수 있다.
  • Full-text search
  • 높은 웹 트래픽을 감당할 수 있도록 최적화.
  • XML, JSON과 HTTP등의 표준 오픈 인터페이스를 제공.
  • HTML 기반의 관리자 인터페이스
  • Linearly scalable, auto index replication, 자동 failover와 복구
  • 유연하고 강력한 XML파일 기반의 설정 환경
  • 확장가능한 플러그인 아키텍처

Solr의 Lucene 확장 기능

  • 일반 문자열을 비롯 Numberic 타입, 다이나믹 필드, 유니크 키 지원
  • Lucene 쿼리 랭퀴지 확장지원
  • 검색과 필터링 지원
  • Geospatial 검색 지원
  • 성능 최적화
  • AJAX 기반의 관리자 인터페이스
  • 로그 모니터링
  • 실시간(Near Real-time) 증분 색인과 색인 복제(replication)지원
  • 분산 검색과 다중 호스트간 샤드(sharded) 지원
  • 데이터베이스와 XML 파일 그리고 HTTP를 이용한 다양한 색인방식 지원
  • Apache Tika를 이용한 다양한 형식의(PDF, Word, HTML 등등) 문서 색인

Joinc 컨텐츠 검색 서비스 만들기

대부분의 CMS 툴들이 그렇듯이 (joinc의 CMS인)모니위키 역시 검색기능이라고 할 만한게 없다. Full text search기능이 있긴 하지만 grep을 이용한 건데, 그냥 쓰지 않는게 낫다.

하여 구글 custom search를 도입했다. 2007년 부터 2011년까지 custom search(이하 CS)를 이용해서 검색서비스를 제공했는데, 쓸만했지만 몇 가지 문제가 있었다.
  1. 색인 갱신 주기를 조절할 수 없다. 컨텐츠를 옮기거나 하는 등으로 URL이 변경되면, 다음번 색인 까지 한참 동안 링크가 깨진다. : 실시간 색인을 포기해야 한다는 의미다.
  2. URL 맵이 엉망으로 만들어진다. 구글 CS는 웹 크롤러를 이용해서 문서를 수집한다. 모든 가능한 링크에 대해서 색인이 만들어 지는데, 그러다 보니 ?action=diff 등과 같은 wiki 액션 페이지들도 색인을 한다. memset 페이지 뿐만 아니라 memset??action=diff 와 같은 페이지까지 색인한다. 쓸데없는 문서들까지 recall 된다는 이야기다.
  3. 텍스트 브라우저로 브라우징 할 수 없다. 주로 w3m을 이용해서 브라우징을 한다. 구글 CS는 Ajax 호출이기 때문에 w3m에서는 검색결과를 볼 수 없다. 대부분의 사람들에게는 상관없겠지만 w3m을 이용해서 브라우징하고 컨텐츠를 작성하는 나에게는 중요한 문제다.
문제를 해결하기 위해서는 컨텐츠 기반으로 색인을 하면 되는데, lucene이 거의 유일한 해결책이라 할 수 있겠다. 하지만 lucene는 너무 무겁고, 설치/운용이 귀찮다. 직접 만드는 건 더 귀찮다. (Mysql도 full text search를 제공한다고 하긴하더라.)

이래 저래 귀찮아서 그냥 CS를 사용하고 있었는데, chef를 가지고 놀다가 우연찮게 solr를 발견했다. chef에서는 메타 정보 검색을 위해서 solr를 사용 한다. 이게 뭔가 하고 살펴봤더니 루신기반의 검색 소프트웨어란다. 게다가 설치/운용도 간단하더라. 이후 검색서비스를 solr로 대체해서 지금까지 사용하고 있다.

2007년 처음 solr를 적용했을 때 버전이 3.x 였는데, 어느덧 2014년 지금은 4.7이다. solr 4.7을 설치하고 운용하는 방법을 정리하려고 한다.

Joinc Solr 적용 방법

Joinc 컨텐츠에 대해서 색인 및 검색 테스트를 진행하고, 문제 없으면 Joinc 사이트에 적용한다.
  • 테스트 서버 : 개인 리눅스 데스크탑 Ubuntu 13.10
  • 테스트 방식 : 모니위키는 컨텐츠를 특정 디렉토리밑에 일반 텍스트 파일로 관리한다. 이 텍스트 파일을 모두 로컬로 복사해서 색인한다.
  • 검색 서비스 테스트 : 색인이 끝나면, solr 검색 서버를 실행한다. 검색요청은 HTTP로 할 수 있으니, 모니위키 페이지에서 Ajax호출해서 결과를 가져와서 뿌려주면 끝난다.
  • 실시간 색인 테스트 : 모니위키는 컨텐츠를 파일로 저장한다. 파일로 저장할 때, 파일의 내용으로 색인 요청하면 된다. 색인은 HTTP POST 간단히 요청할 수 있다.
이렇게 해서 테스트를 다 끝내고 나서, joinc에 실제로 적용하기로 했다. 테스트 환경은 아래와 같다.

  • Virtualbox로 테스트한다.
  • 두 개의 Solr server를 준비한다. solr server 1, solr server 2 라고 부르기로 했다. 분산 검색 테스트를 위해서다. joinc에서 가져온 텍스트 파일은 1/2로 나눠서 각각의 solr server에 분산해서 색인한다.

Solr Tutorial

Joinc 컨텐츠를 색인하기 전에 solr tutorial에 있는 내용을 테스트 해보기로 했다.

요구 사항

Java 1.6 이상이 필요하다. Java는 Oracle, Open JDK, IBM에서 찾을 수 있다. 나는 Open JDK를 사용하기로 했다.
# apt-get install default-jre
# java -version
java version "1.7.0_51"
OpenJDK Runtime Environment (IcedTea 2.4.4) (7u51-2.4.4-0ubuntu0.13.10.1)
OpenJDK 64-Bit Server VM (build 24.45-b08, mixed mode)

solr 다운로드 및 설치

http://lucene.apache.org/solr 에서 다운로드 했다. ()현재 최신 버전은 4.7.0이다.

다운로드한 solr 소프트웨어는 solr server 1에 설치했다. 분산 검색전에 단일 호스트에서의 검색부터 끝내야 하지 않겠는가. 단일 호스트 검색을 끝낸 후 분산 검색을 수행한다. 나는 /opt/solr 디렉토리에 설치했다.

solr server 실행

example 디렉토리 밑에 있는 start.jar를 실행한다.
# java -jar start.jar
[main] INFO  org.eclipse.jetty.server.Server  – jetty-8.1.10.v20130312
20   [main] INFO  org.eclipse.jetty.deploy.providers.ScanningAppProvider  – Deployment monitor /opt/solr/example/contexts at interval 0
30   [main] INFO  org.eclipse.jetty.deploy.DeploymentManager  – Deployable added: /opt/solr/example/contexts/solr-jetty-context.xml
......
Jetty 애플리케이션 서버가 8983 포트로 시작하는 걸 확인할 수 있다.
# netstat -nap
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      821/sshd        
tcp        0      0 192.168.56.5:22         192.168.56.1:52694      ESTABLISHED 944/sshd: yundream 
tcp6       0      0 :::22                   :::*                    LISTEN      821/sshd        
tcp6       0      0 :::8983                 :::*                    LISTEN      7519/java       
웹 브라우저로 http://192.168.56.5:8983/solr에 접근하면, Solr 대쉬보드 화면을 볼 수 있다.

https://lh6.googleusercontent.com/-OvGe99HqSqg/UyXVxWqf0yI/AAAAAAAADfI/cc9GjYqXOdg/s640/solr01.png

예쁘다.

solr example 문서 색인

Joinc 컨텐츠를 색인하기 전에 solr에서 tutorial 용으로 제공하는 example 문서를 색인해보기로 했다. solr는 실행시간에 컨텐츠 색인의 추가/업데이트/삭제가 가능하다. XML, JSON, CSV 등 다양한 형식으로 컨텐츠를 색인할 수 있다.

exampledocs 디렉토리에 샘플파일이 있는데, 이 파일을 색인해 보기로 했다. exampledocs 디렉토리 밑에는 컨텐츠를 색인하고 업데이트 할 수 있는 자바 프로그램인 post.jar가 있다. post.jar를 이용해서 solr.xml과 monitor.xml을 색인했다.
# cd /opt/solr/example/exampledocs
# java -jar post.jar solr.xml monitor.xml
SimplePostTool version 1.5
Posting files to base url http://localhost:8983/solr/update using content-type application/xml..
POSTing file solr.xml
POSTing file monitor.xml
2 files indexed.
COMMITting Solr index changes to http://localhost:8983/solr/update..
Time spent: 0:00:00.256
두개의 문서가 색인됐다. 색인 정보는 solr 관리자 페이지의 Core Admin 탭에서 확인할 수 있다.

https://lh4.googleusercontent.com/-EqeZ4Q3kSNI/UyhssLbj64I/AAAAAAAADfg/z08TvRqI-jo/s640/solr02.png 관리자 페이지에서 검색결과도 확인할 수 있다. collection탭에서 테스트할 collection(지금은 collection1 하나만 있을거다)를 선택하면, Query메뉴가 있다. 클릭하면 질의어와 각종 검색 옵션을 설정할 수 있는 페이지가 뜬다. 질의어를 넣고 Execute Query버튼을 클릭하면, 검색결과를 확인할 수 있다. solr로 검색을 할 경우 http://192.168.56.5:8983/solr/collection1/select?q=solr&wt=json&indent=true 와 같은 요청이 생성된다.

https://lh6.googleusercontent.com/-1x305BQIi9o/Uyhv1gfDFJI/AAAAAAAADfs/pblXtjOUEWU/s640/solr03.png exampledocs에 있는 다른 문서들도 색인해서 테스트를 해보자.
# java -jar post.jar *.xml
SimplePostTool version 1.5
Posting files to base url http://localhost:8983/solr/update using content-type application/xml..
POSTing file gb18030-example.xml
POSTing file hd.xml
POSTing file ipod_other.xml
POSTing file ipod_video.xml
POSTing file manufacturers.xml
POSTing file mem.xml
POSTing file money.xml
POSTing file monitor2.xml
POSTing file monitor.xml
POSTing file mp500.xml
POSTing file sd500.xml
POSTing file solr.xml
POSTing file utf8-example.xml
POSTing file vidcard.xml
14 files indexed.
COMMITting Solr index changes to http://localhost:8983/solr/update..
Time spent: 0:00:00.336
solr에서 사용할 수 있는 질의어 형식은 Solr Query Syntax문서를 참고하면 되겠다.

색인 업데이트

이 문서대로 색인을 했다면 solr.xml 문서를 두번 색인했을 것이다. 하지만 "solr"로 검색하면 여전히 하나의 문서만 검색된다. 이유는 문서를 색인할 때 참고하는 schema.xml에 "uniqueKey"로 "id"필드를 설정했기 때문이다. uniqueKey로 설정된 필드는 단지 하나만 존재할 수 있기 때문에, 필드의 값이 같을 경우에는 추가되는 대신에 "업데이트"된다.

이제 다시 java -jar post.jar를 실행하더라도 (모든 문서가 중복되기 때문에) 색인정보는 변하지 않을 것이다.

색인 삭제

역시 동적으로 색인을 삭제할 수 있다. uniqueKey로 delete 명령을 전송하면 된다. 사용방법은 다음과 같다.
# java -Ddata=args -Dcommit=false -jar post.jar "<delete><id>SP2514N</id></delete>"
SimplePostTool version 1.5
POSTing args to http://localhost:8983/solr/update..
Time spent: 0:00:00.032
id 가 SP2514N인 문서를 찾아서 삭제한다. 하지만 commit가 false인 관계로 여전히 검색이 된다. delete를 실제 색인에 적용하기 위해서는 명시적으로 "commit"해줘야 한다.
# java -jar post.jar -
SimplePostTool version 1.5
COMMITting Solr index changes to http://localhost:8983/solr/update..
Time spent: 0:00:00.030
색인 정보를 보면 numDocs가 1줄어든걸 확인할 수 있다.

commit를 true로 하면, 즉시 색인에 반영할 수 있다.
# java -Ddata=args -Dcommit=true -jar post.jar "<delete><id>SOLR1000</id></delete>"
SimplePostTool version 1.5
POSTing args to http://localhost:8983/solr/update..
COMMITting Solr index changes to http://localhost:8983/solr/update..
Time spent: 0:00:00.038

uniqueKey가 아닌, 일반 질의어로도 삭제할 수 있다. name 필드에 "solr"을 포함한 모든 문서를 지우기 위한 예다.
# java -Dcommit=false -Ddata=args -jar post.jar "<delete><query>name:solr</query></delete>"

색인에는 많은 cpu 자원이 소모된다. 따라서, 실시간으로 매번 commit 하는 것보다는 일괄 commit하는걸 권장한다. 물론 joinc는 즉시 commit 할거다. 어차피 문서작성하는 사람이 혼자라서 배치작업하는게 의미가 없다.

검색 Query

검색은 HTTP GET을 이용하면 된다. query는 q파라메터의 값으로 넘긴다. 일단은 q 파라메터를 이용하는 것으로 검색이 가능하지만, 정교한 검색을 위해서 옵션 파라메터를 사용할 수 있다. 예컨데, fl 파라메터를 이용해서 검색 필드를 지정하거나 소트(sort) 필드의 지정, 출력 형식(XML, JSON)등을 지정할 수 있다.
  • q=video&fl=name,id : name과 id 필드에서 video를 검색한다.
  • q=video&fl=name,id,score : 문서의 가중치(score)를 출력한다.
  • q=video&fl=*,score : 모든 필드에서 video를 검색하고, 문서 가중치도 출력한다.
  • q=video&sort=price desc&fl=name,id,price : price를 내림차순(desc)으로 정렬한다

하일라이팅

검색 결과는 "검색어를 포함한" 문자열의 일부를 출력한다. 하일라이팅 설정을 하면 "검색어"를 강조해서 보기 좋은 검색출력 화면을 만들 수 있다.
  • q=video%20card&fl=name,id&hl=true&hl.fl=name,features&wt=json
하일라이팅에 대한 자세한 내용은 solr Highlighting parameters 문서를 참고하자.

내장 검색 UI

Solr는 검색결과를 확인하기 위한 검색 UI를 내장하고 있다. 색인결과를 테스트하기 위해서 사용할 수 있는데, 검색, 하일라이팅, 자동완성, 위치 검색등의 주요 기능들을 포함하고 있다.
  • http://192.168.56.5:8983/solr/collection1/browse

HTTP API

Solr은 모든 기능을 HTTP를 통해서 제공한다. 여기에는 색인, 검색, 삭제, 업데이트 뿐만 아니라 스키마추가/업데이트, 리플리케이션 등 모든 기능을 포함한다. 여기에서는 색인 추가,업데이트,삭제, 검색 등 필수 기능만 테스트한다.

Solr는 POST와 GET을 이용해서 색인을 관리하고 검색을 요청할 수 있다. HTTP 만으로 모든 작업을 할 수 있기 때문에, CURL등의 도구를 이용해서 간단하게 검색 애플리케이션을 개발할 수 있다. 나는 php에서 제공하는 CURL(client URL Library)를 이용해서 웹 애플리케이션을 개발했다.

CURL을 이용해서 Solr의 기능을 테스트 한다. exampledoc 디렉토리 밑에 보면 books.json이 있는데, 이 파일의 스키마로 테스트하기로 했다.

books.json 파일을 참고해서 테스트에 사용할 cosmos.json을 만들었다.
[
  {
    "id" : "0-394-50294-9",
    "cat" : ["book","paperback"],
    "name" : "cosmos",
    "author" : "Carl sagan",
    "sequence_i" : 1,
    "genre_s" : "Popular science",
    "inStock" : true,
    "price" : 28.20,
    "pages_i" :365 
  }
]

Update와 Insert

JSON 데이터를 Update 한다.
# URL=http://192.168.56.5:8983/solr/update/json
# curl -X POST $URL -H 'Content-type:application/json' -d @cosmos.json
{"responseHeader":{"status":0,"QTime":2}}
commit를 해야 적용된다.
# curl "$URL?commit=true"

Search

cosmos로 검색해보자.
# curl "http://localhost:8983/solr/select?q=cosmos&wt=json&indent=true"
{
  "responseHeader":{
    "status":0,
    "QTime":1,
    "params":{
      "indent":"true",
      "q":"cosmos",
      "wt":"json"}},
  "response":{"numFound":1,"start":0,"docs":[
      {
        "id":"0-394-50294-9",
        "cat":["book",
          "paperback"],
        "name":"cosmos",
        "author":"Carl sagan",
        "author_s":"Carl sagan",
        "sequence_i":1,
        "genre_s":"Popular science",
        "inStock":true,
        "price":28.2,
        "price_c":"28.2,USD",
        "pages_i":365,
        "_version_":1463107060941979648}]
  }}

delete

delete와 update등 색인을 수정하는 것은 모두 "/solr/update"로 이루어진다. delete 할건지, update(혹은 insert)할 건지는 JSON에 정보로 명시를 한다. 색인을 delete하는 예제다.
# cat cosmos.json
{
  "delete":{"id": "0-394-50294-9"}
}

# curl -X POST $URL -H 'Content-type:application/json' -d @cosmos.json
# curl "$URL?commit=true"
이제 id가 0-394-50294-9인 문서(cosmos)는 검색되지 않을 것이다.

Joinc 검색 서비스 개발

Joinc의 컨텐츠를 색인한다.
  • title과 body(본문이 저장되는) 두 개의 field로 구성된다. title field의 boost를 body 보다 높게 설정한다.
  • uniqueKey는 컨텐츠가 저장된 파일의 이름으로 한다.
  • 영문과 한글 숫자만 색인한다. 그 외의 문자들은 색인에서 제외한다.
  • 하일라이팅 기능을 지원한다. css 작업 좀 하면 되겠다.
  • 자동완성 기능을 사용한다. ajax 삽질을 좀 해야겠지.

schema 설계

schema를 설계하기 위해서는 문서를 어떤 기준으로 나눌 것인지를 결정해야 한다. joinc의 컨텐츠는 moniwiki에서 관리하는데, 데이터베이스가 아닌 text로 저장된다. 데이터 형식은 html이 아닌 moniwiki의 고유 마크다운 형식을 따른다.

문서는 title과 body만 가지고 있다. 문서저자, 작성일과 같은 정보는 없다. 따라서 "title"과 "body" 단지 두 개의 필드로 구성한다.

id는 문서가 저장된 파일 이름으로 한다. 정리하자면 이렇다.
필드명 색인 데이터 타입 설명
id 파일이름 UniqueKey
title 문서제목 string body보다 높은 boost 값을 가진다.
body 문서 본문
update 문서 최종 변경일 Moniwiki는 RCS로 저장한며, 최종 업데이트일을 알 수 있다.

색인

마크다운 형식의 joinc wiki 문서를 그대로 색인하기에는 문제가 있다. 유저가 웹 브라우저를 이용해서 보는 페이지의 내용 그대로 색인을 해야 한다. HTML 문서를 색인하기로 했다. 링크기반으로 웹 문서를 수집하는 삽질은 하지 않기로 했다. 어차피 원문을 내가 관리하고 있으니, 색인해야 할 URL의 목록은 간단히 만들 수 있다. 간단한 스크립트를 돌려서 URL 목록을 만든 후, w3m으로 URL을 읽어서 text 파일로 변환하기로 했다.

w3m으로 웹 문서를 txt로 저장하는 방법이다.
# w3m -O utf-8 -dump "http://www.sample.com" > index.txt

이렇게 w3m으로 긁어온 컨텐츠는 HTML로 저장되는게 아니고, 렌더링된 plain/text 형태로 저장된다. 해서 아래의 문제를 해결해야 한다.
  1. 제목과 본문을 알 수 없다.
  2. 쓸데없는 문자들이 많다. 색인할 텍스트만 포함하도록 문서를 정리해야 한다.

검색 인터페이스 개발

검색 출력 페이지 개발

분석기

히스토리

  • 작성일 :

참고