Elasticsearch 대용량 데이터 aggregation 성능 개선
문제 상황
사용자 행동 로그를 집계하는 통계 API가 있었다. 초기에는 문제없이 동작했지만, 데이터가 누적되면서 특정 쿼리에서 30초 타임아웃이 발생하기 시작했다. 일별 통계를 조회하는 간단한 aggregation이었는데, 인덱스 크기가 100GB를 넘어가자 버티지 못했다.
시도한 방법들
1. date_histogram 최적화
기존에는 calendar_interval: "1d"를 사용했는데, fixed_interval로 변경했다.
{
"aggs": {
"daily_stats": {
"date_histogram": {
"field": "timestamp",
"fixed_interval": "1d",
"time_zone": "Asia/Seoul"
}
}
}
}
2. 인덱스 샤드 재분배
초기에 샤드를 3개로 설정했었는데, 데이터 증가에 따라 5개로 늘렸다. reindex 작업이 필요해서 새벽 시간대에 진행했다.
3. 필드 캐싱
자주 aggregation에 사용되는 필드를 eager_global_ordinals로 설정했다.
{
"mappings": {
"properties": {
"user_id": {
"type": "keyword",
"eager_global_ordinals": true
}
}
}
}
4. 구간 쿼리 분할
가장 효과적이었던 방법은 쿼리를 주 단위로 쪼개서 병렬로 실행하고 애플리케이션 레벨에서 합치는 것이었다. 단일 쿼리로 3개월치를 조회하면 타임아웃이 났지만, 주별로 나누니 각각 2~3초 안에 완료됐다.
const queries = dateRanges.map(range =>
esClient.search({
index: 'user-logs',
body: {
query: {
range: {
timestamp: {
gte: range.start,
lt: range.end
}
}
},
aggs: { /* ... */ }
}
})
);
const results = await Promise.all(queries);
const merged = mergeAggregations(results);
결과
평균 응답 속도가 30초에서 5초로 개선됐다. 쿼리 분할이 핵심이었고, 샤드 재분배와 캐싱 설정도 일부 기여했다. 다만 애플리케이션 코드가 복잡해진 트레이드오프는 감수해야 했다.
장기적으로는 Rollup이나 Transform 기능을 고려해볼 예정이다.