백엔드 데이터 인프라 21편. DDL의 큰 그림 — 테이블·스키마·시퀀스·인덱스·뷰·도메인·트리거 PG 객체 종류와 명명 패턴 풀어쓴 학습 노트.
이 글은 백엔드 데이터 인프라 시리즈 70편 중 21편이에요. 9편 CREATE TABLE 에서 "테이블 만들기" 를 다뤘다면, 이번 21편은 DDL(Data Definition Language, 객체 정의 SQL)의 큰 그림을 그려요. 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 같은 형태)
신규 코드라면 SQL 표준 호환이 되는 IDENTITY를 권장해요.
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(PG의 절차형 언어)·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(Java Persistence API, 자바 ORM 표준)가 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
시리즈 다른 편 (앞뒤 글 모음)
이전 글:
- 16편 — 뷰 VIEW와 MATERIALIZED VIEW
- 17편 — 트랜잭션 BEGIN COMMIT ROLLBACK
- 18편 — 윈도우 함수와 고급 SQL
- 19편 — PostgreSQL SQL 어휘 구조
- 20편 — PostgreSQL SQL 문법 전반
다음 글: