Elasticsearch 마스터 — 검색 엔진 프로젝트 설계

2026-05-03확률과 통계 마스터 노트

Elasticsearch 마스터 노트 시리즈 9편. 실전 검색 엔진 프로젝트 설계, DB와 ES의 역할 분담·동기화 전략(application·CDC·Outbox), Index 명명·Alias 패턴, ILM (Index Lifecycle Management), Shard 수 결정 가이드, 무중단 Reindex 워크플로우, 검색 품질 모니터링까지.

이 글은 Elasticsearch 마스터 노트 시리즈의 아홉 번째 편입니다. 1~8편이 ES 자체였다면, 이번엔 실전 시스템 설계 — 검색 엔진 프로젝트.

DB가 단일 진실, ES가 검색. 그 사이 동기화가 핵심. Index·Alias·ILM 패턴으로 운영. 무중단 Reindex 표준.

처음 시스템 설계가 어렵게 느껴지는 이유

처음 이 단원이 어렵게 느껴지는 이유는 두 가지예요. 첫째, DB와 ES 동기화 방법이 막연합니다. 둘째, Reindex 무중단이 어떻게 가능한지 알기 어렵습니다.

해결법은 한 가지예요. "Alias로 인덱스 이름 가리기" + "DB가 단일 진실" 두 줄. Alias 전환 = 무중단. DB 변경 → ES 동기. 이 두 원칙이 시스템 설계 토대.

검색 엔진 아키텍처

[Client]
   ↓
[API Server (Spring Boot)]
   ├── Read   → ES (검색)
   └── Write  → DB (운영 데이터)
                ↓ 동기화
              ES (인덱싱)

DB = 단일 진실, ES = 검색 최적화 사본.

DB ↔ ES 동기화 전략

1. Application-Level Dual Write

@Transactional
public Product save(Product p) {
    Product saved = dbRepo.save(p);
    esRepo.save(saved);   # 같은 메서드에서 둘 다
    return saved;
}

장점 — 단순. 단점:

  • DB 트랜잭션과 ES 분리 (ES 실패 = 일관성 깨짐)
  • 부분 실패 처리 어려움

2. CDC (Change Data Capture) — Debezium

PostgreSQL → Debezium (CDC) → Kafka → ES Sink Connector → ES

DB 트랜잭션 로그 감지 → Kafka → ES.

장점:

  • DB 보장 = ES 보장
  • 비동기·확장
  • 모든 변경 자동 (앱 코드 변경 X)

단점 — 복잡·운영 부담.

3. Transactional Outbox + Kafka

Kafka 시리즈 7편 패턴:

@Transactional
public void save(Product p) {
    productRepo.save(p);
    outboxRepo.save(new Outbox("product-update", toJson(p)));
}

# 폴러 (별도 프로세스)
List<Outbox> events = outboxRepo.fetchPending();
for (Outbox e : events) {
    kafkaProducer.send("product-events", e);
    outboxRepo.delete(e);
}

# Kafka Consumer
@KafkaListener(topics = "product-events")
public void onProduct(ProductEvent event) {
    esRepo.save(event.toProduct());
}

여기서 정말 중요한 시험 함정 — 운영 표준 = Outbox 또는 CDC. 단순 Dual Write는 일관성 위험. 큰 시스템 = CDC, 중간 = Outbox.

Index 명명 규칙

단일 인덱스

products            # 단순

시간 기반

logs-2024-01
logs-2024-02
logs-2024-03

로그·시계열에. ILM과 결합.

버전·환경

products-v1
products-v2
products-prod
products-dev

Reindex 마이그레이션·환경 분리.

Alias — 인덱스 이름 가리기

# Alias 생성
POST /_aliases
{
  "actions": [
    { "add": { "index": "products-v1", "alias": "products" } }
  ]
}

클라이언트는 항상 products (alias) 사용:

@Document(indexName = "products")
public class Product { ... }

여기서 정말 중요한 시험 함정 — 운영 = 모든 인덱스에 Alias. Reindex·재배치 시 무중단. 직접 인덱스 이름 사용 X.

