핵심 요약
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/Pull2 | push → 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