ElasticSearch

ElasticSearch란, 강력한 오픈소스 검색 및 분석 엔진

Elasticsearch vs RDB

  • RDB는 기본적으로 행 단위로 저장을 한다.
  • Elasticsearch는 단어 기반 역인덱스 방식으로 저장 한다.

    이러한 특징 때문에 단어 기반으로 쉽고 빠르게 검색에 특화 될 수 있는 것이다.

용도

  • 신속한 데이터 저장
  • 실시간 검색
  • 로그 분석
  • 문서 검색

주요 특징

  • 실시간 검색 및 분석
    • 데이터를 실시간으로 색인하고 검색할 수 있어서 사용자에게 즉각적으로 데이터를 제공
  • 확장성
    • 클러스터링을 통해 데이터 및 처리 능력을 수평적으로 확장 가능

      클러스터링이란, 여러대의 서버를 하나의 논리적인 시스템(클러스터)으로 묶어서 사용하는 것
      때문에 필요에 따라 서버를 추가하며 클러스터의 규모를 늘릴 수 있다.

  • 높은 가용성
    • 데이터가 **여러 노드(서버)에 복제되므로 단일 노드 장애에도 시스템이 정상 작동 가능
  • 다양한 데이터 유형 지원
    • 구조화된 데이터뿐 아니라 텍스트, 숫자, 지리 공간 데이터 등 다양한 유형의 데이터 처리 가능
  • 강력한 분석 기능
    • Kibana와 연동되어 데이터에 다양한 시각화와 분석을 제공합니다.

      Kibana란, Elasticsearch와 함께 사용되는 데이터 분석 및 시각화 도구
      Elasticsearch에 저장된 데이터를 분석해서 한눈에 보기 쉽게 시각화해줌

이러한 장점들 때문에 Elasticsearch를 사용한다.
진행 과정의 예시를 보고 이해해보자

과정 예시

  1. 사용자가 상품을 검색합니다.
  2. Elasticsearch의 전문 검색기능으로 빠르고 정확한 검색 결과 제공
  3. 사용자의 검색, 구매, 페이지 조회 데이터 등을 Elasticsearch에 저장하고 분석하여 개인에 맞는 상품 추천 기능 구현 가능
  4. 이렇게 사용자들이 웹 사이트를 사용할때에 로그 데이터를 Elasticsearch에 저장할 수 있다.
  5. 저장시킨 로그 데이터를 Kibana를 통해 시각화하여 모니터링을 할 수 있다.
  6. 사용자의 행동 분석또한 Elasticsearch에 저장시킬 수 있다.
  7. 사용자의 행동 정보를 Elasticsearch에 저장하고 이 또한 Kibana를 통해 시각화해서 패턴을 파악한다.
  8. 이러한 정보로 마케팅 전략을 수립할 수 있다.

Elasticsearch 구현

우선 Elasticsearch는 SpringBoot에서 제공하는 서버와 다른 서버를 사용한다. 때문에 도커를 사용해서 서버를 빌려와야하고 이 서버에 접근할 수 있는 Entity,Controller, Service, Repository를 구현해야한다.

의존성 추가

    // elasticsearch 의존성 시작
    implementation 'org.springframework.boot:spring-boot-starter-data-elasticsearch'
    //elasticsearch 의존성 끝

SpringBoot 구현

Entity

@Getter
@Setter
@Document(indexName = "team")
@Setting(replicas = 0)

public class TeamDocument {
    @Id
    private String id;

    @Field(type = FieldType.Integer, index = false, docValues = false)
    private int teamId;

    @Field(type = FieldType.Text, analyzer = "nori")
    private String name;

    @Field(type = FieldType.Text, index = false, docValues = false)
    private String comment;

    
}

Controller

@Tag(name = "검색 API", description = "검색 API Swagger")
@RestController
@RequestMapping("/api/team/search")
public class SearchController {
    private final TeamSearchService teamSearchService;

    public SearchController(TeamSearchService teamSearchService) {
        this.teamSearchService = teamSearchService;
    }

