Elasticsearch 대용량 데이터 색인 시 OOM 해결

문제 상황

코로나로 재택근무가 시작되면서 사용자 활동 로그가 예상보다 2배 이상 증가했다. 기존에는 문제없이 동작하던 Elasticsearch 색인 작업이 매일 새벽마다 OOM으로 실패했다.

[2020-05-15T03:24:11] java.lang.OutOfMemoryError: Java heap space

원인 분석

Elasticsearch 모니터링 결과:

  • Bulk 요청당 5000개 문서를 색인 중이었음
  • refresh_interval이 기본값 1s로 설정되어 있었음
  • 색인 중 segment merge가 과도하게 발생

해결 방법

1. Bulk 사이즈 조정

문서 개수가 아닌 바이트 크기 기준으로 변경했다.

const BULK_SIZE = 5 * 1024 * 1024; // 5MB
let currentBatch = [];
let currentSize = 0;

for (const doc of documents) {
  const docSize = Buffer.byteLength(JSON.stringify(doc));
  
  if (currentSize + docSize > BULK_SIZE) {
    await bulkIndex(currentBatch);
    currentBatch = [];
    currentSize = 0;
  }
  
  currentBatch.push(doc);
  currentSize += docSize;
}

2. 대량 색인 시 refresh_interval 비활성화

await esClient.indices.putSettings({
  index: 'logs-*',
  body: {
    refresh_interval: '-1'
  }
});

// 색인 작업 수행
await indexLogs();

// 색인 완료 후 복구
await esClient.indices.putSettings({
  index: 'logs-*',
  body: {
    refresh_interval: '30s'
  }
});

3. 색인 성능 개선 설정

{
  "number_of_replicas": 0,
  "translog.durability": "async",
  "translog.flush_threshold_size": "1gb"
}

결과

  • 색인 속도: 2000 docs/s → 8000 docs/s
  • 메모리 사용량: 85% → 60%
  • 색인 시간: 3시간 → 45분

색인 완료 후에는 replica를 다시 1로 설정하고 force merge를 실행했다.

POST /logs-2020-05/_forcemerge?max_num_segments=1

대량 색인 시에는 실시간성보다 처리량이 중요하다는 걸 다시 확인했다.