🗄️ 데이터베이스

Elasticsearch

엘라스틱서치

분산 검색/분석 엔진. 로그 분석, 전문 검색에 사용. ELK 스택의 핵심.

📖 상세 설명

Elasticsearch란?

Elasticsearch는 Apache Lucene 기반의 분산형 오픈소스 검색 및 분석 엔진입니다. RESTful API를 통해 대용량 데이터를 실시간으로 저장, 검색, 분석할 수 있으며, 전문 검색(Full-text Search), 로그 분석, 시계열 데이터 분석에 널리 사용됩니다.

핵심 개념

  • Index: 문서의 논리적 컬렉션 (RDB의 테이블과 유사)
  • Document: JSON 형태의 데이터 단위 (RDB의 행과 유사)
  • Field: 문서 내 키-값 쌍 (RDB의 컬럼과 유사)
  • Shard: 인덱스를 분할한 물리적 단위, 수평 확장 가능
  • Replica: 샤드의 복제본, 고가용성과 읽기 성능 향상

ELK/Elastic Stack

  • Elasticsearch: 검색 및 분석 엔진
  • Logstash: 데이터 수집, 변환, 전송 파이프라인
  • Kibana: 데이터 시각화 및 대시보드
  • Beats: 경량 데이터 수집기 (Filebeat, Metricbeat 등)

주요 활용 사례

  • 전문 검색: 웹사이트 검색, 상품 검색, 문서 검색
  • 로그/이벤트 분석: 애플리케이션 로그, 보안 이벤트
  • APM: 애플리케이션 성능 모니터링
  • SIEM: 보안 정보 및 이벤트 관리
  • 메트릭 분석: 시계열 데이터 시각화

검색 방식

  • Match Query: 텍스트 분석 후 매칭 (형태소 분석 적용)
  • Term Query: 정확히 일치하는 값 검색 (분석 없음)
  • Bool Query: must, should, must_not, filter 조합
  • Aggregation: 그룹화, 통계, 버킷팅

💻 코드 예제

인덱스 생성 및 매핑