    @ApiResponses({
            @ApiResponse(responseCode = "200", description = "성공"),
            @ApiResponse(responseCode = "401", description = "인증 실패"),
            @ApiResponse(responseCode = "404", description = "사용자 없음"),
            @ApiResponse(responseCode = "500", description = "서버 오류")
    })
    @Operation(summary = "팀 검색", description = "<strong>팀 이름</strong>를 통해 팀 검색을 한다.")
    @GetMapping(value = "/{name}", produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<?> getTeamByName(@PathVariable("name") String name) {
        return ResponseEntity.status(200).body(teamSearchService.searchTeamByName(name));
    }

    @ApiResponses({
            @ApiResponse(responseCode = "200", description = "성공"),
            @ApiResponse(responseCode = "401", description = "인증 실패"),
            @ApiResponse(responseCode = "404", description = "사용자 없음"),
            @ApiResponse(responseCode = "500", description = "서버 오류")
    })
    @Operation(summary = "팀 가입(elasticsearch)", description = "<strong>팀 이름</strong>를 통해 팀 가입(elasticsearch)을 한다.")
    @PostMapping(value = "/", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<?> create(@RequestBody TeamDocument document) {
        teamSearchService.createTeam(document);
        return ResponseEntity.status(200).body(BaseResponseBody.of("팀이 생성되었습니다(elasticsearch)", 200));
    }

}

Service

  • TeamSearchService
public interface TeamSearchService {
    TeamDocument createTeam(TeamDocument teamDocument);
    
    List<TeamDocument> searchTeamByName(String name);
}
  • TeamSearchServiceImpl
@Service
@RequiredArgsConstructor
public class TeamSearchServiceImpl implements TeamSearchService {

    private final TeamSearchRepository teamSearchRepository;


    @Override
    public TeamDocument createTeam(TeamDocument teamDocument) {
        return teamSearchRepository.save(teamDocument);
    }

    @Override
    public List<TeamDocument> searchTeamByName(String name) {
        return teamSearchRepository.findByName(name);
    }
}

Repository

public interface TeamSearchRepository extends ElasticsearchRepository<TeamDocument, Integer> {
    List<TeamDocument> findByName(String name);
}

이제 관련 Elasticsearch 관련 객체들은 구현했으니 설정클래스를 작성한다.

@Configuration
public class ElasticsearchConfig extends ElasticsearchConfiguration {
//    @Value("${spring.elasticsearch.username}")
//    private String username;
//
//    @Value("${spring.elasticsearch.password}")
//    private String password;

    @Value("${spring.elasticsearch.uris}")
    private String[] esHost;

    @Override
    public ClientConfiguration clientConfiguration() {
        return ClientConfiguration.builder()
                .connectedTo(esHost)
//                .withBasicAuth(username, password)
                .build();
    }
}

도커 구현

  1. Elasticsearch 오픈소스 이미지를 Pull 합니다

    주의할점은 태그(버전)을 꼭 명시해서 Pull 한다.

  2. 가져온 이미지를 run 시켜준다

    이때 cmd를 사용해서 실행시키던가 DockerDesktop을 사용해서 실행시키는 방법이 있는데 cmd를 사용한다면 elasticsearch를 컨테이너화 시키기위한 설정을 명령어로 모두 작성해야한다.
    docker run -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" docker.elastic.co/elasticsearch/elasticsearch:8.7.0
    저자는 DockerDesktop을 사용해서 실행했기때문에 이미지를 실행시킬때 나오는 추가 설정에서

해당 환경변수를 지정해주고 Run 시켰다

  • 해당 environment 설정할때 https 대신 http 환경을 원하면 false로 설정하고 아니면 true 설정
    이 과정에서 Elastic 서버에 접근할 수 없다는 에러가 계속 발생했고 환경변수, 버전 변경 사용자 비밀번호 설정 등 많이 시도했지만 고쳐지지 않음.
    결국 알아낸것은 elastic이 https 접근을 해야하는데 이 접근을 막아서 접근하지 못했다.
    때문에 위에 환경변수 설정에서 보면 xpack.security 설정을 모두 false로 해줘서 https설정을 해제하여 해결함
  1. 정상적으로 도커 컨테이너가 실행되었다면 SpringBoot 실행
    http://localhost:9200 이렇게 접근했을때 화면이 나와야한다

    마지막으로 application.properties 설정에서
    spring.jpa.hibernate.ddl-auto=create
    이 설정을 update로 해놓으면 에러가 발생하더라 이것도 시간많이 잡아먹었다.

profile
멋있는 사람 - 일단 하자

0개의 댓글

Powered by GraphCDN, the GraphQL CDN