다중 인덱스 Alias

POST /_aliases
{
  "actions": [
    { "add": { "index": "logs-2024-01", "alias": "logs-current" } },
    { "add": { "index": "logs-2024-02", "alias": "logs-current" } }
  ]
}

여러 인덱스 → 하나의 alias. 시계열에.

Filter Alias

POST /_aliases
{
  "actions": [
    {
      "add": {
        "index": "products",
        "alias": "active-products",
        "filter": { "term": { "status": "active" } }
      }
    }
  ]
}

Alias가 자동 필터링. 권한·뷰 분리.

Write Alias vs Read Alias

# Write Alias — 한 인덱스만
{ "add": { "index": "products-v2", "alias": "products-write", "is_write_index": true } }

# Read Alias — 여러 인덱스
{ "add": { "index": "products-v1", "alias": "products-read" } }
{ "add": { "index": "products-v2", "alias": "products-read" } }

Reindex 중 — write는 v2만, read는 v1+v2 (점진 전환).

무중단 Reindex 워크플로우

1. 새 인덱스 생성 (products-v2, 새 매핑)
2. 기존 데이터 Reindex (products-v1 → products-v2)
3. 동기화 시작 시점 기록 (timestamp)
4. 그 시점 이후 변경 사항 적용
   - Application: dual write
   - CDC: 자동
5. Alias 전환 — products → products-v2
6. 검증 — 검색 결과 비교
7. products-v1 삭제

여기서 시험 함정이 하나 있어요. Reindex 중 신규 데이터 처리 = 가장 어려운 부분. write alias 또는 dual write로 처리. 또는 CDC가 자동.

ILM — Index Lifecycle Management

PUT /_ilm/policy/logs-policy
{
  "policy": {
    "phases": {
      "hot": {
        "actions": {
          "rollover": { "max_size": "50GB", "max_age": "7d" }
        }
      },
      "warm": {
        "min_age": "7d",
        "actions": {
          "shrink": { "number_of_shards": 1 },
          "forcemerge": { "max_num_segments": 1 }
        }
      },
      "cold": {
        "min_age": "30d",
        "actions": { "freeze": {} }
      },
      "delete": {
        "min_age": "365d",
        "actions": { "delete": {} }
      }
    }
  }
}

4 단계 — Hot (최신·자주 쓰임) / Warm (덜) / Cold (드물게) / Delete.

자동 전환·관리. 시계열·로그에 표준.

Shard 수 결정

Shard 크기 권장: 30~50 GB

총 데이터: 1 TB → Shard 25개 정도
일별 증가: 10 GB·30 GB Shard → 3일치 1 Shard

여기서 정말 중요한 시험 함정 — 너무 적은 Shard = 단일 노드 부담 / 너무 많은 = 메타데이터 폭주. 30~50 GB per shard 권장. 작은 인덱스 (수 GB) = Shard 1·Replica 1 충분.

Replica 수 결정

Replica 0: HA X·인덱싱 빠름 (초기 로드만)
Replica 1: 표준 (1 노드 다운까지 OK)
Replica 2+: 더 안전·읽기 분산 ↑

Read 위주 = Replica ↑·Read 분산.

검색 성능 튜닝

# refresh interval (기본 1초)
PUT /products/_settings
{
  "index": {
    "refresh_interval": "30s"        # 30초마다 = 검색 가능
  }
}

# search_idle 시 자동 단축
"search.idle.after": "30s"

여기서 시험 함정이 하나 있어요. refresh_interval ↑ = 인덱싱 빠름·검색 지연 ↑. 운영 — 30초가 일반. 즉시 검색 필요 = 1초.

Coordinating Node

큰 클러스터:
  Coordinating Node (검색 라우팅·집계 결합) 분리
  Data Node 부하 ↓

검색 품질 모니터링

메트릭

- Query Latency (p50·p99)
- Query Rate (QPS)
- Failed Queries
- Slow Queries (>1s)
- Cache Hit Rate

