Spring Boot 기반 Elasticsearch 과거 인덱스 정리 자동화 프로그램 개발

배경 및 목적

Elasticsearch는 대량의 로그나 이벤트 데이터를 실시간으로 검색할 수 있는 강력한 도구이지만, 시간이 지남에 따라 저장 용량이 급격히 증가하는 문제가 발생합니다. 특히 일자별로 인덱스를 생성하는 경우, 오래된 인덱스가 누적되어 클러스터 성능 저하 및 디스크 부족 현상이 나타날 수 있습니다. 기존에는 cron 기반 스크립트로 특정 날짜의 인덱스를 삭제했으나, 실행 누락 시 복구가 어렵고 유연성이 부족했습니다. 이를 해결하기 위해 Spring Boot 애플리케이션을 활용해 주기적으로 설정된 보관 기간을 초과한 인덱스를 자동으로 스캔하고 삭제하는 시스템을 구현합니다.

개발 환경

항목 버전
Java 8
Spring Boot 2.1.8.RELEASE
Spring Data Elasticsearch 3.1.10.RELEASE
Elasticsearch 6.2.2

구현 흐름

  1. application.properties에 보관 기간(일 단위)과 인덱스 접두사 설정
  2. 애플리케이션 시작 후 주기적으로 모든 인덱스 목록 조회
  3. 설정된 접두사에 해당하는 인덱스 필터링
  4. 각 인덱스 생성 시간 조회 후 현재 시간과 비교
  5. 보관 기간을 초과한 인덱스를 삭제 처리
  6. 10분 간격으로 반복 실행

의존성 설정 (Maven)

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.8.RELEASE</version>
</parent>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
    </dependency>
</dependencies>

설정 파일 구성

application.properties에 인덱스 접두사별 보관 기간을 명시합니다.

# Elasticsearch 연결 정보
spring.data.elasticsearch.cluster-nodes=localhost:9300
spring.data.elasticsearch.cluster-name=my-cluster

# 인덱스 보관 기간 설정 (단위: 일)
index.retention.log-service=7
index.retention.metrics-data=14
index.retention.audit-log=30

설정 정보 바인딩 클래스

@ConfigurationProperties를 사용해 외부 설정 값을 자바 객체로 매핑합니다.

@Component
@ConfigurationProperties(prefix = "index.retention")
@Getter @Setter
public class IndexRetentionConfig {
    private Map<String, Integer> policies = new HashMap<>();
}

핵심 비즈니스 로직

CommandLineRunner를 구현하여 애플리케이션 시작 후 주기적으로 작업을 수행합니다.

@Slf4j
@SpringBootApplication
public class IndexCleanupApplication implements CommandLineRunner {

    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;

    @Autowired
    private IndexRetentionConfig retentionConfig;

    public static void main(String[] args) {
        SpringApplication.run(IndexCleanupApplication.class, args);
    }

    @Override
    public void run(String... args) {
        while (true) {
            try {
                processExpiredIndices();
                TimeUnit.MINUTES.sleep(10); // 10분 대기
            } catch (Exception e) {
                log.error("Index cleanup job failed", e);
            }
        }
    }

    private void processExpiredIndices() {
        Set<String> allIndices = fetchAllIndexNames();
        if (allIndices.isEmpty()) return;

        retentionConfig.getPolicies().forEach((prefix, days) -> {
            log.info("Processing indices with prefix: '{}' (retention: {} days)", prefix, days);

            List<String> expired = findExpiredIndices(allIndices, prefix, days);
            performDeletion(expired);
        });
    }

    private Set<String> fetchAllIndexNames() {
        IndicesStatsResponse response = elasticsearchTemplate.getClient()
            .admin().indices()
            .stats(new IndicesStatsRequest().all())
            .actionGet();

        return response.getIndices().keySet();
    }

    private long getIndexCreationTime(String indexName) {
        GetSettingsResponse settings = elasticsearchTemplate.getClient()
            .admin().indices()
            .getSettings(new GetSettingsRequest().indices(indexName))
            .actionGet();

        String timestamp = settings.getIndexToSettings().get(indexName)
            .getAsSettings("index").get("creation_date");

        return Long.parseLong(timestamp);
    }

    private List<String> findExpiredIndices(Set<String> indices, String prefix, int retentionDays) {
        long threshold = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(retentionDays);

        return indices.parallelStream()
            .filter(name -> name.startsWith(prefix))
            .filter(name -> {
                try {
                    long created = getIndexCreationTime(name);
                    return created < threshold;
                } catch (Exception e) {
                    log.warn("Failed to get creation time for index: {}", name, e);
                    return false;
                }
            })
            .collect(Collectors.toList());
    }

    private void performDeletion(List<String> indicesToDelete) {
        if (indicesToDelete.isEmpty()) {
            log.info("No expired indices found.");
            return;
        }

        log.info("Attempting to delete {} expired indices.", indicesToDelete.size());

        for (String index : indicesToDelete) {
            boolean deleted = elasticsearchTemplate.deleteIndex(index);
            if (deleted) {
                log.info("Successfully deleted index: {}", index);
            } else {
                log.warn("Failed to delete index: {}", index);
            }
        }
    }
}

실행 결과

애플리케이션을 배포하면 지정된 간격으로 Elasticsearch 클러스터를 스캔하여 설정된 보관 기간을 넘긴 인덱스를 안정적으로 제거합니다. 예를 들어, log-service-2023.01.01과 같은 인덱스가 7일 이상 경과하면 자동으로 삭제 대상에 포함됩니다. 시스템 다운타임이나 스케줄링 누락에도 유연하게 대응할 수 있어 운영 안정성이 향상됩니다.

태그: elasticsearch spring-boot spring-data-elasticsearch java index-cleanup

7월 17일 01:03에 게시됨