56P by GN⁺ 2일전 | ★ favorite | 댓글 4개
  • 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 문제 없이 필요한 모든 데이터를 한 번에 가져올 수 있음
  • 단점: 타입 정보 손실, 전체 데이터를 한 번에 메모리에 불러와야 함
  • 성능이나 구조 상 장점이 더 큼

조인 테이블은 기계적으로 이름 지정

이름 지을 때 이런 룰이 있다는 거 자체가 좋은 것 같아요~

UUID7을 고려하면 시간순 정렬은 되는것 아닐까요?

soft delete시 timestamp 넣는 방법 좋군요
기본키로 UUID 넣으면 시간순 정렬이 안되니, snowflake id 또는 ulid를 쓰는 것도 좋을 것 같습니다. 이 경우는 각 서버가 sequence number를 들고 있어야 하긴 하지만요