본문 바로가기
Spring 7기 프로젝트/모임 플렛폼 프로젝트

Elasticsearch 완벽 가이드: 실무에서 알아야 할 모든 것

by JuNo_12 2025. 7. 25.

들어가며

현대 웹 애플리케이션에서 검색 기능은 사용자 경험을 좌우하는 핵심 요소입니다. 사용자들은 구글과 같은 빠르고 정확한 검색 결과를 기대하며, 이러한 요구사항을 충족하기 위해 많은 개발팀들이 Elasticsearch를 도입하고 있습니다.

이 글에서는 Elasticsearch의 핵심 개념부터 실무 활용법까지 체계적으로 다루어보겠습니다.


Elasticsearch란 무엇인가?

정의와 특징

Elasticsearch(이하 ES)는 Apache Lucene 기반의 분산형 검색 엔진입니다. 중요한 점은 ES는 데이터베이스가 아닌 검색 전용 도구라는 것입니다. 데이터를 저장하긴 하지만, 주된 목적은 빠른 검색과 실시간 분석에 있습니다.

관계형 데이터베이스와의 대응

RDBMS        ↔    Elasticsearch
Database     ↔    Index (인덱스)
Table        ↔    Type (타입) - 7.x부터 deprecated
Row          ↔    Document (문서)
Column       ↔    Field (필드)
Schema       ↔    Mapping (매핑)

핵심 개념 이해하기

인덱스(Index) - 서류함

인덱스는 같은 종류의 데이터를 모아둔 서류함이라고 생각하시면 됩니다.

📁 상품 서류함 (products 인덱스)
📁 사용자 서류함 (users 인덱스)  
📁 주문 서류함 (orders 인덱스)

 

도큐먼트(Document) - 개별 서류

도큐먼트는 서류함 안에 들어있는 하나하나의 서류입니다.

// 상품 도큐먼트 예시
{
  "_index": "products",
  "_id": "1",
  "_source": {
    "name": "아이폰 15 프로",
    "price": 1500000,
    "category": "스마트폰",
    "brand": "Apple"
  }
}

 

필드(Field) - 서류의 항목들

필드는 서류에 적힌 각각의 항목입니다.

📄 아이폰 15 서류
├── 상품명: 아이폰 15 프로     (name 필드)
├── 가격: 1,500,000원        (price 필드)
├── 카테고리: 스마트폰        (category 필드)
└── 브랜드: Apple           (brand 필드)

 

매핑(Mapping) - 서류 양식

매핑은 서류를 어떤 양식으로 작성할지 정하는 규칙입니다.

{
  "mappings": {
    "properties": {
      "name": {
        "type": "text",
        "analyzer": "korean"
      },
      "price": {
        "type": "integer"
      },
      "category": {
        "type": "keyword"
      },
      "created_date": {
        "type": "date"
      }
    }
  }
}

역색인(Inverted Index) - ES의 핵심 동작 원리

역색인이란?

역색인은 책 뒤쪽의 '찾아보기' 색인과 같은 개념입니다. 이것이 ES가 빠른 이유입니다.

일반적인 방식:
문서1: "아이폰 15 프로는 최고의 스마트폰입니다"
문서2: "갤럭시 S24도 훌륭한 스마트폰입니다"
문서3: "아이폰과 갤럭시를 비교해보세요"

역색인 방식:
"아이폰" → 문서1, 문서3
"갤럭시" → 문서2, 문서3  
"스마트폰" → 문서1, 문서2

 

검색 과정

// 사용자가 "아이폰 15"로 검색
// 1. 검색어 분석
["아이폰", "15"]

// 2. 역색인에서 찾기
"아이폰" → [문서1, 문서3, 문서5]
"15" → [문서1, 문서2]

// 3. 교집합 계산
결과: [문서1] (두 단어 모두 포함)

// 4. 관련성 점수 계산 후 정렬하여 반환

분산 구조: 노드와 샤드

노드(Node) - 서버

노드는 Elasticsearch가 실행되는 하나의 서버입니다.

🏢 클러스터 (여러 서버로 구성)
├── 🖥️ 노드1 (서버1)
├── 🖥️ 노드2 (서버2)
└── 🖥️ 노드3 (서버3)

샤드(Shard) - 데이터 분할

샤드는 큰 인덱스를 여러 조각으로 나눈 것입니다.

📁 상품 인덱스 (100만개 상품)
↓ (3개 샤드로 분할)
📂 샤드1: 1~33만번 상품
📂 샤드2: 34~66만번 상품  
📂 샤드3: 67~100만번 상품

샤드의 종류

원본 (Primary Shard)    복사본 (Replica Shard)
📂 샤드1-원본      →    📂 샤드1-복사본
📂 샤드2-원본      →    📂 샤드2-복사본
📂 샤드3-원본      →    📂 샤드3-복사본

분산 저장 구조

🖥️ 노드1
├── 📂 샤드1-원본
└── 📂 샤드2-복사본

🖥️ 노드2  
├── 📂 샤드2-원본
└── 📂 샤드3-복사본

🖥️ 노드3
├── 📂 샤드3-원본
└── 📂 샤드1-복사본

언제 Elasticsearch를 사용해야 할까?

사용이 권장되는 상황

