PHpullh
학습 로드맵/시스템/백엔드

Go

⚙️ 시스템·백엔드 학습 로드맵

기능을 늘리는 순서가 아니라, 문제가 생겼을 때 원인을 좁혀 갈 수 있는 능력을 쌓는 순서로 정리했습니다.

백엔드는 눈에 보이는 결과물이 적어 학습 진도를 체감하기 어려운 분야입니다. 화면이 그려지는 프론트엔드와 달리, 잘 만들었는지가 장애가 났을 때에야 드러납니다. 그래서 이 로드맵은 기능을 늘리는 순서가 아니라, 문제가 생겼을 때 원인을 좁혀 갈 수 있는 능력을 쌓는 순서로 짰습니다.

여기서는 Go를 기준으로 설명하지만 순서 자체는 언어와 무관합니다. Java나 Python으로 진행해도 각 단계의 목표는 같습니다. 전체를 도는 데 대략 4~6개월을 잡되, 단계마다 실제로 배포까지 해 본 결과물을 하나씩 남기십시오.

1단계 — 언어 기초와 표준 라이브러리

백엔드에서 쓰는 언어 지식은 생각보다 좁습니다. 자료구조 다루기, 에러 처리, 파일과 네트워크 입출력, 그리고 동시성의 기본 개념이면 시작할 수 있습니다. Go를 고른다면 명시적인 에러 반환 방식에 적응하는 것이 첫 관문입니다. 예외로 던져 버리던 습관이 있으면 if err != nil이 지루하게 느껴지지만, 실패 경로가 코드에 그대로 남는다는 점이 나중에 값을 합니다.

완료 기준: 표준 라이브러리 문서를 보고 처음 쓰는 패키지를 스스로 익힐 수 있으면 됩니다.
체크포인트: 로그 파일을 읽어 상태 코드별 건수를 집계하고 결과를 JSON으로 출력하는 CLI 도구를 만들어 보십시오.
자주 막히는 곳: 에러를 _로 무시하고 넘어가는 습관입니다. 초반에 이걸 허용하면 나중에 원인 추적이 불가능해집니다.

자료는 Go 기초 문법 가이드와 에러 처리 가이드가 출발점입니다.

2단계 — 동시성

서버는 본질적으로 여러 요청을 동시에 처리합니다. 고루틴과 채널로 작업을 나누고 합치는 법, 그리고 공유 상태를 언제 잠가야 하는지를 익히는 단계입니다. 문법보다 무엇이 공유되고 있는지 파악하는 눈이 핵심입니다.

완료 기준: 경쟁 상태가 의심될 때 어디를 봐야 하는지 알고, 레이스 검출 도구를 돌려 확인할 수 있으면 됩니다.
체크포인트: 여러 URL을 동시에 요청해 결과를 모으되, 동시 실행 개수에 상한을 두고 실패한 항목만 재시도하는 프로그램을 만들어 보십시오.
자주 막히는 곳: 고루틴을 띄우기만 하고 종료를 관리하지 않는 것입니다. 취소 신호를 전달하는 구조를 처음부터 넣는 습관을 들이십시오. 상한 없는 동시 실행은 상대 서버를 공격하는 꼴이 되기도 합니다.

체크포인트 — 동시 실행에 상한을 두기

package main

// 여러 URL을 동시에 요청하되 동시 실행 개수를 제한하고,
// 실패한 항목만 다시 시도한다.
import (
	"context"
	"net/http"
	"sync"
	"time"
)

const maxInFlight = 5 // 상한이 없으면 상대 서버를 공격하는 꼴이 된다

type result struct {
	url  string
	code int
	err  error
}

func fetchAll(ctx context.Context, urls []string) []result {
	sem := make(chan struct{}, maxInFlight)
	out := make([]result, len(urls))
	var wg sync.WaitGroup

	for i, u := range urls {
		wg.Add(1)
		go func(i int, u string) {
			defer wg.Done()
			sem <- struct{}{}        // 자리 하나를 얻을 때까지 대기
			defer func() { <-sem }()

			req, _ := http.NewRequestWithContext(ctx, http.MethodGet, u, nil)
			resp, err := http.DefaultClient.Do(req)
			if err != nil {
				out[i] = result{u, 0, err}
				return
			}
			defer resp.Body.Close()
			out[i] = result{u, resp.StatusCode, nil}
		}(i, u)
	}
	wg.Wait()
	return out
}

func main() {
	// 취소 신호를 전달할 통로를 처음부터 만들어 둔다
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	_ = fetchAll(ctx, []string{"https://example.com"})
}

고루틴·채널 가이드에서 패턴을 확인할 수 있습니다.

3단계 — HTTP 서버와 데이터베이스

