Elasticsearch, fscrawler 기반 문서 검색 시스템 구축하기
Post

Elasticsearch, fscrawler 기반 문서 검색 시스템 구축하기

목표

Elasticsearchfscrawler를 활용하여 문서 검색 시스템을 구축한다.

Elasticsearch ?
Elasticsearch는 Apache Lucene( 아파치 루씬 ) 기반의 Java 오픈소스 분산 검색 엔진으로 대용량 데이터를 신속하게 저장, 검색 및 분석할 수 있는 기능을 제공한다. 데이터를 실시간으로 처리하여 색인할 수 있고 RESTful API를 통해 데이터에 쉽게 액세스 할 수 있다.

fscrawler ?
FSCrawler는 Elasticsearch를 위한 도구 중 하나로 파일 시스템의 다양한 형식의 문서(예: 텍스트 파일, PDF, 워드 문서 등)를 검색 가능한 형태로 변환하여 Elasticsearch에 색인하는 역할을 한다.

개발환경

UseDescription
Elasticsearch검색 엔진
fscrawler 문서 파일 색인 추출
KibanaElasticsearch 시각화 도구
analysis-noriElasticsearch의 한국어 형태소 분석기
nextjsweb 개발 프레임워크로 사용
docker컨테이너 기반 서비스로 docker를 활용
vscode소스 편집 툴

프로젝트 가져오기

아래 프로젝트에서 소스를 clone 한다.

1
$ git clone https://github.com/surfharu/withsearch.git

주요 파일 구성은 다음과 같다.

폴더 및 파일설명 
elasticsearch한국어 형태 분석기를 포함한 elasticsearch 이미지 생성하기 위한 폴더 
config환경 설정 파일 (elasticsearch & fscrawler) 
web검색을 위한 web 페이지 (nextjs로 구성) 
documents검색을 위한 문서들을 저장할 위치  
logs주요 서비스 로그 위치 
docker-compose.yml서비스 docker-compose 설정 파일 

프로젝트 설치 및 셋팅

먼저 elasticsearch 에 analysis-nori 플러그인을 추가하여 도커 이미지를 생성한다.

1
2
$ cd elasticsearch 
$ docker build -t custom-elastic:latest .

web 서비스를 위한 도커 이미지를 생성한다. (nextjs 프레임워크를 이용하여 개발함)

1
2
$ cd web 
$ docker build -t custom-web:latest .

다음으로 config 폴더 아래 elasticsearch, fscrawler 관련 설정을 해준다.

config/fscrawer/_default/7/_settings.json 파일에 FSCrawler가 한국어 형태소 분석기(nori_analyzer)를 활용할 수 있도록 설정하였다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
  "settings": {
    "number_of_shards": 1,
    "index.mapping.total_fields.limit": 2000,
    "analysis": {
      "analyzer": {
        "fscrawler_path": {
          "tokenizer": "fscrawler_path"
        },
        "nori_analyzer": {
          "type":"custom",
          "tokenizer":"korean_nori_tokenizer"
          }
      },
      "tokenizer": {
        "fscrawler_path": {
          "type": "path_hierarchy"
        },
        "korean_nori_tokenizer":{
          "type":"nori_tokenizer",
          "decompound_mode":"mixed",
          "user_dictionary":"userdict_ko.txt"
        }
      }
    }
  },

config/fscrawer/haru/_settings.yaml 에 문서 경로 및 업데이트 시간을 설정한다.
name은 인덱스명이므로 사용자설정에 맞게 입력한다. update_rate5m으로 5분 주기로 문서를 읽어들이도록 설정하였다.
보다 자세한 설정 방법은 fscrawler Site를 참조한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
name: "haru"
fs:
  url: "/tmp/documents"
  update_rate: "5m"
  json_support: false
  filename_as_id: true
  add_filesize: true
  remove_deleted: true
  add_as_inner_object: false
  store_source: true
  index_content: true
  attributes_support: false
  raw_metadata: true
  xml_support: false
  index_folders: true
  lang_detect: false
  continue_on_error: false

