- Postgres를 더 생산적이고 안전하게 사용하는 데 도움이 되는 실용적인 패턴들을 정리한 글
- 각 패턴은 작지만 누적되면 큰 차이를 만들어냄
UUID 기본 키 사용
- UUID는 무작위이기 때문에 정렬이나 인덱스 성능 면에서 단점이 있음
- 숫자 ID보다 공간을 더 많이 차지함
- 하지만 다음과 같은 장점이 있음
- DB에 연결하지 않고도 UUID를 생성할 수 있음
- 외부에 안전하게 노출 가능함
-
gen_random_uuid()
를 사용해 기본 키로 UUID를 자동 생성할 수 있음
created_at과 updated_at 필드 항상 추가
- 디버깅 시 레코드 생성 및 변경 시점을 아는 것이 매우 유용함
-
updated_at
은 트리거를 통해 자동으로 갱신되도록 설정 가능함
- 함수는 한 번만 만들고, 트리거는 각 테이블에 적용해야 함
외래 키에는 on update/delete restrict 설정
- 외래 키 제약 조건을 설정할 때
on update restrict on delete restrict
를 반드시 사용해야 함
- 데이터 삭제 시 실수로 연쇄 삭제가 발생하지 않도록 방지함
- 저장 공간은 저렴하지만 데이터 복구는 매우 어렵기 때문에 보수적으로 처리하는 것이 좋음
스키마 사용 권장
- 기본 스키마는
public
이지만, 애플리케이션이 커지면 별도의 스키마로 분리하는 것이 좋음
- 스키마는 네임스페이스처럼 작동하며, 서로 다른 스키마 간에도 조인이 가능함
- 테이블 수가 많아질수록 스키마를 활용하는 것이 가독성과 유지보수에 유리함
Enum 테이블 패턴 사용
- PostgreSQL의 enum 타입이나 check constraint 대신 enum 테이블을 사용하는 방식이 더 유연함
- enum 값을 별도 테이블로 관리하면, 메타데이터를 추가하거나 enum 값을 쉽게 확장 가능함
- 외래 키로 enum 테이블의 값을 참조하여 제약 조건을 유지함
테이블 이름은 단수형으로 지정
- 테이블 이름은 복수가 아닌 단수형으로 지정하는 것이 바람직함
- 쿼리 작성 시 단수형이 더 명확하며, 복수형은 소유격이나 의미적 혼란을 야기할 수 있음
조인 테이블은 기계적으로 이름 지정
- 다대다 관계를 위한 조인 테이블은 두 테이블명을 이어붙여 명명하는 것이 안전하고 명확함
- 예:
person_pet
- 조합에 대한 고유 인덱스를 추가하여 중복 방지
삭제 대신 soft delete 사용
- 데이터를 실제로 삭제하기보다, 삭제 시점을 나타내는
revoked_at
같은 timestamp 필드를 사용하는 것이 좋음
- 삭제 여부뿐 아니라, 언제 삭제되었는지를 추적할 수 있음
- Boolean 값보다 timestamp가 더 많은 정보를 제공함
상태(Status)는 로그 테이블로 표현
- 단일 컬럼으로 상태를 표현하는 대신, 상태 변경 이력을 별도 테이블로 저장
- 상태 발생 시점은
valid_at
컬럼으로 명시
- 최신 상태를 빠르게 조회할 수 있도록
latest
플래그 및 유니크 인덱스 + 트리거를 설정함
- 이는 비동기 이벤트 처리나 순서가 뒤섞일 수 있는 상황에서 유리함
특별한 행에는 system_id 추가
- enum 테이블 외에도, 특정 "시스템 행"이 필요한 경우가 있음
-
system_id
텍스트 필드를 nullable로 추가하고 유니크 인덱스를 설정
-
system_id
를 통해 특정 행을 명확하게 조회 가능
뷰(View)는 최소한으로 사용
- 뷰는 복잡한 쿼리를 추상화하는 데 유용하지만 유지보수가 어려움
- 컬럼 제거 시 뷰 재생성이 필요
- 뷰 위에 뷰를 만들면 성능 및 가독성 문제가 생김
- 필요한 만큼만 신중하게 사용할 것
JSON 쿼리 적극 활용
- Postgres는 JSON 저장뿐 아니라 JSON 반환 쿼리도 매우 강력함
- 중첩된 관계를 한 번의 쿼리로 JSON 형태로 반환 가능함
- N+1 문제 없이 필요한 모든 데이터를 한 번에 가져올 수 있음
- 단점: 타입 정보 손실, 전체 데이터를 한 번에 메모리에 불러와야 함
- 성능이나 구조 상 장점이 더 큼