Go 1.23의 iterator 패턴 도입과 실전 적용기
배경
8월에 출시된 Go 1.23의 가장 큰 변화는 iterator 패턴의 공식 지원이다. 기존에는 채널이나 슬라이스로 데이터를 순회했는데, 이제 iter.Seq를 통해 더 유연한 순회가 가능해졌다.
사내 데이터 파이프라인에서 대용량 로그를 청크 단위로 처리하는 부분이 있었는데, 메모리 이슈로 계속 골치를 썩고 있었다. 이참에 iterator 패턴을 적용해봤다.
기존 코드
func ProcessLogs(filepath string) error {
logs, err := LoadAllLogs(filepath) // 전체 로딩
if err != nil {
return err
}
for _, log := range logs {
process(log)
}
return nil
}
파일이 몇 GB 단위가 되면 메모리에 전부 올리는 게 부담스러웠다.
Iterator 적용
func LogIterator(filepath string) iter.Seq[LogEntry] {
return func(yield func(LogEntry) bool) {
file, _ := os.Open(filepath)
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
log := ParseLog(scanner.Text())
if !yield(log) {
return
}
}
}
}
func ProcessLogs(filepath string) error {
for log := range LogIterator(filepath) {
process(log)
}
return nil
}
효과
- 메모리 사용량이 약 80% 감소했다
- 첫 데이터 처리까지 지연시간이 거의 없어졌다
- 코드가 더 선언적이고 읽기 좋아졌다
주의사항
yield 함수가 false를 반환하면 순회가 중단된다. 이 시점에 리소스 정리가 제대로 되는지 확인해야 한다. defer를 활용하는 게 안전하다.
또한 기존 라이브러리들이 아직 iterator를 지원하지 않는 경우가 많아서, 어댑터 함수를 직접 작성해야 하는 경우도 있었다.
정리
Go 1.23의 iterator는 실용적이다. 특히 스트리밍 처리나 대용량 데이터 다룰 때 유용했다. 다만 팀 전체가 Go 1.23으로 업그레이드할 때까지는 제한적으로만 사용할 예정이다.