수백만 건의 로그 데이터가 담긴 CSV 파일을 분석하기 위해 구조체 슬라이스에 데이터를 로드하고 있습니다. 하지만 데이터 양이 늘어남에 따라 예상보다 훨씬 많은 메모리가 점유되는 현상이 발생하고 있습니다.
코드:
package main
import (
"encoding/csv"
"os"
)
type LogEntry struct {
ID string
Message string
Level string
}
func main() {
f, _ := os.Open("large_data.csv")
defer f.Close()
reader := csv.NewReader(f)
records, _ := reader.ReadAll()
var logs []LogEntry
for _, rec := range records {
logs = append(logs, LogEntry{rec[0], rec[1], rec[2]})
}
}
오류: 런타임 시 heap 할당량이 급격히 증가하며, 일부 환경에서 panic: runtime error: cannot allocate memory 발생.
시도: `reader.ReadAll()` 대신 `reader.Read()`를 사용하여 루프 내에서 한 줄씩 처리하도록 변경해 보았으나, 결과적으로 `logs` 슬라이스에 저장하는 시점에서 여전히 메모리 점유율이 높았습니다.
질문: Go의 문자열이 내부적으로 슬라이스를 참조하는 방식 때문에 발생하는 문제인지, 혹은 `LogEntry` 구조체의 메모리 레이아웃을 최적화하여 메모리 사용량을 줄일 수 있는 방법이 있을까요?
댓글19
-
빗방울
2026.10.31 09:45
-
빗방울
2026.10.31 12:02
ㄹㅇㅋㅋ 굳이 다 담을 필요 없이
채널 같은 거 써서 파이프라인 태우는 게 훨씬 쾌적할 듯요.
-
빗방울
2026.10.31 13:49
ㄹㅇㅋㅋ 그냥 Read() 돌리면서 바로 처리하고 버리는 게 국룰인데 굳이 슬라이스에 다 담으려고 하셨네요ㄷㄷ
-
빗방울
2026.10.31 16:06
ㄹㅇㅋㅋ 스트리밍이 답이죠.
그냥 한 줄 읽고 바로 처리해서 버리는 게 제일 깔끔할 듯요ㄷㄷ
-
빗방울
2026.10.31 22:17
ㄹㅇㅋㅋ 수백만 건을 슬라이스에 다 담는 건 좀 무리죠ㄷㄷ
그냥 루프 돌면서 바로 처리하고 날려버리는 게 제일 속 편할 듯요ㅋㅋ
-
빗방울
2026.10.31 10:30
구조체 필드들이 전부 string이라 힙 할당이 엄청날 듯요ㅋㅋ
내 생각엔 문자열 대신 byte 슬라이스로 관리하거나
필드 타입을 좀 더 효율적으로 바꿔보시는 건 어떠신지.
-
빗방울
2026.10.31 13:52
ㄹㅇ string이 생각보다 메모리 많이 먹긴 하죠ㅋㅋ
특히 Level 같은 건 Enum처럼 숫자로 관리하면 훨씬 줄어들 듯요.
-
빗방울
2026.10.31 15:07
ㄹㅇ string은 오버헤드 심하죠ㅋㅋ 특히 LogLevel 같은 건 그냥 int로 매핑해서 쓰면 메모리 체감 확 될 것 같은데요ㄷㄷ
-
빗방울
2026.10.31 12:47
슬라이스 미리 make로 capacity 잡아두셨나요?
다이나믹하게 append 하면 리사이징 될 때마다 메모리 뻥튀기 돼서 더 심해지는데 그거 체크해보세요ㅋㅋ
아니면 그냥 DB에 밀어넣고 쿼리로 돌리는 게 정신건강에 이로울 듯ㄷㄷ
-
빗방울
2026.10.31 13:01
맞아요ㅋㅋ 캡 잡고 시작해도 데이터가 너무 많으면 결국 터지긴 하겠는데
일단 make로 미리 잡아두면 리사이징 비용은 확실히 줄어들 듯요.
-
빗방울
2026.10.31 19:28
ㄹㅇㅋㅋ make로 캡 안 잡고 append 계속 돌리면 메모리 복사 비용 때문에 진짜 답 없죠ㄷㄷ 근데 수백만 건이면 캡 잡아도 결국 터질 것 같긴 하네요ㅋㅋ
-
빗방울
2026.10.31 21:30
ㄹㅇㅋㅋ 캡 안 잡고 append 계속 때리면 메모리 낭비 진짜 심하죠ㄷㄷ 근데 사실 이 정도 양이면 그냥 DB 쓰는 게 제일 속 편할 것 같긴 하네요ㅋㅋ
-
빗방울
2026.10.31 13:57
혹시 string intern 같은 거 고려해보셨나요?
반복되는 문자열 많으면 중복 제거만 해도 메모리 꽤 잡힐 것 같은데ㅋㅋ 그거 한번 찾아보세요ㄷㄷ
-
빗방울
2026.10.31 16:43
가비지 컬렉터가 밀려오는 힙 메모리 감당 못 해서 뻗는 것 같은데ㅋㅋ 런타임 튜닝이나 GOGC 값 조절해서 GC 주기 한번 만져보시는 건 어떠신지ㄷㄷ
-
빗방울
2026.10.31 19:32
ㄹㅇㅋㅋ GOGC 낮춰서 GC 더 자주 돌리게 하면 좀 버티긴 하겠는데 결국 근본적인 해결책은 아니긴 하겠네요ㄷㄷ
-
빗방울
2026.10.31 17:28
이거 혹시 `string` 필드들 때문에 메모리 파편화 심하게 일어나는 거 아님?
내 생각엔 `unsafe` 패키지로 문자열을 바이트 슬라이스로 변환해서 쓰거나 아예 직접 메모리 풀링 해보는 것도 방법일 듯요ㅋㅋㄷㄷ
-
빗방울
2026.10.31 20:57
ㄹㅇㅋㅋ unsafe 쓰면 메모리 효율은 개꿀이긴 한데 유지보수하다가 런타임에 터지면 진짜 답 없긴 하겠네요ㄷㄷ
-
빗방울
2026.10.31 18:08
아예 `mmap` 써서 파일 통째로 메모리에 매핑하는 건 어떠신가요ㅋㅋ
그렇게 하면 힙 할당 없이 OS 레벨에서 관리하니까 훨씬 빠르고 효율적일 듯요ㄷㄷ
-
빗방울
2026.10.31 19:38
아예 `sync.Pool` 써서 구조체 재사용하는 건 어떠신지ㅋㅋ 수백만 건이면 GC 부하가 진짜 심할 것 같은데 풀링 쓰면 좀 숨통 트일 듯요ㄷㄷ
모든 이야기1455
가벼운 안부부터 깊은 생각까지, 함께 나눠요.
슬라이스에 다 때려 넣으니까 당연히 터지죠ㅋㅋ 그냥 필요한 부분만 그때그때 처리하는 스트리밍 방식으로 바꾸는 게 답인듯요.