elasticsearch:
  nodes:
  - url: "http://elasticsearch:9200"
  bulk_size: 1000
  flush_interval: "5s"
  byte_size: "10mb"

config/elasticsearch/elasticsearch.yml에 CORS 설정을 추가해준다. 동일 호스트는 허용하도록 설정하였다.

1
2
http.cors.enabled: true
http.cors.allow-origin: /https?:\/\/localhost(:[0-9]+)?/

docker-compose.yml 파일을 설정한다. 운영 서비스는 아니므로 싱글노드로 구성하여 설정하였다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
version: '2.2'
services:
  elasticsearch:
    image: custom-elastic
    container_name: elasticsearch
    environment:
      - discovery.type=single-node
      - cluster.name=single-cluster
      - bootstrap.memory_lock=true
      - "ES_JAVA_OPTS=-Xmx2g -Xms2g"
    healthcheck:
      test: ["CMD-SHELL", "curl --silent --fail localhost:9200/_cluster/health || exit 1"]
      interval: 20s
      timeout: 20s
      retries: 3
    ulimits:
      memlock:
        soft: -1
        hard: -1
    volumes:
      - esdata01:/usr/share/elasticsearch/data
      - ${PWD}/config/elasticsearch/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml
    ports:
      - 9200:9200
    networks:
      - elastic

  # Kibana
  kibana:
    image: docker.elastic.co/kibana/kibana:7.9.2
    container_name: kibana
    environment:
      ELASTICSEARCH_URL: http://elasticsearch:9200
      ELASTICSEARCH_HOSTS: http://elasticsearch:9200
    ports:
      - "5601:5601"
    networks:
      - elastic
    depends_on:
      elasticsearch:
          condition: service_healthy

  # FSCrawler 
  fscrawler:
    image: dadoonet/fscrawler
    container_name: fscrawler
    volumes:
      - ${PWD}/config/fscrawler:/root/.fscrawler
      - ${PWD}/documents:/tmp/documents
      - ${PWD}/logs/fscrawler:/usr/share/fscrawler/logs
    environment: 
      - "FS_JAVA_OPTS=-Dlog4j.configurationFile=/usr/share/fscrawler/config/log4j2.xml"
    networks: 
      - elastic
    command: fscrawler haru --trace
    depends_on:
      elasticsearch:
          condition: service_healthy

  # search web service
  web:
    image: custom-web:latest
    ports:
      - "3000:3000"
    networks:
      - elastic
    depends_on:
      elasticsearch:
          condition: service_healthy

volumes:
  esdata01:
    driver: local

networks:
  elastic:
    driver: bridge

서비스를 기동한다.

1
$ docker-compose up -d

검색할 문서를 documents 폴더에 넣고 5분을 기다린다.
서비스를 위한 준비는 끝났다.

서비스 확인

아래 주소에 접속하여 각각의 서비스를 확인한다.

서비스명접속주소
Elasticsearchhttp://localhost:9200
Kibana http://localhost:5601
Webhttp://localhost:3000

Web에서 검색 내용을 입력하면 유사도 순으로 검색 결과가 출력되는 것을 확인할 수 있다.

elasticsearch 주요 Restful API

  • 현재 haru 인덱스에 등록된 문서의 수를 리턴한다.
    1
    
    (GET) http://localhost:9200/haru/_count 
    
  • 문서를 검색한다.
    1
    
    (GET) http://localhost:9200/haru/_search?size=10000&q=검색문장
    
  • Insert (인덱스id 11에 해당 내용을 입력한다.)
    1
    2
    3
    4
    5
    6
    
    (POST) http://localhost:9200/haru/_doc/11
    {
      "first_name":"John",
      "last_name":"Smith",
      "age":25,
    }
    
  • Select (인덱스 id가 11에 내용을 조회한다.)
    1
    
    (GET) http://localhost:9200/haru/_doc/11
    
  • Delete (인덱스 id가 11인 데이터를 삭제한다.)
    1
    
    (DELETE) http://localhost:9200/haru/_doc/11
    

Reference

  1. Elasticsearch Site
  2. fscrawler Site