Node.js 개발자의 Go 첫 프로젝트 회고

배경

회사에서 트래픽이 많은 이미지 리사이징 서비스를 새로 만들게 되었다. 기존 Node.js 서버로는 CPU 부하가 컸고, 동시 처리 성능이 부족했다. 팀에서 Go를 선택했고, 나는 처음으로 Go 프로젝트를 맡았다.

좋았던 점

컴파일 타임 타입 체크

Node.js에서는 런타임에야 발견되던 타입 오류들이 컴파일 단계에서 잡혔다. TypeScript를 고려했지만, Go의 네이티브 타입 시스템이 더 엄격하고 명확했다.

type ImageRequest struct {
    URL    string `json:"url"`
    Width  int    `json:"width"`
    Height int    `json:"height"`
}

func resizeImage(req ImageRequest) ([]byte, error) {
    if req.Width <= 0 || req.Height <= 0 {
        return nil, errors.New("invalid dimensions")
    }
    // ...
}

고루틴과 성능

동시에 여러 이미지를 처리할 때 고루틴이 강력했다. Node.js의 이벤트 루프와 달리 실제 병렬 처리가 가능해서, 멀티코어를 제대로 활용할 수 있었다.

var wg sync.WaitGroup
for _, img := range images {
    wg.Add(1)
    go func(image string) {
        defer wg.Done()
        process(image)
    }(img)
}
wg.Wait()

불편했던 점

에러 핸들링

if err != nil 구문이 코드 곳곳에 반복되었다. Node.js의 try-catch나 Promise에 익숙했던 터라, 처음에는 장황하게 느껴졌다.

data, err := ioutil.ReadFile(path)
if err != nil {
    return err
}

result, err := process(data)
if err != nil {
    return err
}

제네릭 부재

같은 로직을 다른 타입에 적용하려면 코드를 복사하거나 interface{}를 써야 했다. 타입 안정성을 포기하거나 보일러플레이트를 감수해야 하는 트레이드오프였다.

결과

배포 후 CPU 사용률이 60% 감소했고, 응답 시간도 평균 200ms에서 50ms로 개선되었다. 메모리 사용량도 안정적이었다. 학습 곡선은 있었지만, 성능이 중요한 서비스에서는 Go가 확실히 좋은 선택이었다.