Go 1.22 range over function 실험적 사용기

배경

팀 내부 배치 처리 서비스를 Go로 마이그레이션하는 중이었다. 대용량 데이터를 청크 단위로 처리하는 로직이 많았는데, 기존 코드가 channel과 goroutine으로 복잡하게 얽혀있었다.

Go 1.22에서 실험적으로 추가된 range over function을 보고 이걸로 정리할 수 있겠다 싶었다.

기존 코드의 문제

func ProcessRecords(db *sql.DB) error {
    rows, err := db.Query("SELECT * FROM large_table")
    if err != nil {
        return err
    }
    defer rows.Close()
    
    for rows.Next() {
        var record Record
        if err := rows.Scan(&record); err != nil {
            return err
        }
        // 처리 로직
    }
    return rows.Err()
}

에러 핸들링이 산재되어 있고, 청크 단위 처리를 추가하려면 코드가 더 복잡해졌다.

range over function 적용

GOEXPERIMENT=rangefunc로 활성화 후 이터레이터 함수를 작성했다.

func QueryRecords(db *sql.DB, query string) func(yield func(Record) bool) {
    return func(yield func(Record) bool) {
        rows, err := db.Query(query)
        if err != nil {
            return
        }
        defer rows.Close()
        
        for rows.Next() {
            var record Record
            if err := rows.Scan(&record); err != nil {
                return
            }
            if !yield(record) {
                return
            }
        }
    }
}

// 사용
for record := range QueryRecords(db, "SELECT * FROM large_table") {
    process(record)
}

리소스 관리가 이터레이터 내부로 캡슐화되면서 사용하는 쪽 코드가 깔끔해졌다.

청크 처리 구현

func Chunk[T any](size int) func(func([]T) bool) func(func(T) bool) {
    return func(yield func([]T) bool) func(func(T) bool) {
        return func(yieldItem func(T) bool) {
            chunk := make([]T, 0, size)
            for item := range yieldItem {
                chunk = append(chunk, item)
                if len(chunk) >= size {
                    if !yield(chunk) {
                        return
                    }
                    chunk = chunk[:0]
                }
            }
            if len(chunk) > 0 {
                yield(chunk)
            }
        }
    }
}

함수 시그니처가 복잡해 보이지만, 조합 가능한 이터레이터를 만들 수 있다는 게 핵심이었다.

한계

아직 실험 단계라 프로덕션 적용은 보류했다. 에러 핸들링이 명확하지 않고, 타입 추론도 완벽하지 않았다. 하지만 방향성은 좋아 보였다.

Go 1.23에서 정식 기능이 되면 본격적으로 도입할 계획이다.