Slow Log

PUT /products/_settings
{
  "index.search.slowlog.threshold.query.warn": "1s",
  "index.search.slowlog.threshold.query.info": "500ms"
}

느린 쿼리 자동 로그.

사용자 메트릭

- 검색 후 클릭률 (CTR)
- 빈 검색 결과 비율
- "Did you mean?" 클릭률
- 첫 결과 클릭 비율

검색 품질 직접 측정. A/B 테스트.

캐시 전략

# Field Data Cache (집계용)
indices.fielddata.cache.size: 30%

# Query Cache (필터 캐시)
indices.queries.cache.size: 10%

# Request Cache (자주 쓰는 검색)
indices.requests.cache.size: 1%

Filter Context = Query Cache 자동.

백업·복구

Snapshot

# Repository 등록 (S3·로컬·HDFS)
PUT /_snapshot/my_backup
{
  "type": "s3",
  "settings": {
    "bucket": "my-es-backup",
    "region": "us-east-1"
  }
}

# Snapshot 생성
PUT /_snapshot/my_backup/snapshot_2024-01-15
{
  "indices": "products,users",
  "include_global_state": false
}

# 복구
POST /_snapshot/my_backup/snapshot_2024-01-15/_restore

여기서 정말 중요한 시험 함정 — Snapshot은 운영 필수. 정기 백업 (일·시간)·다른 리전 보관. 재해 복구.

운영 체크리스트

✓ DB가 단일 진실
✓ ES = 검색·분석 사본
✓ 동기화 — Outbox 또는 CDC
✓ 모든 인덱스에 Alias
✓ 명시 매핑 (Dynamic 회피)
✓ Shard 30~50 GB 권장
✓ Replica 1 (운영 표준)
✓ ILM (시계열·로그)
✓ refresh_interval 30s (운영)
✓ Slow Log 활성
✓ 정기 Snapshot (S3 등)
✓ Master 3+ (정족수)
✓ 모니터링 — Prometheus·Kibana
✓ 검색 품질 — CTR·빈 결과율

시험 직전 한 번 더 — 자주 헷갈리는 함정 모음

여기까지가 9편의 핵심입니다. 시험 직전 또는 실무에서 헷갈릴 때 다시 펼쳐 볼 수 있게 압축 노트로 마무리할게요.

  • 아키텍처 — DB (단일 진실) + ES (검색)
  • 동기화 — Application·CDC (Debezium)·Outbox + Kafka
  • 운영 = Outbox 또는 CDC (단순 Dual Write 위험)
  • Index 명명 — 단일·시간 기반·버전 (v1, v2)
  • 모든 인덱스에 Alias 권장
  • Alias = 무중단 교체
  • 다중 인덱스 Alias·Filter Alias·Write·Read Alias
  • 무중단 Reindex — 새 인덱스 + Reindex + dual write·CDC + Alias 전환
  • ILM = 시계열·로그 자동 관리
  • 4 단계 — Hot / Warm / Cold / Delete
  • Shard 권장 = 30~50 GB
  • 작은 인덱스 = 1 Shard 충분
  • Replica 1 = 운영 표준
  • refresh_interval 30s = 운영 (1s = 즉시 검색)
  • Coordinating Node 분리 (큰 클러스터)
  • Slow Log 활성
  • 검색 품질 — CTR·빈 결과율·"Did you mean?"
  • A/B 테스트
  • Snapshot 정기 백업 (S3 등)
  • 다른 리전 보관 (재해 복구)
  • 운영 체크 — DB 단일 진실·동기화·Alias·매핑·Shard·Replica·ILM·refresh·Slow Log·Snapshot·Master 3+·모니터링·검색 품질

시리즈 다른 편

공식 문서: Index Lifecycle Management / Snapshot and Restore 에서 더 깊이.

다음 글(10편, 마지막)에서는 Security — 인증·인가·TLS·X-Pack·Audit Log까지 시리즈 마무리.

※ 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

답글 남기기

error: Content is protected !!