선수 개념

이 문서를 읽기 전에 다음 개념을 먼저 이해하세요:

소요 시간: 약 25-30분

전체 비유: 도서관의 신간 입고 프로세스#

인덱싱 전략을 도서관의 신간 입고 프로세스에 비유하면 이해하기 쉽습니다:

도서관 비유Elasticsearch역할
한 권씩 등록단건 인덱싱느리지만 즉시 확인 가능
박스 단위 대량 입고Bulk 인덱싱훨씬 빠름 (10배 이상)
입고 후 서가 배치Refresh검색 가능하게 만듦 (기본 1초)
영구 장서 등록Flush디스크에 영구 저장
입고 일지Translog장애 시 복구용 기록
신간 분류 규칙 템플릿Index Template새 인덱스 자동 설정
오래된 책 → 창고 → 폐기ILM (Index Lifecycle)Hot → Warm → Cold → Delete
구판 → 신판 교체Reindex데이터 마이그레이션
도서 별칭 (시리즈명)Alias인덱스 별명으로 무중단 교체

이처럼 인덱싱 전략은 “도서관에 새 책을 효율적으로 입고하고 관리"하는 프로세스와 같습니다.

대용량 데이터를 효율적으로 저장하기 위한 Bulk 인덱싱, Refresh, Index Lifecycle Management를 배웁니다.

Elasticsearch의 인덱싱은 단순히 데이터를 저장하는 것 이상의 의미를 가집니다. 문서가 인덱싱되면 텍스트 분석, 역색인 생성, 세그먼트 관리 등 복잡한 과정을 거치게 됩니다. 이 과정을 이해하면 왜 Bulk 인덱싱이 단건보다 10배 이상 빠른지, 왜 인덱싱 직후 검색이 안 되는지, 왜 대량 인덱싱 시 Refresh를 비활성화해야 하는지 알 수 있습니다. 올바른 인덱싱 전략은 시스템 성능과 운영 비용에 직접적인 영향을 미칩니다.

인덱싱 기본 개념#

인덱싱 과정#

flowchart LR
    A[문서 수신] --> B[분석 Analyze]
    B --> C[역색인 생성]
    C --> D[Memory Buffer]
    D --> E[Refresh]
    E --> F[Segment]
    F --> G[Flush]
    G --> H[Disk]
단계설명
분석텍스트를 토큰으로 분리
Memory Buffer메모리에 임시 저장
Refresh검색 가능하게 만듦 (기본 1초)
Segment불변의 인덱스 조각
Flush디스크에 영구 저장

단건 vs Bulk 인덱싱#

단건 인덱싱#

PUT /products/_doc/1
{
  "name": "맥북 프로",
  "price": 2390000
}

Bulk 인덱싱#

여러 문서를 한 번에 처리:

POST /_bulk
{"index": {"_index": "products", "_id": "1"}}
{"name": "맥북 프로", "price": 2390000}
{"index": {"_index": "products", "_id": "2"}}
{"name": "맥북 에어", "price": 1390000}
{"index": {"_index": "products", "_id": "3"}}
{"name": "아이패드", "price": 1499000}

NDJSON 형식: 각 줄은 개행문자(\n)로 구분, 마지막 줄도 개행 필요

성능 비교#

방식1만 건 처리 시간네트워크 요청
단건~30초10,000회
Bulk (1000건씩)~3초10회

Bulk 권장 설정#

POST /_bulk
// 권장 크기: 5-15MB per request
// 권장 문서 수: 1,000-5,000

Spring에서 Bulk 인덱싱#

@Service
public class ProductBulkService {

    private final ElasticsearchOperations operations;

    public void bulkIndex(List<Product> products) {
        List<IndexQuery> queries = products.stream()
            .map(product -> new IndexQueryBuilder()
                .withId(product.getId())
                .withObject(product)
                .build())
            .toList();

        operations.bulkIndex(queries, Product.class);
    }
}

Refresh#

Refresh란?#

Memory Buffer의 데이터를 검색 가능하게 만드는 작업입니다.

flowchart LR
    A[Memory Buffer] -->|Refresh| B[Segment<br>검색 가능]

Refresh Interval#

PUT /products/_settings
{
  "index": {
    "refresh_interval": "30s"    // 기본값: 1s
  }
}
설정의미용도
1s1초마다 (기본)실시간 검색
30s30초마다일반적인 서비스
-1비활성화대량 인덱싱 중

대량 인덱싱 시 최적화#

// 1. Refresh 비활성화
PUT /products/_settings
{ "refresh_interval": "-1" }

// 2. Bulk 인덱싱 수행
POST /_bulk
...

// 3. 수동 Refresh
POST /products/_refresh

// 4. Refresh 복원
PUT /products/_settings
{ "refresh_interval": "1s" }

Flush와 Translog#

Translog#

데이터 유실 방지를 위한 Write-Ahead Log입니다. Lucene 내부 구조에서 중요한 역할을 합니다. → Lucene 내부 구조 상세

flowchart LR
    A[문서] --> B[Translog]
    A --> C[Memory Buffer]
    B -->|장애 복구| D[데이터 복원]
    C -->|Flush| E[Disk Segment]

