백엔드 데이터 인프라 21편. DDL의 큰 그림 — 테이블·스키마·시퀀스·인덱스·뷰·도메인·트리거 PG 객체 종류와 명명 패턴 풀어쓴 학습 노트.
이 글은 백엔드 데이터 인프라 시리즈 70편 중 21편이에요. 9편 CREATE TABLE 에서 "테이블 만들기" 를 다뤘다면, 이번 21편은 DDL의 큰 그림 — PG가 다루는 객체 전체 + 분류 + 명명 패턴.
DDL이 헷갈리는 이유
CREATE·ALTER·DROP 만 안다고 끝이 아니에요. PG는 "테이블" 외에도 — 시퀀스·인덱스·뷰·머티리얼라이즈드 뷰·도메인·타입·함수·트리거·확장·외래 데이터 래퍼 등 10가지 이상 객체 종류. 각각 별도 DDL.
이 글은 "어떤 객체가 있고, 각각 언제 쓰나" 의 지도. 22편부터 각 객체 깊이.
PG 객체 — 10대 종류
| 객체 | 의미 | 자주 쓰임 |
|---|---|---|
| TABLE | 데이터 저장 단위 | ★★★ 거의 모든 곳 |
| SCHEMA | 객체 그룹·네임스페이스 | ★★★ 큰 시스템 |
| INDEX | 조회 가속 | ★★★ 운영 필수 |
| SEQUENCE | 자동 증가 정수 생성기 | ★★★ BIGSERIAL 내부 |
| VIEW | 저장된 쿼리 | ★★ 추상화 |
| MATERIALIZED VIEW | 캐시된 뷰 | ★★ 대시보드 |
| DOMAIN | 사용자 정의 타입 + 제약 | ★ 가끔 |
| TYPE (Composite·Enum) | 사용자 정의 타입 | ★ 가끔 |
| FUNCTION·PROCEDURE | 사용자 정의 함수 | ★★ 트리거·복잡 로직 |
| TRIGGER | 이벤트 자동 실행 | ★★ 감사·자동 갱신 |
| EXTENSION | PG 확장 (postgis·pg_cron 등) | ★★ 특수 기능 |
| FOREIGN TABLE | 외부 데이터 매핑 | ★ 가끔 |
SCHEMA — 객체 그룹
3편 아키텍처 의 4단계 계층(Cluster→Database→Schema→Table) 중 3단계.
CREATE SCHEMA app;
CREATE SCHEMA audit;
CREATE SCHEMA reporting;
CREATE TABLE app.users (...);
CREATE TABLE audit.user_changes (...);
큰 시스템 = 스키마로 분리. 기본 = public 스키마.
search_path
SET search_path TO app, public;
SELECT * FROM users; -- 자동으로 app.users 찾음
스키마 명시 안 하면 — search_path 순서대로 검색.
SEQUENCE — 자동 증가 정수
CREATE SEQUENCE user_id_seq START WITH 1 INCREMENT BY 1;
INSERT INTO users (id, name) VALUES (nextval('user_id_seq'), 'Alice');
BIGSERIAL 도 내부적으로 시퀀스.
CREATE TABLE users (id BIGSERIAL PRIMARY KEY);
-- 자동으로 users_id_seq 시퀀스 생성
-- DEFAULT nextval('users_id_seq') 박힘
IDENTITY — 표준 SQL 스타일
CREATE TABLE users (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name TEXT
);
PG 10+ 표준. BIGSERIAL과 차이: - BIGSERIAL = 시퀀스 별도 노출 (직접 접근 가능) - IDENTITY = 시퀀스 묵음 (DEFAULT 같은 형태)
신규 코드 = IDENTITY 권장 (SQL 표준 호환).
INDEX — 조회 가속
CREATE INDEX idx_users_email ON users(email);
CREATE UNIQUE INDEX idx_users_email_unique ON users(email);
CREATE INDEX idx_orders_created ON orders(created_at DESC);
PG 인덱스 6종 — 34~36편에서 깊이.
VIEW·MATERIALIZED VIEW
16편 VIEW 에서 다룬 둘. 정의·캐시·갱신.
DOMAIN — 사용자 정의 타입
CREATE DOMAIN email_address AS TEXT
CHECK (VALUE ~ '^[^@]+@[^@]+\.[^@]+$');
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
email email_address
);
도메인 = "TEXT + 이메일 형식 검증" 묶음 타입. 여러 테이블에서 재사용.
TYPE — Composite·Enum
Composite Type
CREATE TYPE address AS (
zip_code TEXT,
city TEXT,
street TEXT
);
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
home_addr address,
work_addr address
);
Enum
CREATE TYPE order_status AS ENUM ('PENDING', 'PAID', 'SHIPPED', 'DELIVERED', 'CANCELED');
CREATE TABLE orders (
id BIGSERIAL PRIMARY KEY,
status order_status NOT NULL DEFAULT 'PENDING'
);
CHECK 제약 + TEXT 의 대안. ENUM 단점 = "새 값 추가 시 ALTER TYPE" 필요.
FUNCTION·PROCEDURE
CREATE OR REPLACE FUNCTION fullname(first TEXT, last TEXT)
RETURNS TEXT AS $$
SELECT first || ' ' || last;
$$ LANGUAGE sql;
SELECT fullname('Alice', 'Kim'); -- 'Alice Kim'
복잡한 비즈니스 로직을 PG 안에. PL/pgSQL·SQL·Python·JavaScript 등 다양한 언어.
-- PL/pgSQL 함수
CREATE FUNCTION calculate_age(birth DATE)
RETURNS INTEGER AS $$
DECLARE
age INTEGER;
BEGIN
age := EXTRACT(YEAR FROM AGE(birth));
RETURN age;
END;
$$ LANGUAGE plpgsql;
PROCEDURE = PG 11+ 추가. 트랜잭션 안 commit 가능 (FUNCTION은 불가).
TRIGGER — 이벤트 자동 실행
CREATE FUNCTION update_modified()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER users_modified
BEFORE UPDATE ON users
FOR EACH ROW EXECUTE FUNCTION update_modified();
updated_at 자동 갱신·감사 로그·캐시 무효화 등.
타이밍: - BEFORE — 변경 전 - AFTER — 변경 후 - INSTEAD OF — 뷰의 INSERT·UPDATE 가로채기
EXTENSION — PG 확장
PG는 확장 기능 풍부.
CREATE EXTENSION IF NOT EXISTS pgcrypto; -- 암호화 함수
CREATE EXTENSION IF NOT EXISTS uuid-ossp; -- UUID 생성
CREATE EXTENSION IF NOT EXISTS pg_trgm; -- 유사 문자열 검색
CREATE EXTENSION IF NOT EXISTS postgis; -- 지리 정보
CREATE EXTENSION IF NOT EXISTS pg_cron; -- cron 스케줄러
CREATE EXTENSION IF NOT EXISTS pg_stat_statements; -- 쿼리 통계
운영 PG 거의 다 이 중 일부 활용.
FOREIGN TABLE — 외부 데이터
CREATE EXTENSION postgres_fdw;
CREATE SERVER otherdb FOREIGN DATA WRAPPER postgres_fdw
OPTIONS (host '...', dbname 'other');
CREATE FOREIGN TABLE remote_users (id BIGINT, name TEXT)
SERVER otherdb OPTIONS (schema_name 'public', table_name 'users');
SELECT * FROM remote_users; -- 다른 PG의 테이블처럼
다른 DB·CSV·MongoDB 등을 "PG 테이블처럼" 조회. 데이터 마이그레이션·분산 쿼리에 강력.
명명 패턴 — 한국 회사 표준
| 객체 | 패턴 | 예 |
|---|---|---|
| 테이블 | snake_case 복수 | users·order_items |
| 컬럼 | snake_case | user_id·created_at |
| 인덱스 | idx_<테이블>_<컬럼> |
idx_users_email |
| 유니크 인덱스 | uniq_<테이블>_<컬럼> |
uniq_users_email |
| 외래 키 | fk_<테이블>_<참조> |
fk_orders_user |
| 시퀀스 | <테이블>_<컬럼>_seq |
users_id_seq (자동) |
| 트리거 | tr_<테이블>_<이벤트> |
tr_users_updated |
| 함수 | snake_case | calculate_age |
| 뷰 | vw_* 또는 그대로 |
active_users·vw_user_stats |
DDL 트랜잭션 — PG 강점
BEGIN;
ALTER TABLE users ADD COLUMN status TEXT;
ALTER TABLE users ADD COLUMN level INTEGER;
CREATE INDEX idx_users_status ON users(status);
COMMIT; -- 또는 ROLLBACK
PG는 DDL이 트랜잭션 안에서 동작. MySQL은 일부 DDL이 암묵 커밋. 이게 PG의 큰 운영 장점.
함정 5가지
(1) BIGSERIAL vs IDENTITY 혼용
같은 시스템 안에 둘 섞이면 — 일관성 깨짐. 신규 = IDENTITY 통일.
(2) ENUM 변경 어려움
ENUM 새 값 추가 = ALTER TYPE ... ADD VALUE. 값 삭제는 매우 어려움. "안정적으로 정해진 상태" 만 ENUM.
(3) DOMAIN 너무 많음
도메인 = 재사용 좋지만 — 너무 많이 만들면 관리 부담. 정말 "여러 테이블에 반복" 일 때만.
(4) FUNCTION 비즈니스 로직 박기
DB에 비즈니스 로직 박으면 — 버전 관리·테스트 어려움. 핵심 로직 = 앱 코드, DB는 "무결성·자동화" 만.
(5) EXTENSION 운영에 임의 박기
운영 PG에 확장 추가는 — DBA·인프라 팀 협업. 클러스터 재시작 필요한 경우도.
★★★ 거의 매일 = TABLE·INDEX·SCHEMA·SEQUENCE. ★★ 자주 = VIEW·TRIGGER·EXTENSION. ★ 가끔 = DOMAIN·TYPE·FUNCTION·FOREIGN TABLE. 입문자는 ★★★ 4가지부터.
한 줄 정리 — PG DDL 객체 10대 종류. TABLE·SCHEMA·INDEX·SEQUENCE 4가지가 운영 핵심. PG는 DDL이 트랜잭션 안 동작 — 운영 강점. 확장(EXTENSION)으로 강력한 추가 기능. JPA가 DDL 자동 생성하지만 — 운영 깊이 이해 필요.
시험 직전 한 번 더 — DDL 입문자가 매번 헷갈리는 것
- DDL 객체 10대 = TABLE·SCHEMA·INDEX·SEQUENCE·VIEW·MATERIALIZED VIEW·DOMAIN·TYPE·FUNCTION·TRIGGER·EXTENSION·FOREIGN TABLE
- SCHEMA = 네임스페이스 (4단계 계층 3단계)
- search_path = 스키마 검색 순서
- SEQUENCE = 자동 증가 정수 생성기
- IDENTITY = SQL 표준 시퀀스 (PG 10+)
- BIGSERIAL = PG 옛 스타일 (시퀀스 노출)
- IDENTITY 권장 = 신규 표준
- DOMAIN = TEXT + CHECK 묶음 타입
- ENUM = 고정 값 집합
- ENUM 값 추가 = ALTER TYPE ADD VALUE
- ENUM 값 삭제 = 어려움
- Composite Type = 컬럼 묶음
- FUNCTION = 사용자 정의 함수 (sql·plpgsql 등)
- PROCEDURE = PG 11+ 트랜잭션 가능
- TRIGGER = BEFORE·AFTER·INSTEAD OF
- updated_at 자동 갱신 = TRIGGER 표준
- EXTENSION = pgcrypto·uuid-ossp·pg_trgm·postgis·pg_cron 등
- FOREIGN TABLE = 외부 DB·CSV 매핑
- PG DDL = 트랜잭션 안 (MySQL과 차이)
- 명명 = snake_case lowercase
- 인덱스 명명 = idx_<테이블>_<컬럼>
- FK 명명 = fk_<테이블>_<참조>
- 비즈니스 로직 = 앱 코드 (DB는 무결성만)
- ★★★ 4가지 = TABLE·SCHEMA·INDEX·SEQUENCE
시리즈 다른 편
- Part 2 SQL Language 깊이: 19편 어휘 · 20편 SQL 문법 · 21편 (현재 글)
시리즈 다음 글
다음 글(22편)에서는 CREATE TABLE 깊이 — PARTITION·INHERITS·UNLOGGED·TEMPORARY 고급 옵션.
공식 문서: PostgreSQL 18 — Data Definition에서 더 자세한 사양을 확인할 수 있어요.