백엔드 데이터 인프라 5편. 새 프로젝트의 시작점 CREATE DATABASE와 createdb 명령으로 PostgreSQL 데이터베이스를 만드는 표준 패턴 풀어쓴 학습 노트.
이 글은 백엔드 데이터 인프라 시리즈 70편 중 5편이에요. 4편 psql 까지 손에 익혔으니, 이번 5편은 새 프로젝트의 가장 첫 단계 — 데이터베이스 한 개 만들기.
데이터베이스 = 격리된 작업 공간
3편 아키텍처 의 4단계 계층을 떠올려보면 Cluster → Database → Schema → Table 구조예요. 새 프로젝트마다 "Database" 한 개를 만들고 그 안에서 모든 스키마·테이블 작업을 진행해요.
같은 클러스터 안에 여러 DB가 있어도 서로 격리돼 있어서, 다른 DB의 테이블에 직접 접근할 수 없고 사용자 권한도 DB 단위로 부여돼요.
두 가지 방법 — CREATE DATABASE vs createdb
방법 1 — SQL CREATE DATABASE
psql에 접속한 상태에서:
CREATE DATABASE myappdb;
한 줄이면 끝, 옵션 없이도 충분해요.
방법 2 — createdb OS 명령
2편 설치 에 함께 설치된 OS 명령이에요.
createdb myappdb
내부적으로는 CREATE DATABASE 를 호출하는데, 셸 스크립트나 CI(지속적 통합 자동화 파이프라인)에서 쓰기 편해요.
CREATE DATABASE 옵션
옵션 없는 단순 형태 외에 실무에서 자주 박는 6가지가 있어요.
CREATE DATABASE myappdb
WITH OWNER = appuser
ENCODING = 'UTF8'
LC_COLLATE = 'ko_KR.UTF-8'
LC_CTYPE = 'ko_KR.UTF-8'
TEMPLATE = template0
CONNECTION LIMIT = 100;
| 옵션 | 의미 |
|---|---|
OWNER |
데이터베이스 소유자 (사용자) |
ENCODING |
문자 인코딩 — UTF8 표준 |
LC_COLLATE |
문자열 정렬 순서 |
LC_CTYPE |
대소문자·문자 분류 |
TEMPLATE |
복제 원본 (보통 template0) |
CONNECTION LIMIT |
동시 접속 수 제한 |
UTF8 인코딩
한글·이모지·다국어를 다룬다면 UTF8이 무조건이에요. 옛 "EUC-KR" 같은 인코딩은 위험하니 운영 DB를 처음 만들 때 명시해 두세요.
TEMPLATE — template0 vs template1
PG에는 두 가지 "DB 템플릿" 이 있어요.
template1= 기본 템플릿. CREATE DATABASE 시 자동 복제 원본template0= 빈 깨끗한 템플릿. 다른 인코딩·로케일 지정 시 사용
template1 에 다른 사람이 객체를 박아 뒀다면 우리 새 DB에도 같이 박히기 때문에, 깨끗하게 시작하려면 TEMPLATE template0 을 명시해 주세요.
CONNECTION LIMIT
특정 DB의 동시 접속 수를 제한해서, 운영 환경에서 "한 DB가 폭주해 다른 DB까지 영향" 가는 상황을 막아줘요.
소유자(OWNER) 모범 사례
운영 환경에선 슈퍼유저(postgres)가 DB 소유자가 아닌 게 표준이에요.
-- 1. 앱 전용 사용자 생성
CREATE USER appuser WITH PASSWORD 'strongpass' LOGIN;
-- 2. 그 사용자 소유로 DB 생성
CREATE DATABASE myappdb OWNER appuser;
-- 3. 앱은 appuser 로 접속 (postgres 슈퍼유저 X)
이유는 간단해요. 앱이 슈퍼유저로 접속하면 "앱 버그 = DB 전체 위험" 이 되니까요. 최소 권한 원칙을 따르는 거죠.
접속 변경
-- psql에서
\c myappdb
-- 결과
You are now connected to database "myappdb" as user "postgres".
myappdb=#
새 DB로 접속이 바뀌면 프롬프트도 postgres=# 에서 myappdb=# 로 변해요.
외부 접속 (명령행)
psql -U appuser -d myappdb -h localhost -W
# -W 옵션 = 비밀번호 프롬프트
데이터베이스 목록 확인
\l
# 또는 더 자세히
SELECT datname, pg_size_pretty(pg_database_size(datname)) AS size
FROM pg_database
ORDER BY pg_database_size(datname) DESC;
pg_database 시스템 뷰가 클러스터 안 모든 DB 정보를 제공해요.
데이터베이스 삭제 — DROP DATABASE
DROP DATABASE myappdb;
-- 위험 방지 IF EXISTS
DROP DATABASE IF EXISTS myappdb;
또는 OS 명령으로:
dropdb myappdb
함정 — 자기 자신에게 접속해 있으면 못 지워요.
ERROR: cannot drop the currently open database
해결은 다른 DB로 접속을 바꾼 뒤 시도하는 거예요.
\c postgres
DROP DATABASE myappdb;
또 다른 사용자가 접속해 있어도 못 지워요.
-- 다른 접속 모두 끊기
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE datname = 'myappdb' AND pid <> pg_backend_pid();
-- 그 후 DROP
DROP DATABASE myappdb;
운영에선 운영 시간 외에 백업 후에만 DROP하세요. 절대 실수로 누르면 안 돼요.
Spring Boot 연결
자바 백엔드 입문 13편 application.yml 에서 다룬 패턴 그대로예요.
spring:
datasource:
url: jdbc:postgresql://localhost:5432/myappdb
username: appuser
password: ${DB_PASSWORD}
driver-class-name: org.postgresql.Driver
URL의 마지막 /myappdb 가 "방금 만든 DB" 인데, 이 부분이 다르면 다른 DB로 붙어요.
멱등 스크립트 — 마이그레이션 패턴
CI에서 "이미 있으면 패스, 없으면 생성" 으로 돌리는 패턴이에요.
-- DB가 이미 있는지 확인
SELECT 1 FROM pg_database WHERE datname = 'myappdb';
-- 또는 PL/pgSQL 익명 블록
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_database WHERE datname = 'myappdb') THEN
PERFORM dblink_exec('host=localhost user=postgres',
'CREATE DATABASE myappdb');
END IF;
END
$$;
또는 셸에서 OS 명령으로:
psql -U postgres -tAc "SELECT 1 FROM pg_database WHERE datname='myappdb'" | grep -q 1 \
|| createdb -U postgres myappdb
함정 5가지
(1) UTF8 안 박음
오래된 시스템에서 "SQL_ASCII" 인코딩으로 시작했다가 한글이 깨지는 일이 잦아요. 처음부터 UTF8로 박아두세요.
(2) 슈퍼유저가 앱 DB 소유
앱 코드의 SQL 인젝션·버그가 슈퍼유저 권한으로 동작하면 클러스터 전체가 위험해져요.
(3) template1 오염
template1 에 박은 객체는 모든 새 DB에 자동 복제돼서, "실수로 박힌 테스트 테이블" 이 운영 DB로 퍼지는 사고로 이어져요.
(4) CONNECTION LIMIT 없음
기본값이 무제한이라 폭주 시 클러스터 전체가 영향받아요. 운영에선 적정 값을 박아두세요.
(5) DROP 실수
DROP DATABASE 는 취소 불가예요. 백업과 운영 시간 외 작업, 다중 확인을 꼭 거치세요.
CREATE DATABASE myappdb OWNER appuser ENCODING 'UTF8' TEMPLATE template0 CONNECTION LIMIT 100;. 다섯 가지 옵션이 90% 운영 시나리오에 충분.
한 줄 정리 — CREATE DATABASE name OWNER user ENCODING 'UTF8' TEMPLATE template0 표준 패턴. 슈퍼유저 X — 앱 전용 사용자에 소유권. DROP은 백업 + 운영 시간 외. createdb·dropdb OS 명령은 스크립트에 편함.
시험 직전 한 번 더 — CREATE DATABASE 입문자가 매번 헷갈리는 것
- CREATE DATABASE = SQL 명령
- createdb = OS 명령 (내부적으로 같은 SQL)
- 새 프로젝트 = 새 DB 한 개
- 인코딩 = UTF8 무조건
- OWNER = 앱 전용 사용자 (슈퍼유저 X)
- TEMPLATE = template0 (깨끗한 시작)
- CONNECTION LIMIT = 동시 접속 제한
- LC_COLLATE = 정렬 순서 (한글 = ko_KR.UTF-8)
- LC_CTYPE = 대소문자·문자 분류
\c db= psql 접속 변경\l= DB 목록pg_database= 시스템 뷰 (DB 정보)- DROP DATABASE = 취소 불가 — 백업 후 운영 시간 외
- 자기 자신 DB 안에서 DROP 불가 — 다른 DB로 이동 후
- 다른 사용자 접속 있으면 =
pg_terminate_backend로 끊고 - IF EXISTS = 안전 가드
\c db user= 사용자 변경- Spring
jdbc:postgresql://host:port/dbname의 dbname - 멱등 스크립트 = pg_database 검사 + 조건부 생성
- template1 오염 주의 — 모든 새 DB에 복제됨
- pg_size_pretty = DB 크기 사람 친화적 표시
- 한 클러스터 안 DB 수 = 보통 5~50개, 큰 시스템 = 100+
시리즈 다른 편 (앞뒤 글 모음)
이전 글:
- 1편 — PostgreSQL이란 + MySQL과의 비교
- 2편 — PostgreSQL 설치 (Docker·brew·apt 3가지)
- 3편 — PostgreSQL 아키텍처 (클라이언트·서버·DB·테이블)
- 4편 — psql 첫 접속과 기본 명령
다음 글: