어제 오늘 내일

[Spring Boot] "RDB에서 검색하면 느려터져요..." Elasticsearch로 고성능 검색 엔진 구축하기 본문

IT/SpringBoot

[Spring Boot] "RDB에서 검색하면 느려터져요..." Elasticsearch로 고성능 검색 엔진 구축하기

hi.anna 2026. 3. 29. 01:57

 

우리가 흔히 쓰는 관계형 데이터베이스(RDB)는 "저장""정합성"에 특화되어 있지, "검색"에는 약합니다.
특히 본문 검색(Full-text Search)이나 오타 교정, 자동 완성 같은 기능은 RDB로 구현하기가 매우 까다롭고 느립니다.

이 문제를 해결하기 위해 Elasticsearch를 도입해 보겠습니다.


1. 왜 Elasticsearch 인가요? (역색인 구조)

RDB는 책의 본문을 처음부터 끝까지 다 읽으면서 찾는 방식(Scan)이라면, Elasticsearch는 책의 맨 뒤에 있는 "색인(Index)"을 보고 찾는 방식입니다.

  • RDB: "강남"이라는 단어가 들어간 행을 다 뒤진다. -> 느림
  • Elasticsearch: "강남" -> [1번 문서, 5번 문서, 100번 문서] -> 즉시 리턴

역색인(Inverted Index) 구조 덕분에 데이터가 아무리 많아도 검색 속도가 거의 일정하게 빠릅니다.


2. 설치하기 (Docker Compose)

엘라스틱서치(엔진)와 키바나(시각화 도구)를 함께 설치합니다.

docker-compose.yml

version: '3'
services:
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.17.10
    container_name: elasticsearch
    environment:
      - discovery.type=single-node
      - xpack.security.enabled=false
    ports:
      - "9200:9200"

  kibana:
    image: docker.elastic.co/kibana/kibana:7.17.10
    container_name: kibana
    environment:
      - ELASTICSEARCH_HOSTS=http://elasticsearch:9200
    ports:
      - "5601:5601"
    depends_on:
      - elasticsearch

docker-compose up -d 후 브라우저에서 http://localhost:5601 (Kibana)에 접속되면 성공입니다.


3. 스프링 부트 설정 (Spring Data Elasticsearch)

JPA를 써보셨다면 아주 익숙하실 겁니다. 스프링은 Spring Data Elasticsearch라는 강력한 라이브러리를 제공합니다.

build.gradle

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-elasticsearch'
}

application.yml

spring:
  elasticsearch:
    uris: http://localhost:9200

4. 도큐먼트(Document) 정의하기

RDB의 Entity와 비슷한 개념입니다.

@Getter
@Setter
@Document(indexName = "blog_post") // 인덱스 이름 (RDB의 테이블)
public class BlogPostDocument {

    @Id
    private String id;

    @Field(type = FieldType.Text) // 형태소 분석이 필요한 필드
    private String title;

    @Field(type = FieldType.Text)
    private String content;

    @Field(type = FieldType.Keyword) // 정확히 일치해야 하는 필드 (태그 등)
    private String category;
}

5. 리포지토리(Repository) 만들기

이게 하이라이트입니다. JPA처럼 인터페이스만 만들면 끝납니다.

public interface BlogPostRepository extends ElasticsearchRepository<BlogPostDocument, String> {

    // 1. 제목에 검색어가 포함된 문서 찾기
    List<BlogPostDocument> findByTitleContaining(String keyword);

    // 2. 제목이나 내용에 검색어가 포함된 문서 찾기 (OR 검색)
    List<BlogPostDocument> findByTitleContainingOrContentContaining(String title, String content);
}

6. 서비스에서 사용하기

이제 RDB 대신 Elasticsearch를 호출하면 됩니다.

@Service
@RequiredArgsConstructor
public class BlogSearchService {

    private final BlogPostRepository blogPostRepository;

    public List<BlogPostDocument> search(String keyword) {
        // RDB였다면 3초 걸릴 쿼리가 0.05초 만에 나옵니다.
        return blogPostRepository.findByTitleContaining(keyword);
    }

    // 데이터 저장 (색인)
    public void save(BlogPostDocument post) {
        blogPostRepository.save(post);
    }
}

7. 주의사항: 데이터 동기화 (Sync)

가장 큰 고민거리는 "MySQL에 글이 써졌을 때, 어떻게 Elasticsearch에도 넣지?"입니다.

  1. Dual Write: 서비스 코드에서 rdbRepository.save() 하고 바로 esRepository.save()를 호출한다. (가장 쉽지만, 하나가 실패하면 데이터가 꼬임)
  2. Logstash / Kafka Connect: MySQL의 바이너리 로그(Binlog)를 읽어서 비동기로 Elasticsearch에 밀어 넣는다. (구축이 복잡하지만 안정적)

초기에는 Dual WriteSpring Event(@EventListener)를 활용해서 동기화하는 것을 추천합니다.


마치며

오늘의 결론입니다.

  1. RDB는 저장소, Elasticsearch는 검색 엔진으로 역할을 분리하자.
  2. 역색인 기술 덕분에 대용량 데이터에서도 검색 속도가 압도적으로 빠르다.
  3. Spring Data Elasticsearch를 쓰면 JPA처럼 쉽게 개발할 수 있다.

이제 여러분의 서비스는 구글처럼 빠른 검색 속도를 자랑하게 되었습니다.

다음 포스팅에서는 "서버가 1,000명까진 버티는데 10,000명이 오면 죽어요..." 내 서버의 한계점을 찾아내는 nGrinder 부하 테스트(Load Test)에 대해 알아보겠습니다.

도움이 되셨다면 좋아요와 댓글 부탁드립니다! 😊

반응형
Comments