Flush#

Memory Buffer + Translog → Disk Segment 영구화:

POST /products/_flush

주의: 일반적으로 수동 Flush 불필요. Elasticsearch가 자동 관리.

Flush 설정#

PUT /products/_settings
{
  "index": {
    "translog": {
      "durability": "async",        // async: 성능, request: 안정성
      "sync_interval": "5s",
      "flush_threshold_size": "512mb"
    }
  }
}

Index Template#

새 인덱스 생성 시 자동 적용되는 설정:

PUT /_index_template/products_template
{
  "index_patterns": ["products-*"],
  "priority": 1,
  "template": {
    "settings": {
      "number_of_shards": 3,
      "number_of_replicas": 1,
      "refresh_interval": "5s"
    },
    "mappings": {
      "properties": {
        "name": { "type": "text" },
        "price": { "type": "integer" },
        "created_at": { "type": "date" }
      }
    }
  }
}

이제 products-2024, products-2025 등 생성 시 자동 적용.


Index Lifecycle Management (ILM)#

시계열 데이터의 수명주기를 자동 관리합니다. 로그 데이터 관리에 특히 유용합니다. → ILM 실전 적용 예제

수명주기 단계#

flowchart LR
    A[Hot<br>활발한 쓰기/읽기] --> B[Warm<br>읽기 위주]
    B --> C[Cold<br>가끔 읽기]
    C --> D[Frozen<br>거의 안 읽음]
    D --> E[Delete<br>삭제]

ILM 정책 생성#

PUT /_ilm/policy/logs_policy
{
  "policy": {
    "phases": {
      "hot": {
        "min_age": "0ms",
        "actions": {
          "rollover": {
            "max_size": "50gb",
            "max_age": "7d"
          },
          "set_priority": { "priority": 100 }
        }
      },
      "warm": {
        "min_age": "7d",
        "actions": {
          "shrink": { "number_of_shards": 1 },
          "forcemerge": { "max_num_segments": 1 },
          "set_priority": { "priority": 50 }
        }
      },
      "cold": {
        "min_age": "30d",
        "actions": {
          "set_priority": { "priority": 0 }
        }
      },
      "delete": {
        "min_age": "90d",
        "actions": {
          "delete": {}
        }
      }
    }
  }
}

ILM 정책 적용#

PUT /_index_template/logs_template
{
  "index_patterns": ["logs-*"],
  "template": {
    "settings": {
      "index.lifecycle.name": "logs_policy",
      "index.lifecycle.rollover_alias": "logs"
    }
  }
}

Reindex#

기존 인덱스를 새 인덱스로 복사/변환:

기본 Reindex#

POST /_reindex
{
  "source": { "index": "products-old" },
  "dest": { "index": "products-new" }
}

필터링 Reindex#

POST /_reindex
{
  "source": {
    "index": "products-old",
    "query": {
      "term": { "in_stock": true }
    }
  },
  "dest": { "index": "products-active" }
}

필드 변환#

POST /_reindex
{
  "source": { "index": "products-old" },
  "dest": { "index": "products-new" },
  "script": {
    "source": "ctx._source.price_krw = ctx._source.price * 1000"
  }
}

비동기 Reindex#

POST /_reindex?wait_for_completion=false
{
  "source": { "index": "large-index" },
  "dest": { "index": "large-index-new" }
}

진행 상황 확인:

GET /_tasks?actions=*reindex&detailed

Alias#

인덱스에 별명을 붙여 유연하게 관리:

Alias 생성#

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

Zero Downtime 재인덱싱#

// 1. 새 인덱스 생성 및 데이터 복사
PUT /products-v2
POST /_reindex
{
  "source": { "index": "products-v1" },
  "dest": { "index": "products-v2" }
}

// 2. Alias 전환 (원자적)
POST /_aliases
{
  "actions": [
    { "remove": { "index": "products-v1", "alias": "products" } },
    { "add": { "index": "products-v2", "alias": "products" } }
  ]
}

애플리케이션은 products alias만 사용 → 무중단 전환


인덱싱 성능 최적화#

대량 인덱싱 체크리스트#

// 1. Replica 비활성화
PUT /products/_settings
{ "number_of_replicas": 0 }

// 2. Refresh 비활성화
PUT /products/_settings
{ "refresh_interval": "-1" }

// 3. Bulk 인덱싱 수행
POST /_bulk
...

// 4. Refresh
POST /products/_refresh

// 5. 설정 복원
PUT /products/_settings
{
  "number_of_replicas": 1,
  "refresh_interval": "1s"
}

최적 Bulk 크기#

항목권장 값
요청 크기5-15 MB
문서 수1,000-5,000
동시 요청2-3 (노드당)

인덱싱 스레드#

PUT /products/_settings
{
  "index": {
    "indexing": {
      "slowlog": {
        "threshold": {
          "index": {
            "warn": "10s",
            "info": "5s"
          }
        }
      }
    }
  }
}

다음 단계#

목표추천 문서
클러스터 구성클러스터 관리
검색 최적화성능 튜닝
장애 대응고가용성