본문 바로가기
Backend2026년 5월 21일4분 읽기

Go 1.25 range-over-func + iter — 새 이터레이터 프로덕션 패턴

YS
김영삼
조회 104
Go 1.25 range-over-func + iter — 새 이터레이터 프로덕션 패턴

핵심 요약

Go 1.25에서 range-over-func과 iter 패키지가 정식 안정. 컬렉션·스트림 처리에서 채널을 남발하던 코드가 사라지고 메모리 압박이 줄었다. 사내 ETL 파이프라인을 옮긴 결과 메모리 사용량 38% 감소, GC pause 50% 단축.

1. 기본 문법

// 1.25
func Lines(r io.Reader) iter.Seq[string] {
  return func(yield func(string) bool) {
    s := bufio.NewScanner(r)
    for s.Scan() {
      if !yield(s.Text()) { return }
    }
  }
}

for line := range Lines(file) {
  process(line)
}

2. iter 패키지 핵심 타입

타입의미
iter.Seq[V]값 시퀀스
iter.Seq2[K,V]키-값 시퀀스
iter.Pull/Pull2push → pull 변환

3. 채널 대체

// 이전: 채널로 데이터 흘리기
ch := make(chan Row, 100)
go produce(ch)
for row := range ch { ... }

// 1.25: 이터레이터로
for row := range produce() { ... }

고루틴·채널 오버헤드 사라짐. 단, 동시성 필요하면 여전히 채널.

4. 변환 체인 — fp 스타일

import "slices"
total := 0
for v := range slices.Values(nums) {
  total += v
}
// 또는 filter/map 헬퍼 직접 작성 (표준엔 아직 제한적)

5. 데이터베이스 결과 처리

func (q Query) Iterate(ctx context.Context) iter.Seq2[Row, error] {
  return func(yield func(Row, error) bool) {
    rows, err := q.Query(ctx)
    if err != nil { yield(Row{}, err); return }
    defer rows.Close()
    for rows.Next() {
      var r Row
      if err := rows.Scan(&r.A, &r.B); err != nil { if !yield(Row{}, err) { return }; continue }
      if !yield(r, nil) { return }
    }
  }
}

for row, err := range q.Iterate(ctx) {
  if err != nil { return err }
  process(row)
}

6. 성능

ETL 1억 행 처리: 채널 버전 메모리 1.2GB, GC pause p99 8ms → 이터레이터 버전 메모리 750MB, GC pause p99 4ms.

7. 함정

  • yield의 반환값(bool)을 무시하면 break 후에도 계속 → 누수
  • 고루틴 안에서 외부 이터레이터의 변수 캡처 시 race 주의
  • 제네릭 인터페이스 메서드로는 직접 노출 어려움 — 별도 함수로

자주 묻는 질문

Q. Rx-Go 같은 라이브러리는? 단순 변환은 표준 iter로 충분. 복잡한 비동기 스트림은 여전히 채널 + 라이브러리.

댓글 0

아직 댓글이 없습니다.
Ctrl+Enter로 등록