HTTP
PUT /products
{
  "settings": {
    "number_of_shards": 3,
    "number_of_replicas": 1,
    "analysis": {
      "analyzer": {
        "korean": {
          "type": "custom",
          "tokenizer": "nori_tokenizer"
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "name": {
        "type": "text",
        "analyzer": "korean",
        "fields": {
          "keyword": { "type": "keyword" }
        }
      },
      "description": { "type": "text", "analyzer": "korean" },
      "price": { "type": "integer" },
      "category": { "type": "keyword" },
      "created_at": { "type": "date" },
      "tags": { "type": "keyword" }
    }
  }
}

문서 색인 및 조회

HTTP
# 문서 색인
POST /products/_doc
{
  "name": "맥북 프로 16인치",
  "description": "M3 Pro 칩 탑재 고성능 노트북",
  "price": 3490000,
  "category": "electronics",
  "tags": ["laptop", "apple", "premium"],
  "created_at": "2024-01-15"
}

# ID 지정 색인
PUT /products/_doc/1
{
  "name": "아이패드 프로",
  "description": "M2 칩, Liquid Retina XDR 디스플레이",
  "price": 1729000,
  "category": "electronics",
  "tags": ["tablet", "apple"]
}

# 문서 조회
GET /products/_doc/1

검색 쿼리

HTTP
# Match Query (전문 검색)
GET /products/_search
{
  "query": {
    "match": {
      "description": "고성능 노트북"
    }
  }
}

# Bool Query (복합 조건)
GET /products/_search
{
  "query": {
    "bool": {
      "must": [
        { "match": { "description": "노트북" } }
      ],
      "filter": [
        { "term": { "category": "electronics" } },
        { "range": { "price": { "gte": 1000000, "lte": 3000000 } } }
      ],
      "should": [
        { "term": { "tags": "premium" } }
      ],
      "minimum_should_match": 1
    }
  },
  "sort": [
    { "price": "asc" },
    "_score"
  ],
  "from": 0,
  "size": 10
}

Aggregation (집계)

HTTP
GET /products/_search
{
  "size": 0,
  "aggs": {
    "category_stats": {
      "terms": {
        "field": "category",
        "size": 10
      },
      "aggs": {
        "avg_price": { "avg": { "field": "price" } },
        "max_price": { "max": { "field": "price" } },
        "price_ranges": {
          "range": {
            "field": "price",
            "ranges": [
              { "to": 1000000 },
              { "from": 1000000, "to": 2000000 },
              { "from": 2000000 }
            ]
          }
        }
      }
    },
    "daily_sales": {
      "date_histogram": {
        "field": "created_at",
        "calendar_interval": "day"
      }
    }
  }
}

Python (elasticsearch-py)

Python
from elasticsearch import Elasticsearch

es = Elasticsearch(['http://localhost:9200'])

# 문서 색인
doc = {
    'name': '맥북 에어',
    'price': 1690000,
    'category': 'electronics'
}
es.index(index='products', id=1, document=doc)

# 검색
query = {
    'bool': {
        'must': [{'match': {'name': '맥북'}}],
        'filter': [{'range': {'price': {'lte': 2000000}}}]
    }
}
results = es.search(index='products', query=query)

for hit in results['hits']['hits']:
    print(f"Score: {hit['_score']}, Name: {hit['_source']['name']}")

# Bulk 색인 (대량 데이터)
from elasticsearch.helpers import bulk

actions = [
    {'_index': 'products', '_id': i, '_source': {'name': f'Product {i}'}}
    for i in range(1000)
]
bulk(es, actions)

💬 현업 대화 예시

백엔드 개발자
"상품 검색 기능이 필요한데, 'ㄴㅌㅂ' 치면 '노트북' 나오게 할 수 있어요?"
검색 엔지니어
"초성 검색이네. 한글 형태소 분석기(Nori) 설정하고, 초성 변환 필터 추가하면 돼. Elasticsearch에서 custom analyzer 만들어서 적용하면 자동완성까지 구현 가능해."
DevOps 엔지니어
"ES 클러스터 디스크 사용량이 90% 넘었어요. 어떻게 해야 하죠?"
DBA
"먼저 오래된 인덱스 ILM으로 삭제 정책 설정해. 그리고 노드 추가하거나, 샤드 수 조정해서 데이터 리밸런싱해야 해. 95% 넘으면 read-only 모드 걸려서 큰일 나."
프론트엔드 개발자
"검색 결과에서 왜 관련 없어 보이는 상품이 먼저 나와요?"
검색 엔지니어
"TF-IDF 기반 기본 스코어링 때문이야. function_score로 인기도, 최신성 등 비즈니스 로직 반영해서 스코어 부스팅 해줘야 해. 아니면 Learning to Rank 모델 적용하는 것도 방법이고."

⚠️ 주의사항

⚠️ Primary Data Store로 사용 금지
Elasticsearch는 검색 엔진이지 Primary Database가 아닙니다. 데이터 유실 가능성이 있으므로 원본 데이터는 별도 DB에 저장하고, ES는 검색용 인덱스로만 사용하세요.
⚠️ 매핑 변경 불가
인덱스 생성 후 필드 타입 변경이 불가합니다. 스키마 변경이 필요하면 새 인덱스 생성 후 Reindex해야 합니다. 초기 매핑 설계가 매우 중요합니다.
⚠️ 메모리 관리
JVM 힙 메모리를 전체 RAM의 50% 이하, 최대 32GB로 설정하세요. 나머지는 파일 시스템 캐시용으로 남겨둬야 검색 성능이 좋습니다. OOM 발생 시 노드 다운됩니다.
⚠️ 샤드 수 계획
샤드가 너무 많으면 클러스터 오버헤드 증가, 너무 적으면 확장 어려움. 샤드당 20-50GB, 노드당 600-800개 샤드 이하가 권장사항입니다. 생성 후 변경 불가합니다.

🔗 관련 용어

📚 더 배우기