JWT 인증까지 구현했다면,
이제 서버는 **“진짜 운영 환경을 전제로 한 단계”**로 넘어갈 준비가 됐다.
그 핵심이 바로 PostgreSQL 연동과 스키마 관리(Migration) 다.
이번 챕터에서는
- PostgreSQL을 Go 서버에 연결하는 기본 흐름
- database/sql 기준의 실무 설정
- Migration을 왜, 어떻게 관리하는지
를 과하지 않게, 하지만 실무 기준으로 정리한다.
왜 SQLite가 아니라 PostgreSQL인가
이전 챕터에서는 구조 설명을 위해
SQLite나 메모리 저장소를 사용했다.
하지만 운영 환경에서는 대부분 다음 이유로 PostgreSQL을 선택한다.
- 동시성 처리에 강함
- 트랜잭션 안정성
- JSON, 인덱스, 확장 기능 풍부
- 클라우드 환경과 궁합이 좋음
즉,
“Go 서버 실무”를 이야기하려면 PostgreSQL은 사실상 기본 전제다.
PostgreSQL 연동의 전체 흐름
Go 서버에서 PostgreSQL을 사용하는 기본 흐름은 항상 같다.
- DB 드라이버 로드
- 커넥션 풀 설정
- Ping으로 연결 확인
- Repository에 *sql.DB 주입
- Migration으로 스키마 관리
[이미지: Go + PostgreSQL 연동 전체 흐름]
이 중에서 4번 이후가 구조를 가르는 포인트다.
PostgreSQL 드라이버 선택
가장 많이 쓰이는 드라이버는 다음 두 가지다.
- lib/pq
- pgx
이번 챕터에서는 database/sql과 궁합이 좋은 pgx 드라이버 기준으로 설명한다.
import (
"database/sql"
_ "github.com/jackc/pgx/v5/stdlib"
)
⚠️ 드라이버는 import만 해도 된다
실제 사용은 database/sql이 담당한다
DB 연결 문자열 구성
PostgreSQL 연결 문자열은 보통 환경 변수로 관리한다.
postgres://user:password@localhost:5432/mydb?sslmode=disable
dsn := os.Getenv("DATABASE_URL")
db, err := sql.Open("pgx", dsn)
if err != nil {
log.Fatal(err)
}
⚠️ 주의할 점
- sql.Open은 실제 연결을 바로 만들지 않는다
- 반드시 Ping()으로 연결 확인
if err := db.Ping(); err != nil {
log.Fatal(err)
}
커넥션 풀 설정 (실무에서 매우 중요)
*sql.DB는 단순한 커넥션이 아니라
커넥션 풀(pool) 이다.
db.SetMaxOpenConns(25)
db.SetMaxIdleConns(25)
db.SetConnMaxLifetime(5 * time.Minute)
실무 기준 감각은 이렇다.
- 너무 크면 DB가 먼저 죽는다
- 너무 작으면 서버가 막힌다
- 기본값에 의존하지 않는다
이 설정은
서버 성능 이슈의 1차 원인이 되는 경우가 정말 많다.
Repository 구조에 DB 주입하기
이전 챕터에서 만든 구조를 그대로 사용한다.
type UserRepository struct {
db *sql.DB
}
func NewUserRepository(db *sql.DB) *UserRepository {
return &UserRepository{db: db}
}
- DB는 main에서 한 번만 생성
- repository에 주입
- service는 repository 인터페이스만 의존
이 흐름이 깨지면
DB 의존성이 전파되기 시작한다.
PostgreSQL 기준 SQL 예시
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
email TEXT NOT NULL UNIQUE,
password TEXT NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT now()
);
PostgreSQL에서는 보통
- SERIAL / BIGSERIAL
- TIMESTAMP
- UNIQUE 제약
을 적극적으로 활용한다.
SQL 실행 예시 (Insert)
func (r *UserRepository) Create(email, password string) (User, error) {
var user User
err := r.db.QueryRow(`
INSERT INTO users (email, password)
VALUES ($1, $2)
RETURNING id, email, created_at
`, email, password).
Scan(&user.ID, &user.Email, &user.CreatedAt)
return user, err
}
PostgreSQL의 RETURNING 문법은
Go + SQL 조합에서 굉장히 자주 쓰인다.
이제 중요한 문제: 스키마를 어떻게 관리할 것인가
여기서 반드시 짚고 넘어가야 한다.
SQL 파일을 수동으로 실행하는 방식은
팀 개발, 운영 환경에서 바로 한계가 온다
그래서 필요한 게 Migration이다.
Migration이 필요한 이유
Migration은 단순히 “테이블 생성” 문제가 아니다.
- 개발/스테이징/운영 환경 동기화
- 변경 이력 추적
- 롤백 가능성 확보
즉,
DB 스키마도 코드처럼 관리하기 위한 장치다.
Migration 기본 개념
Migration은 보통 다음 형태를 가진다.
0001_create_users_table.up.sql
0001_create_users_table.down.sql
- up: 적용
- down: 되돌리기
이 숫자가
DB 변경 이력의 순서가 된다.
[이미지: DB Migration 적용 흐름]
실무 기준 Migration 관리 원칙
⚠️ 이 기준은 정말 중요하다
- 이미 배포된 migration은 수정하지 않는다
- 변경이 필요하면 새로운 migration을 추가한다
- 로컬에서만 테스트 후 커밋한다
이 원칙이 깨지면
환경 간 스키마 불일치가 발생한다.
Migration은 애플리케이션 코드와 분리한다
Migration은 보통
- CLI 도구
- 별도 실행 단계
로 관리한다.
즉,
- 서버 실행 = API 제공
- Migration 실행 = 스키마 관리
를 명확히 분리한다.
이 분리가 안 되면
운영 중 예측 불가능한 문제가 생긴다.
이 챕터에서 가져가야 할 감각
이 단계에서 중요한 건 문법이 아니다.
- DB는 “연결”보다 “운영”이 중요하다
- 커넥션 풀 설정은 필수다
- 스키마는 코드처럼 관리해야 한다
이 감각이 생기면
ORM을 쓰든, SQL을 쓰든
DB 관련 실수가 눈에 띄게 줄어든다.
정리
PostgreSQL 연동의 핵심은
연결 성공이 아니라, 운영 가능한 구조를 만드는 것이다.
- database/sql + pgx 조합
- 명시적인 커넥션 풀 설정
- Repository 패턴 유지
- Migration으로 스키마 관리
이제 서버는
“돌아가는 코드”를 넘어서
“운영 가능한 서비스”의 형태를 갖추기 시작했다.
다음 챕터에서는 이 DB 구조 위에서
👉 트랜잭션 설계 (service 단위 트랜잭션)
👉 또는 API 에러 응답 규약 통일하기
중 하나로 이어가면 흐름이 딱 좋다.
'backend' 카테고리의 다른 글
| 백엔드 MVC 패턴 이유 | 왜 쓰고 어디서 한계가 생길까 (0) | 2026.03.31 |
|---|---|
| Go API 에러 응답 규약 정리: 실무에서 흔들리지 않는 기준 만들기 (1) | 2026.02.04 |
| Go 인증/인가 구현하기: JWT 기반 인증 미들웨어 설계 (2) | 2026.02.03 |
| Gin으로 API 서버 옮기기: 기존 net/http 구조 그대로 활용하기 (0) | 2026.02.02 |
| Go에서 DB 연동하기: database/sql과 Repository 패턴으로 구조 잡기 (0) | 2026.02.01 |