로그를 Elasticsearch에 계속 쌓아도 될까? 비용이 커지는 지점
100GB/일을 90일 보관하면 월 858만 원입니다. 그런데 그중 스토리지는 3분의 1도 안 됩니다. 진짜 돈은 RAM에서 나가고, 티어를 나누면 84%가 줄어듭니다.
먼저 계산 기준
앞선 운영비 글과 같은 기준입니다. 서울 리전(ap-northeast-2) 온디맨드 정가, 2026년 9월 기준.
환율 1,340원 / $1
부가세 10%
EBS gp3 $0.0912 / GB·월
S3 Standard $0.025 / GB·월
로그는 JSON 100GB/일을 기준으로 잡았습니다. 이 정도면 중견 규모 서비스 하나, 또는 마이크로서비스 열 개쯤의 합입니다.
결론부터
| 구성 (100GB/일, 90일 보관) | USD | KRW (VAT 포함) |
|---|---|---|
| 전량 핫 티어에 보관 | $6,406.75 | 약 8,585,000원 |
| ILM으로 티어 분리 | $1,033.61 | 약 1,385,000원 |
같은 로그, 같은 보관 기간인데 84% 차이입니다. 그리고 이 차이의 대부분은 스토리지가 아니라 RAM에서 나옵니다.
Elasticsearch가 로그 1GB를 받으면 디스크에 얼마가 남나
"100GB 쌓으면 100GB 쓰겠지"가 첫 번째 착각입니다. Elasticsearch는 원문만 저장하는 게 아닙니다.
원본 JSON 로그 100GB
+ _source (원문 보관, 압축) ~35GB
+ 역인덱스 (검색용) ~45GB
+ doc_values (집계·정렬용) ~40GB
───────
인덱싱 후 약 120GB (1.2배)
+ 복제본 1개 (replica: 1) 약 240GB (2.4배)
로그 100GB를 넣으면 디스크에서는 240GB가 됩니다. 배율은 매핑에 따라 0.6배까지 내려가고 2배 넘게도 올라갑니다 — 뒤에서 다루겠습니다.
doc_values가 눈에 안 띄는 항목입니다. Kibana에서 집계와 정렬을 하려면 필요한데, 실제로 집계하지 않는 필드에도 기본으로 켜져 있습니다.
규모별로 보면 어디서 꺾이는지 보입니다
90일 보관 기준입니다. 핫 티어는 RAM : 디스크 = 1 : 30이 Elastic의 권장 비율입니다.
| 로그량 | 90일 디스크 | 필요 핫 RAM | 스토리지 비용 | 노드(64GB) |
|---|---|---|---|---|
| 10GB/일 | 2,160GB | 72GB | $196.99 (264,000원) | 2대 (최소 3대) |
| 50GB/일 | 10,800GB | 360GB | $984.96 (1,320,000원) | 6대 |
| 100GB/일 | 21,600GB | 720GB | $1,969.92 (2,639,700원) | 11대 |
| 500GB/일 | 108,000GB | 3,600GB | $9,849.60 (13,198,500원) | 56대 |
10GB/일까지는 고민할 문제가 아닙니다. 노드 3대짜리 클러스터면 충분하고, 스토리지는 월 26만 원입니다. 이 구간에서 로그 비용을 최적화하는 건 시간 낭비입니다.
꺾이는 건 50~100GB/일 구간입니다. 여기서 노드가 두 자릿수로 들어가고, 그때부터 청구서의 성격이 바뀝니다.
진짜 비용은 스토리지가 아니라 RAM입니다
100GB/일 · 90일 전량 핫 구성을 항목별로 열어보겠습니다. 노드는 r6g.2xlarge(8 vCPU / 64GB), 서울 온디맨드 약 $0.48/시간으로 잡았습니다.
| 항목 | USD | KRW |
|---|---|---|
| EBS gp3 21,600GB | $1,969.92 | 2,639,700원 |
| r6g.2xlarge × 11대 | $3,854.40 | 5,164,900원 |
| 소계 | $5,824.32 | 7,804,600원 |
| + VAT 10% | $6,406.75 | 약 8,585,000원 |
컴퓨트가 스토리지의 두 배입니다. 보관 기간을 늘리면 디스크만 느는 게 아니라, 그 디스크를 검색 가능한 상태로 유지하기 위한 노드 수가 같이 늘어납니다. 이게 로그 비용이 선형이 아니라 계단식으로 뛰는 이유입니다.
바꿔 말하면 이렇습니다. Elasticsearch에 로그를 넣는 비용은 "저장하는 값"이 아니라 "언제든 검색 가능하게 유지하는 값"입니다. 그리고 90일 전 로그를 검색하는 일은 거의 없습니다.
비용이 예고 없이 튀는 세 지점
1. 동적 매핑 폭발
기본 설정에서 Elasticsearch는 들어온 필드를 전부 인덱싱합니다. 구조화 로깅을 하면서 요청 파라미터나 응답 바디를 통째로 넣으면, 필드 수가 수천 개로 늘어납니다.
{ "level": "INFO", "msg": "order created",
"request": { "items": [ {"sku": "A-1", "opt": {"color": "red"} } ] } }
이런 로그가 들어오면 request.items.opt.color 같은 필드가 값마다 새로 생깁니다. 인덱스 크기가 1.2배가 아니라 2~3배가 되고, index.mapping.total_fields.limit(기본 1,000)에 걸려 로그가 통째로 유실되기 시작합니다.
해결은 단순합니다. 검색할 필드만 인덱싱하고 나머지는 문자열 한 덩어리로 둡니다.
"mappings": {
"dynamic": "strict",
"properties": {
"level": { "type": "keyword" },
"trace_id": { "type": "keyword" },
"msg": { "type": "text" },
"payload": { "type": "object", "enabled": false }
}
}
"enabled": false는 저장은 하되 인덱싱하지 않습니다. 검색은 안 되지만 화면에서 눈으로 볼 수는 있습니다. 로그 페이로드는 대부분 이걸로 충분합니다.
2. 샤드 개수 폭발
일별 인덱스를 만들고 샤드를 5개씩 두면 이렇게 됩니다.
서비스 10개 × 샤드 5개 × 90일 = 4,500 샤드
샤드 하나는 데이터가 비어 있어도 힙 메모리를 씁니다. 힙 1GB당 샤드 20개 이하가 권장치인데, 4,500샤드면 225GB의 힙이 필요합니다. 그런데 JVM 힙은 압축 오프셋 때문에 노드당 31GB가 상한입니다. 즉 이것만으로 노드 8대가 필요해집니다 — 데이터 크기와 무관하게요.
10GB/일짜리 서비스가 "클러스터가 자꾸 죽는다"고 할 때 열에 아홉은 데이터가 아니라 샤드 수 문제입니다. 샤드 하나는 10~50GB를 목표로 잡고, 일별 인덱스 대신 데이터 스트림 롤오버(max_primary_shard_size: 50gb)를 쓰는 편이 맞습니다.
3. 핫 티어 RAM 한계
앞의 1:30 비율입니다. 보관 기간을 30일에서 90일로 늘리는 건 디스크만 3배가 아니라 핫 노드도 3배입니다. 이게 "보관 기간 좀 늘려주세요"가 실제로 얼마짜리 요청인지 설명해 줍니다.
30일 → 90일 : 월 285만 원 → 월 858만 원 (+573만 원)
ILM으로 티어를 나누면 84%가 줄어듭니다
로그의 접근 패턴은 극단적으로 최근 편향입니다. 대부분의 검색은 최근 며칠 안에서 일어나고, 그 뒤는 사고 조사 때 가끔 뒤집니다.
Index Lifecycle Management로 티어를 나눕니다.
hot 0~3일 복제본 1, gp3 ← 장애 대응 창
warm 4~14일 복제본 0, 강제 병합 ← 회고·버그 추적
frozen 15~90일 S3 검색 가능 스냅샷 ← 가끔 뒤지는 용
delete 90일 삭제
| 티어 | USD | KRW |
|---|---|---|
| hot 3일 (720GB, 복제본 1) | $65.66 | 88,000원 |
| warm 11일 (1,320GB, 복제본 0) | $120.38 | 161,300원 |
| frozen 76일 (9,120GB, S3) | $228.00 | 305,500원 |
| 노드 r6g.xlarge × 3 | $525.60 | 704,300원 |
| 소계 | $939.65 | 1,259,100원 |
| + VAT 10% | $1,033.61 | 약 1,385,000원 |
월 858만 원이 138만 원이 됩니다. 세 가지가 동시에 작동한 결과입니다.
- frozen 티어는 S3에 있습니다. GB당 $0.0912가 $0.025가 되고, S3는 자체 내구성이 있어 복제본이 필요 없습니다. 두 효과가 곱해져 약 7분의 1입니다.
- warm에서 복제본을 뗍니다. 스냅샷이 있으면 복제본은 가용성용이지 내구성용이 아닙니다. 15일 전 로그가 몇 분간 검색이 안 되는 건 대개 견딜 수 있습니다.
- 핫 노드가 11대에서 3대로 줄어듭니다. 1:30 비율이 걸리는 대상이 90일치가 아니라 3일치가 되기 때문입니다.
frozen 티어의 검색은 느립니다 — S3에서 끌어와야 하니 몇 초에서 수십 초입니다. 그런데 두 달 전 로그를 뒤질 때 10초를 못 기다릴 이유가 없습니다.
그냥 Elasticsearch를 안 쓰는 선택지
100GB/일 · 90일, 스토리지 단가만 비교하면 이렇습니다.
| 방식 | USD | KRW |
|---|---|---|
| Elasticsearch 전량 핫 | $1,969.92 | 2,639,700원 |
| Elasticsearch + ILM | $414.05 | 554,800원 |
| Grafana Loki (S3, 청크 압축) | $45.00 | 60,300원 |
| S3 + Athena (gzip 원본) | $33.75 | 45,200원 |
| CloudWatch Logs (수집분만) | $2,280.00 | 3,055,200원 |
CloudWatch Logs가 제일 비쌉니다. 수집 단가가 GB당 $0.76인데, 100GB/일이면 월 3,000GB × $0.76 = $2,280입니다. 보관료는 여기 포함도 안 됐습니다. 이 규모에서 CloudWatch에 전부 넣는 건 검토 대상이 아닙니다.
반대쪽 끝에 Loki가 있습니다. Loki는 라벨만 인덱싱하고 로그 본문은 압축 청크로 S3에 던집니다. 역인덱스를 안 만드니까 싸고, 대신 본문 검색은 grep에 가깝습니다 — 라벨로 범위를 좁힌 뒤 훑습니다.
이게 핵심적인 교환입니다.
Elasticsearch 전문 검색·집계가 빠름 비쌈 ← 검색이 제품 기능일 때
Loki 라벨 검색 + 본문 훑기 쌈 ← 로그가 디버깅 도구일 때
S3 + Athena SQL, 느림 가장 쌈 ← 규제 보관·가끔 분석
로그를 디버깅용으로만 쓴다면 Elasticsearch는 과합니다. 반면 사용자에게 검색 기능을 노출하거나, 대시보드에서 복잡한 집계를 상시 돌린다면 Elasticsearch가 맞습니다. 판단 기준은 로그의 양이 아니라 쿼리의 모양입니다.
Spring Boot 쪽에서 실제로 줄일 것들
인프라를 손대기 전에 발생량 자체를 줄이는 게 언제나 더 큽니다.
1. INFO 레벨 SQL 로그. org.hibernate.SQL이나 show-sql: true가 운영에 올라간 채인 경우가 흔합니다. 요청 하나에 쿼리 20개면 로그가 20줄 늘어납니다. 로그량 절반이 이것뿐인 서비스를 여럿 봤습니다.
2. 스택트레이스 전문 저장. 예외 하나에 5KB입니다. 같은 예외가 초당 10번 발생하면 하루 4GB입니다. 스택트레이스는 해시로 묶어 대표 하나만 남기고 나머지는 카운트만 세는 편이 낫습니다.
3. 액세스 로그 중복. ALB 액세스 로그를 S3에 쌓으면서 앱에서도 같은 요청을 로깅하고 있는 경우입니다. 같은 정보를 두 번 냅니다.
4. 레벨별 샘플링. 이게 가장 효과가 큽니다. ERROR와 WARN은 전량, INFO는 일부만 남깁니다.
| 정책 | USD | KRW |
|---|---|---|
| 전량 (ILM 적용 상태) | $414.05 | 554,800원 |
| INFO 30% 샘플링 | $124.21 | 166,400원 |
| INFO 10% 샘플링 | $41.40 | 55,500원 |
문제 추적에는 trace_id가 있으면 됩니다. 같은 요청의 INFO 로그 100개보다, 에러 하나에 붙은 trace_id 하나가 낫습니다.
한국에서 보관 기간을 마음대로 줄일 수 없는 경우
「개인정보의 안전성 확보조치 기준」상 개인정보처리시스템의 접속기록은 최소 1년 보관해야 합니다. 5만 명 이상의 정보주체에 관한 개인정보나 고유식별정보·민감정보를 처리하는 시스템이면 2년입니다.
여기서 많이들 오해하는 지점이 있습니다. 규제가 요구하는 건 "보관"이지 "검색 가능한 상태"가 아닙니다.
접속기록 1년 보관 → S3에 gzip으로 두면 충족
Elasticsearch 핫 티어에 1년 = 규제와 무관한 과잉
접속기록(계정, 접속일시, 접속지 정보, 수행업무)은 애플리케이션 로그 전체에 비하면 아주 작습니다. 이걸 분리해서 별도로 1년 보관하고, 나머지 로그는 자유롭게 90일에 자르는 게 맞습니다. 규제 때문에 전체 로그를 1년 보관한다는 말이 나오면, 대개 분리를 안 한 것입니다.
그래서 판단 기준
질문을 이렇게 바꾸면 답이 나옵니다. "이 로그를 지난 30일 동안 실제로 몇 번 검색했나?"
- 10GB/일 이하 — 그냥 Elasticsearch에 넣으세요. 월 26만 원짜리 고민에 시간 쓰지 마세요.
- 50GB/일 이상 — ILM 티어링을 먼저 켜세요. 코드 한 줄 안 고치고 80% 줄어듭니다.
- 100GB/일 이상 — 발생량부터 줄이세요. 샘플링과 SQL 로그 정리가 인프라 튜닝보다 효과가 큽니다.
- 검색이 제품 기능이 아니라면 — Loki나 S3+Athena를 진지하게 보세요. 로그가 디버깅 도구일 뿐이라면 역인덱스 값을 낼 이유가 없습니다.
정리
- 로그 100GB를 넣으면 디스크에는 240GB가 남습니다. 인덱스·doc_values·복제본 때문입니다.
- 100GB/일 · 90일 전량 핫 구성은 월 858만 원이고, 그중 컴퓨트가 스토리지의 두 배입니다.
- 보관 기간을 3배로 늘리면 디스크만 3배가 아니라 핫 노드도 3배입니다. 계단식으로 뜁니다.
- ILM 티어링만으로 월 138만 원, 84% 절감됩니다. frozen 티어는 S3라 복제본도 필요 없습니다.
- 비용이 예고 없이 튀는 지점은 동적 매핑 폭발, 샤드 수 폭발, 핫 티어 RAM 셋입니다.
- CloudWatch Logs는 이 규모에서 가장 비쌉니다 — 수집만 월 305만 원.
- 로그가 디버깅 도구일 뿐이라면 Loki가 45분의 1입니다. 판단 기준은 양이 아니라 쿼리의 모양입니다.
INFO10% 샘플링이 인프라 튜닝보다 큽니다. trace_id 하나가 INFO 100줄보다 낫습니다.- 접속기록 1년 보관은 S3 gzip으로 충족됩니다. 규제 때문에 전량을 핫에 둘 이유는 없습니다.
여러분의 클러스터는 지금 며칠치를 핫 티어에 들고 있나요? 그리고 그중 마지막으로 검색해 본 건 며칠 전 로그인가요?
출처
- Elastic, Size your shards — 샤드 크기 10~50GB, 힙 1GB당 샤드 20개 권장치.
- Elastic, Data tiers — hot·warm·cold·frozen 티어와 권장 RAM:디스크 비율.
- Elastic, Index lifecycle management — 롤오버와 티어 이동 정책.
- Elastic, Searchable snapshots — frozen 티어가 S3에서 직접 검색하는 구조.
- Elastic, Mapping —
enabled파라미터 — 저장하되 인덱싱하지 않기. - Amazon EBS 요금 및 S3 요금 — 서울 리전 GB 단가.
- Amazon CloudWatch 요금 — 로그 수집 GB 단가.
- Grafana Loki 아키텍처 문서 — 라벨만 인덱싱하는 구조.
- 개인정보의 안전성 확보조치 기준 (개인정보보호위원회 고시) — 접속기록 보관 기간.