Go 1.23의 Generic Iterator 패턴 적용기
문제 상황
레거시 API 서버에서 대용량 데이터를 페이지네이션으로 조회하는 코드가 있었다. 기존에는 모든 페이지를 slice에 담아 반환했는데, 데이터가 커지면서 메모리 부담이 심해졌다.
func FetchAllRecords(limit int) ([]Record, error) {
var records []Record
offset := 0
for {
batch, err := db.Query(limit, offset)
if err != nil {
return nil, err
}
if len(batch) == 0 {
break
}
records = append(records, batch...)
offset += limit
}
return records, nil
}
Go 1.23 Iterator 적용
Go 1.23부터 range over function이 지원되면서 iterator 패턴을 구현할 수 있게 되었다. iter.Seq[T] 타입을 활용해 lazy evaluation이 가능해졌다.
func IterateRecords(limit int) iter.Seq[Record] {
return func(yield func(Record) bool) {
offset := 0
for {
batch, err := db.Query(limit, offset)
if err != nil {
return
}
if len(batch) == 0 {
return
}
for _, record := range batch {
if !yield(record) {
return
}
}
offset += limit
}
}
}
// 사용
for record := range IterateRecords(100) {
if err := process(record); err != nil {
break
}
}
결과
- 메모리 사용량 70% 감소 (10GB → 3GB)
- 첫 번째 레코드 처리까지 시간 단축
- 에러 발생 시 즉시 중단 가능
기존에는 channel로 비슷한 패턴을 구현했었는데, iterator가 훨씬 직관적이고 고루틴 관리 부담도 없었다. 다만 아직 에러 핸들링 패턴은 커뮤니티에서 정립 중인 듯하다.