실제 서비스의 뼈대를 만드는 단계입니다. 라우팅, 요청 검증, 응답 형식, 그리고 데이터베이스 연동까지 이어집니다. 프레임워크를 고르기 전에 표준 라이브러리만으로 서버를 한 번 세워 보는 편이 좋습니다. 프레임워크가 무엇을 대신해 주는지가 그때 보입니다.

완료 기준: 잘못된 입력이 들어왔을 때 서버가 죽지 않고 적절한 상태 코드로 응답하며, 그 판단 기준을 설명할 수 있으면 됩니다.
체크포인트: 인증이 붙은 CRUD API를 만들고, 데이터베이스 스키마와 인덱스를 직접 설계해 보십시오.
자주 막히는 곳: 세 가지가 반복됩니다. 첫째, 커넥션 풀 설정을 기본값 그대로 두어 부하가 몰릴 때 고갈되는 경우. 둘째, 타임아웃을 걸지 않아 느린 외부 호출 하나가 서버 전체를 묶는 경우. 셋째, 목록 조회에서 각 행마다 연관 데이터를 다시 조회하는 N+1 패턴입니다.

체크포인트 — 타임아웃과 커넥션 풀 설정

package main

// 기본값 그대로 두면 부하가 몰릴 때 가장 먼저 무너지는 두 지점.
import (
	"database/sql"
	"net/http"
	"time"
)

func newDB(dsn string) (*sql.DB, error) {
	db, err := sql.Open("postgres", dsn)
	if err != nil {
		return nil, err
	}
	// 기본값은 무제한에 가깝다. DB의 최대 연결 수를 넘지 않게 잡는다.
	db.SetMaxOpenConns(25)
	db.SetMaxIdleConns(25)
	db.SetConnMaxLifetime(5 * time.Minute)
	return db, nil
}

func newServer(h http.Handler) *http.Server {
	return &http.Server{
		Addr:              ":8080",
		Handler:           h,
		ReadHeaderTimeout: 5 * time.Second,  // 느린 헤더 공격 차단
		ReadTimeout:       15 * time.Second,
		WriteTimeout:      30 * time.Second,
		IdleTimeout:       60 * time.Second,
	}
}

// 외부 호출에도 반드시 타임아웃을 건다.
// http.DefaultClient는 기본 타임아웃이 없어 응답이 없으면 영원히 기다린다.
var client = &http.Client{Timeout: 3 * time.Second}

4단계 — 컨테이너와 배포

내 컴퓨터에서 도는 것과 서버에서 도는 것은 다릅니다. 컨테이너 이미지를 만들고, 환경별 설정을 분리하고, 배포 후 상태를 확인하는 흐름을 익히는 단계입니다. Kubernetes까지 갈지는 상황에 따라 다르니, 먼저 컨테이너 하나를 제대로 띄우는 데 집중하십시오.

완료 기준: 배포한 서비스가 살아 있는지 확인하는 방법과, 문제가 생겼을 때 로그를 어디서 봐야 하는지 알면 됩니다.
체크포인트: 3단계에서 만든 API를 컨테이너로 감싸 배포하고, 헬스 체크와 구조화된 로그를 붙여 보십시오.
자주 막히는 곳: 설정과 비밀 값을 이미지 안에 굽는 것입니다. 이미지는 환경에 무관해야 하고, 접속 정보나 키는 실행 시점에 주입해야 합니다.

5단계 — 관측과 설계

여기서부터는 코드를 더 쓰는 것보다 돌아가는 시스템을 이해하는 일이 중심이 됩니다. 로그·지표·추적을 붙여 문제를 재현 없이 좁힐 수 있게 만들고, 그 정보를 바탕으로 캐시나 큐 같은 구조를 도입할지 판단하는 단계입니다.

완료 기준: 응답이 느려졌다는 제보를 받았을 때, 추측이 아니라 데이터로 원인 후보를 좁힐 수 있으면 됩니다.
체크포인트: 자신의 서비스에 지연 시간과 에러율 지표를 붙이고, 부하를 걸어 어느 지점이 먼저 무너지는지 관찰해 보십시오.
자주 막히는 곳: 측정 없이 마이크로서비스로 쪼개는 것입니다. 분리는 성능이 아니라 조직과 배포 경계의 문제이고, 잘못 쪼개면 장애 지점만 늘어납니다.

시스템 설계 면접 자료에서 설계 판단을 말로 정리하는 연습을 할 수 있습니다.

지금은 미뤄도 되는 것

  • Kubernetes 전체 기능 — 컨테이너 하나를 안정적으로 운영해 본 뒤에 필요해집니다.
  • 마이크로서비스 분리 — 하나의 서비스가 감당이 안 될 때 고민할 문제입니다.
  • 프레임워크 비교 — 하나로 끝까지 만들어 본 뒤에야 차이가 보입니다.
  • 이벤트 소싱·CQRS 같은 고급 패턴 — 문제가 먼저 있고 패턴이 나중입니다.

바로 시작할 학습 자료