전문 검색이 필요한 경우

  • 제품명에 특정 키워드가 포함된 상품 검색
  • 게시글 내용에서 특정 단어 검색
  • 자동완성 기능 구현

위치 기반 검색

  • 특정 지역 근처의 맛집 검색
  • 반경 내 서비스 제공업체 찾기

로그 분석 및 모니터링

  • 애플리케이션 로그 실시간 분석
  • 에러 패턴 분석

 

사용하지 않아도 되는 상황

단순 CRUD 작업 - 회원가입, 로그인 등

정확한 계산이 중요한 업무 - 결제 처리, 재고 관리

복잡한 관계형 데이터 처리 - 여러 테이블 간의 복잡한 JOIN


실무에서의 활용

시스템 아키텍처에서의 위치

ES는 메인 데이터베이스의 보조 역할을 수행합니다:

사용자
    ↓ (검색 요청)
웹 애플리케이션 
    ↓ 
Elasticsearch ← (빠른 검색)
    ↑ (데이터 동기화)
메인 데이터베이스 ← (정확한 데이터 저장)

 

실제 코드 예시

@Service
public class ProductSearchService {
    
    // 상품 등록 - 메인 DB와 ES 동시 저장
    public void createProduct(Product product) {
        productRepository.save(product);        // MySQL에 저장
        elasticsearchService.index(product);    // ES에도 복사
    }
    
    // 상품 검색 - ES에서 검색
    public List<Product> search(String keyword) {
        return elasticsearchService.search(keyword);
    }
    
    // 주문/결제 - 메인 DB만 사용 (정확성 중요)
    public void createOrder(Order order) {
        orderRepository.save(order);  // MySQL에만 저장
    }
}

 

데이터 동기화 방식

실시간 동기화

@Transactional
public void createProduct(Product product) {
    Product saved = productRepository.save(product);
    elasticsearchService.index(saved);
}

배치 동기화

@Scheduled(fixedRate = 300000) // 5분마다
public void syncToElasticsearch() {
    List<Product> changedProducts = productRepository
        .findByUpdatedAtAfter(lastSyncTime);
    elasticsearchService.bulkIndex(changedProducts);
}

샤드 최적화 전략

샤드 크기 권장사항

  • 일반적 권장: 샤드당 50GB 이하
  • 고성능 요구사항: 샤드당 5-10GB

노드당 샤드 개수

  • 경험법칙: 힙 메모리 1GB당 20개 샤드
  • 예시: 30GB 힙 노드 → 최대 600개 샤드

샤드 개수 결정 가이드

데이터 규모별 샤드 설정:
- 소규모 (< 1GB): 샤드 1개
- 중간 규모 (1~10GB): 샤드 2~3개  
- 대규모 (> 10GB): 샤드 5~10개

ES를 메인 데이터베이스로 사용하면 안 되는 이유

주요 한계점

ACID 트랜잭션 미지원 - 결제 중 문제 발생 시 데이터 정합성 보장 어려움

제한적인 JOIN 연산 - 복잡한 관계형 데이터 처리에 부적합

실시간 업데이트 최적화 부족 - 검색 성능 우선 설계

데이터 손실 가능성 - 검색 성능을 위한 일부 정확성 포기

 

올바른 사용 패턴

// 잘못된 사용
public void payOrder(Long orderId, BigDecimal amount) {
    elasticsearchService.updateOrder(orderId, "PAID", amount); // 위험!
}

// 올바른 사용
public void payOrder(Long orderId, BigDecimal amount) {
    // 1. 메인 DB에서 정확한 처리
    Order order = orderRepository.findById(orderId);
    order.pay(amount);
    orderRepository.save(order);
    
    // 2. ES는 검색용으로만 동기화
    elasticsearchService.index(order);
}

팀에서 ES 도입하기

단계별 도입 전략

1단계: 기본 검색 기능

// 기존: DB LIKE 검색 (느림)
SELECT * FROM products WHERE name LIKE '%아이폰%';

// 개선: ES 사용 (빠름)  
elasticsearchRepository.findByNameContaining("아이폰");

2단계: 고급 검색 기능

  • 자동완성, 오타 교정, 연관 검색

3단계: 분석 및 모니터링

  • 로그 분석, 사용자 행동 패턴 분석

실무 설정 가이드

// 인덱스 설정 예시
{
  "settings": {
    "number_of_shards": 3,
    "number_of_replicas": 1
  },
  "mappings": {
    "properties": {
      "name": {
        "type": "text",
        "fields": {
          "keyword": {"type": "keyword"}  // 정확한 매칭용
        }
      },
      "price": {"type": "integer"},
      "tags": {"type": "keyword"}
    }
  }
}

마치며

Elasticsearch는 강력한 검색 엔진이지만 만능 솔루션은 아닙니다. 핵심은 메인 데이터베이스의 보조 역할로서 적절히 활용하는 것입니다.

성공적인 ES 도입을 위한 체크리스트:

  • 명확한 사용 목적 정의 (검색 vs 저장)
  • 적절한 아키텍처 설계 (메인 DB + ES)
  • 단계적 도입 전략 수립
  • 샤드 크기와 개수 최적화
  • 지속적인 성능 모니터링

이러한 요소들을 고려하여 도입한다면, ES는 사용자에게 빠르고 정확한 검색 경험을 제공하는 강력한 도구가 될 것입니다.