5분

Postgres 스키마 설계 플래닝 모드: 단계별 접근법

Postgres 스키마 설계 플래닝 모드는 코드 생성 전에 엔티티, 제약, 인덱스, 마이그레이션을 정의해 이후 재작업을 줄여줍니다.

Postgres 스키마 설계 플래닝 모드: 단계별 접근법

코드 생성 전에 Postgres 스키마를 왜 계획해야 하나요

엔드포인트와 모델을 데이터베이스 구조가 확실해지기 전에 만들면 같은 기능을 두 번 고치는 일이 자주 생깁니다. 데모용으로는 동작하던 앱이 실제 데이터와 엣지 케이스를 만나면 모두 취약해 보이기 시작하죠.

대부분의 재작성은 예측 가능한 세 가지 문제에서 옵니다:

  • 엔티티가 불명확함(이름이나 그룹화가 잘못됨)
  • 규칙이 강제되지 않음(제약이 없음)
  • 성능 문제가 늦게 드러남(인덱스를 나중에 추가함)

각각은 코드, 테스트, 클라이언트 앱 전반에 걸쳐 파급되는 변경을 강요합니다.

Postgres 스키마 계획은 먼저 데이터 계약을 결정하고 그에 맞춰 코드를 생성하는 것을 의미합니다. 실전에서는 엔티티, 관계, 중요한 몇 개의 쿼리를 적고 제약, 인덱스, 마이그레이션 방식을 선택한 다음 어떤 도구도 테이블과 CRUD를 스캐폴드하기 전에 이 과정을 마치는 식입니다.

이것은 Koder.ai 같은 바이브-코딩 플랫폼을 사용할 때 더 중요합니다. 많은 코드를 빠르게 생성할 수 있는 건 좋지만, 스키마가 확정되어 있을 때 훨씬 더 신뢰할 수 있습니다. 생성된 모델과 엔드포인트는 나중에 수정이 적게 필요합니다.

계획을 건너뛰면 흔히 이렇게 문제가 생깁니다:

  • “User” 테이블이 조용히 사용자, 관리자, 팀이 섞인 테이블이 되어버림
  • 고유성이 강제되지 않아 중복 레코드가 나타남
  • 삭제가 히스토리를 망가뜨림(참조 동작이 결정되지 않음)
  • 인기 있는 리스트 페이지가 느려짐(적절한 인덱스가 계획되지 않음)

좋은 스키마 계획은 단순합니다: 엔티티를 평문으로 설명하고, 테이블과 컬럼 초안을 만들고, 핵심 제약과 인덱스를 정한 뒤 제품 성장에 따라 안전하게 변경할 수 있는 마이그레이션 전략을 세우는 것입니다.

테이블이 아니라 데이터 필요성에서 시작하세요

스키마 계획은 앱이 무엇을 기억해야 하고 사람들이 그 데이터를 가지고 무엇을 할 수 있어야 하는지를 먼저 정할 때 가장 잘 작동합니다. 목표를 2~3문장으로 적으세요. 간단히 설명할 수 없다면 불필요한 테이블을 만들 가능성이 큽니다.

다음으로 데이터를 생성하거나 변경하는 행동에 집중하세요. 이런 행동들이 실제 행(row)의 근원이며 무엇을 검증해야 하는지 드러냅니다. 명사가 아니라 동사로 생각하세요.

예를 들어 예약 앱은 예약 생성, 일정 변경, 취소, 환불, 고객에게 메시지 전송 같은 동사가 필요할 것입니다. 이런 동사는 시간 슬롯, 상태 변경, 금액 같은 무엇을 저장해야 하는지 빠르게 제시합니다.

읽기 경로도 캡처하세요. 읽기가 구조와 인덱싱을 결정합니다. 사람들이 실제로 사용할 화면이나 리포트를 나열하고 어떻게 데이터를 자르는지 적으세요: 날짜별 정렬된 “내 예약”, 고객 이름이나 예약 참조로 검색하는 관리자 화면, 위치별 일일 매출, 누가 언제 무엇을 변경했는지 보여주는 감사 뷰 등.

마지막으로 감사 기록, 소프트 삭제, 멀티테넌시 분리, 개인정보 규칙(예: 연락처 세부 정보를 누가 볼 수 있는지 제한 등)처럼 스키마 선택에 영향을 주는 비기능적 요구사항을 적어두세요.

이후 코드를 생성할 계획이라면 이 메모들은 강력한 프롬프트가 됩니다. 무엇이 필요한지, 무엇이 변경될 수 있는지, 무엇이 검색 가능해야 하는지를 분명히 적어줍니다. Koder.ai를 사용하는 경우에는 생성 전에 이걸 적어두면 플래닝 모드가 추측 대신 실제 요구사항을 기반으로 작동해 훨씬 효과적입니다.

평문으로 엔티티와 관계 정의하기

테이블을 건드리기 전에 앱이 저장하는 내용을 평문으로 적으세요. 반복해서 나오는 명사들을 먼저 나열합니다: user, project, message, invoice, subscription, file, comment. 각 명사는 후보 엔티티입니다.

그다음 각 엔티티에 대해 한 문장으로 답하세요: 그것은 무엇이고 왜 존재하는가? 예: “Project는 사용자가 작업을 그룹화하고 다른 사람을 초대하기 위해 만드는 워크스페이스다.” 이렇게 하면 data, items, misc 같은 모호한 테이블을 방지할 수 있습니다.

소유권도 중요한 결정이며 거의 모든 쿼리에 영향을 줍니다. 각 엔티티에 대해:

  • 누가 생성하는가,
  • 누가 볼 수 있는가,
  • 누가 변경할 수 있는가,
  • 소유자가 삭제되면 어떻게 할 것인가(유지, 이전, 삭제)

이제 레코드를 어떻게 식별할지도 결정하세요. UUID는 웹/모바일/백그라운드 잡 등 여러 곳에서 생성될 수 있거나 예측 가능한 ID를 원하지 않을 때 좋습니다. bigint ID는 더 작고 빠릅니다. 사람이 읽기 쉬운 식별자가 필요하면 기본 키로 강제하지 말고 별도 고유 컬럼(예: 계정 내에서 고유한 project_code)으로 두세요.

마지막으로 관계를 다이어그램하기 전에 말로 적으세요: 사용자 하나가 여러 프로젝트를 가진다, 프로젝트 하나가 여러 메시지를 가진다, 사용자가 여러 프로젝트에 속할 수 있다 등. 각 링크를 필수인지 선택인지로 표시하세요(예: “메시지는 반드시 프로젝트에 속해야 한다” vs “송장은 프로젝트에 속할 수도 있다”). 이 문장들이 나중에 코드 생성의 진실의 원천이 됩니다.

엔티티를 테이블과 컬럼으로 옮기기

엔티티가 평문으로 명확해지면 각 엔티티를 테이블로 바꾸고 실제로 저장해야 할 사실에 맞는 컬럼을 만드세요.

먼저 이름과 타입은 고수할 수 있는 패턴으로 정하세요. 일관된 규칙을 선택하세요: snake_case 컬럼명, 같은 의미에는 같은 타입, 예측 가능한 기본 키. 타임스탬프는 timestamptz를 선호해 시간대 때문에 놀라지 않게 하세요. 금액은 numeric(12,2)(또는 센트 정수)로 두고 부동소수점은 피하세요.

상태 필드는 Postgres enum을 쓰거나 허용 값을 제어하기 위해 textCHECK 제약을 사용할 수 있습니다.

필수와 선택은 규칙을 NOT NULL로 번역해 결정하세요. 값이 행의 의미를 위해 반드시 있어야 하면 필수로, 정말로 알 수 없거나 해당하지 않으면 NULL 허용으로 하세요.

계획에 포함할 실용적인 기본 컬럼 세트:

  • id (uuid 또는 bigint, 한 가지 접근을 선택하고 일관되게 사용)
  • created_atupdated_at
  • 소프트 삭제가 정말 필요하면 deleted_at
  • 누가 했는지 로그가 필요하면 created_by

다대다 관계는 거의 항상 조인 테이블로 만드세요. 예: 여러 사용자가 앱에서 협업할 수 있다면 app_members를 만들어 app_iduser_id를 두고 쌍에 대해 고유성을 강제하세요.

히스토리를 초기에 고려하세요. 버전 관리가 필요하면 불변 테이블(app_snapshots)을 계획해 각 행을 app_id로 연결하고 created_at으로 타임스탬프를 찍으세요.

데이터를 보호하는 제약 추가하기

체크리스트를 코드로 전환
체크리스트를 빌드 플랜으로 바꿔 더 안정적인 첫 버전을 빨리 출시하세요.

제약은 스키마의 가드레일입니다. 어떤 서비스나 스크립트, 관리자 도구가 데이터베이스를 건드려도 반드시 참이어야 할 규칙들을 결정하세요.

아이덴티티와 관계부터 시작하세요. 모든 테이블은 기본 키가 필요하고, 어떤 ‘belongs to’ 필드든 단순 정수가 아니라 실제 외래 키여야 합니다.

그다음 중복이 실제로 해를 끼치는 곳에는 고유성을 추가하세요(예: 같은 이메일을 가진 두 계정, (order_id, product_id)가 중복된 라인 항목).

초기에 계획해야 할 고가치 제약:

  • Primary keys: UUID 또는 bigint 같은 일관된 스타일 선택
  • Foreign keys: 관계를 명시하고 고아 행 방지
  • Unique constraints: 이메일, 사용자명 같은 비즈니스 식별자 및 '하나만 있어야 하는' 규칙
  • Check constraints: amount >= 0, status IN ('draft','paid','canceled'), rating BETWEEN 1 AND 5 같은 저비용 규칙
  • Not null: 현실에서 반드시 필요한 필드는 NOT NULL로

Cascade 동작은 미리 생각하면 나중에 많은 문제를 막습니다. 사람들이 실제로 무엇을 기대하는지 물어보세요. 고객이 삭제되면 주문이 사라져야 할까요? 대개는 그렇지 않으니 제한 삭제(restrict)를 사용하고 기록을 유지하세요. 반면 주문 항목처럼 부모 없이는 의미가 없는 데이터는 부모에서 CASCADE가 적절할 수 있습니다.

나중에 모델과 엔드포인트를 생성하면 이 제약들은 명확한 요구사항이 됩니다: 어떤 오류를 처리해야 하는지, 어떤 필드가 필수인지, 어떤 엣지 케이스가 설계상 불가능한지 알려줍니다.

실제 쿼리에서 인덱스 계획하기

인덱스는 단 하나의 질문에 답해야 합니다: 실제 사용자에게 무엇이 빨라야 하는가.

먼저 출시할 화면들과 API 호출을 생각하세요. 상태로 필터하고 최신순으로 정렬하는 리스트 페이지는 관련 레이블을 불러오는 상세 페이지와 다른 요구를 가집니다.

인덱스를 선택하기 전에 평문으로 5–10개의 쿼리 패턴을 적으세요. 예: “지난 30일간 내 인보이스를 보여주고, 결제/미결제로 필터하며 created_at으로 정렬” 또는 “프로젝트 열람 시 해당 프로젝트의 작업을 due_date로 정렬해 나열”. 이렇게 하면 인덱스 선택이 실제 사용에 기반하게 됩니다.

좋은 초기사용 인덱스는 보통 조인에 사용되는 외래 키 컬럼, 자주 필터되는 컬럼(예: status, user_id, created_at), 그리고 안정적인 멀티-필터 쿼리를 위한 1~2개의 복합 인덱스를 포함합니다. 예: 항상 account_id로 필터하고 시간순 정렬하면 (account_id, created_at) 같은 인덱스가 유용합니다.

복합 인덱스의 순서는 중요합니다. 가장 자주 필터하고 선택도가 높은 컬럼을 앞에 두세요. 테넌트별로 항상 tenant_id로 필터한다면 많은 인덱스의 맨 앞에 두는 것이 맞습니다.

모든 것을 ‘혹시 모르니’ 인덱싱하지 마세요. 인덱스는 INSERT와 UPDATE 비용을 증가시키며 드문 쿼리를 약간 빠르게 하는 것이 그 비용을 정당화하지 않을 수 있습니다.

텍스트 검색은 별도로 계획하세요. 간단한 포함 검색만 필요하면 처음에는 ILIKE로 충분할 수 있습니다. 검색이 핵심이라면 초기에 풀텍스트 검색(tsvector)을 계획해 나중에 재설계하는 일을 피하세요.

코드 생성 전에 마이그레이션 전략 결정하기

스키마는 최초 테이블 생성으로 ‘완성’되지 않습니다. 기능을 추가하거나 실수를 고치거나 데이터를 더 알게 될 때마다 바뀝니다. 미리 마이그레이션 전략을 정하면 코드 생성 이후 고통스러운 재작업을 피할 수 있습니다.

간단한 규칙: 데이터베이스 변경은 작은 단계로, 기능 단위로 하세요. 각 마이그레이션은 리뷰하기 쉽고 모든 환경에서 안전하게 실행될 수 있어야 합니다.

파괴적 변경을 안전하게 처리하는 방법

대부분의 실패는 컬럼 이름 변경, 제거, 타입 변경에서 옵니다. 한 번에 모든걸 하지 말고 안전한 경로를 계획하세요:

  • 먼저 새 컬럼 추가(필요하면 nullable)
  • 데이터를 백필(backfill)
  • 앱이 계속 동작해야 하면 잠시 이중 쓰기(구/신 필드 모두에 쓰기)
  • 읽기를 새 필드로 전환하고 나중에 옛 컬럼을 정리

이 과정은 단계가 더 많지만 실전에서는 중단과 긴급 패치를 줄여 더 빠릅니다.

시드 데이터도 마이그레이션의 일부입니다. 어떤 참조 테이블(roles, statuses, countries, plan types 등)이 항상 있어야 하는지 정하고 예측 가능하게 만드세요. 이러한 테이블의 INSERT/UPDATE는 전용 마이그레이션에 넣어 모든 개발자와 배포 환경이 같은 결과를 갖게 하세요.

로컬, 개발, 프로덕션 간 일관성 규칙

초기에 기대치를 정하세요:

  • 동일한 마이그레이션이 동일한 순서로 실행되어야 합니다.
  • 프로덕션에서 수동 핫픽스는 대응하는 마이그레이션 없이 하지 마세요.
  • 모든 마이그레이션은 명확한 순방향(foward) 스토리와 현실적인 롤백 계획을 가져야 합니다.

롤백이 항상 완벽한 'down migration'인 것은 아닙니다. 때로는 백업 복원이 더 현실적인 롤백입니다. Koder.ai를 사용한다면 위험한 변경 전 스냅샷과 롤백 시점을 정해 빠른 복구에 활용하는 것도 고려하세요.

복사해 쓰기 좋은 간단한 예

계획에서 배포까지
스키마와 마이그레이션이 준비되면 앱을 배포하고 호스팅하세요.

팀에 사람들이 가입하고 프로젝트를 만들고 작업을 추적하는 작은 SaaS 앱을 상상해보세요.

먼저 엔티티와 첫날에 필요한 필드만 나열합니다:

  • users: id, email, full_name, created_at
  • teams: id, org_id, name, created_at
  • team_members: team_id, user_id, role, joined_at
  • projects: id, team_id, name, status, created_at
  • tasks: id, project_id, assignee_user_id (nullable), title, state, due_date (nullable), created_at

관계는 직관적입니다: 팀은 여러 프로젝트를 가지며, 프로젝트는 여러 작업을 갖고 사용자는 team_members를 통해 팀에 참여합니다. 작업은 프로젝트에 속하고 사용자가 배정될 수 있습니다.

몇 가지 제약을 더해 늦게 발견하는 버그를 예방하세요:

  • users.email은 고유하게 만들기(예: citext를 쓰면 대소문자 구분 없는 고유성 적용)
  • 같은 조직 내에서 팀 이름은 고유: UNIQUE (org_id, name)
  • 멤버십 중복 방지: UNIQUE (team_id, user_id) on team_members

인덱스는 실제 화면에 맞추세요. 예: 작업 리스트가 프로젝트와 상태로 필터하고 최신순으로 정렬하면 tasks (project_id, state, created_at DESC) 같은 인덱스를 계획하세요. “내 작업”이 중요하면 tasks (assignee_user_id, state, due_date) 같은 인덱스가 도움됩니다.

마이그레이션은 첫 세트를 안전하고 단순하게 유지하세요: 테이블 생성, 기본 키, 외래 키, 핵심 고유 제약을 먼저 만드세요. 사용 후 증명이 필요한 변경(예: 소프트 삭제 deleted_at 추가)은 추후에 추가하는 것이 좋습니다. 그런 변경은 인덱스를 조정하거나 ‘활성 작업’ 뷰를 업데이트하는 작업을 수반할 수 있습니다.

흔한 실수와 피하는 방법

대부분의 재작성은 최초 스키마에 규칙과 실제 사용 세부사항이 빠져 있기 때문에 발생합니다. 좋은 계획은 완벽한 다이어그램이 아니라 초기 함정을 미리 발견하는 것입니다.

일반적 오류는 중요한 규칙을 애플리케이션 코드에만 두는 것입니다. 값이 고유해야 하거나 존재해야 하거나 범위 안에 있어야 한다면 데이터베이스가 강제해야 합니다. 그렇지 않으면 백그라운드 잡, 새로운 엔드포인트, 수동 임포트가 그 규칙을 우회할 수 있습니다.

또 다른 실수는 인덱스를 나중 문제로 생각하는 것. 출시 후 인덱스를 추가하면 추측에 기반한 작업이 되고 실제 느린 쿼리가 조인이나 상태 필터인 경우 잘못된 것을 인덱싱할 수 있습니다.

조인 테이블도 조용한 버그의 원천입니다. 조인 테이블이 중복을 막지 않으면 같은 관계가 두 번 저장되어 “사용자가 역할을 두 번 가지고 있다” 같은 문제로 몇 시간을 허비할 수 있습니다.

감사 로그, 소프트 삭제, 이벤트 히스토리가 필요하다는 것을 뒤늦게 깨닫고 테이블을 먼저 만든 뒤에 추가하는 일도 흔합니다. 이런 추가는 엔드포인트와 리포트 전반에 영향을 미칩니다.

마지막으로 JSON 컬럼은 유혹적이지만 핵심 비즈니스 필드를 위해 쓰면 검사와 인덱싱이 어려워집니다. JSON은 진짜로 가변적인 페이로드에만 쓰고 핵심 필드는 컬럼으로 승격시키세요.

코드 생성 전에 빠르게 점검할 목록:

  • 핵심 규칙을 제약으로 옮기기: NOT NULL, CHECK, UNIQUE, 외래 키
  • 실제 쿼리 3–5개를 적고 그에 맞춰 인덱스 설계
  • 조인 테이블에 복합 UNIQUE 추가(예: user_id + role_id)
  • 감사 기록이 필요하면 별도 테이블로 모델링
  • JSON은 옵션 속성에만 쓰고 중요한 필드는 컬럼으로 옮기기

모델과 엔드포인트 생성 전 빠른 체크리스트

플래닝 모드로 시작하기
스키마 노트를 명확한 프롬프트로 바꿔 스캐폴딩 후 이름 변경을 줄이세요.

여기서 잠시 멈추고 플랜이 모델을 생성해도 깜짝 질문을 유발하지 않을 만큼 충분한지 확인하세요. 목표는 완벽이 아니라 나중에 재작성으로 이어지는 간극(빠진 관계, 불명확한 규칙, 실제 사용과 맞지 않는 인덱스)을 잡는 것입니다.

간단한 사전 점검:

  • 각 엔티티에 대해 누가 소유하는지(user, org, system)와 삭제의 의미(하드 삭제, 소프트 삭제, 보관)를 한 문장으로 적기
  • 모든 테이블에 대해 컬럼 타입, 필수 여부, 기본값, 타임스탬프가 앱에서 설정되는지 DB에서 설정되는지 확인
  • 절대로 깨지면 안 될 규칙 적기: 기본 키, 외래 키, 고유성 규칙, 간단한 체크(예: amount >= 0 또는 허용 상태)
  • 상위 3–5개의 화면 또는 API 호출을 골라 정확한 필터와 정렬 순서를 적고 인덱스가 일치하는지 확인
  • 첫 마이그레이션 스케치: 초기 스키마, 첫날에 반드시 있어야 할 데이터(시드), 곧 예상되는 한 가지 변경(예: 상태 추가, 이름 필드 분리, 조직 도입)

간단한 건선 테스트: 내일 팀원이 합류한다고 가정했을 때 그 사람이 매시간 “이 필드는 NULL일 수 있나요?” 또는 “삭제하면 어떻게 되죠?” 같은 질문을 계속하지 않고도 첫 엔드포인트를 만들 수 있을까요?

다음 단계: 계획에서 코드로, 재작성은 적게

플랜이 분명하고 주요 흐름이 문서상에서 타당하면 실행 가능한 형태로 전환하세요: 실제 스키마와 마이그레이션.

초기 마이그레이션은 테이블, 타입(ENUM 사용 시), 필수 제약을 생성하는 것으로 시작하세요. 첫 버전은 작지만 정확하게 만드세요. 약간의 시드 데이터를 넣고 앱이 실제로 필요로 하는 몇 가지 쿼리를 실행해 보세요. 흐름이 어색하면 마이그레이션 기록이 짧을 때 스키마를 바로 고치면 됩니다.

테이블, 키, 네이밍이 충분히 안정적일 때 모델과 엔드포인트를 생성하세요. 그래야 다음 날 모든 걸 이름 바꾸느라 허비하지 않습니다.

재작성 감소를 위한 실용적 루프:

  • 테스트로 알게 된 것을 플랜에 반영
  • 새 마이그레이션 추가(다른 사람이 이미 쓴 이전 마이그레이션 수정 금지)
  • 스키마 변경이 있으면 모델과 엔드포인트 재생성
  • 같은 흐름과 쿼리를 다시 실행해 문제 없는지 확인
  • 핵심 경로가 안정되면 추가 필드와 인덱스를 붙이기

초기에 DB에서 검증할 것과 API 레이어에서 검증할 것을 결정하세요. 영구 규칙(외래 키, 고유성, CHECK)은 DB에 넣고 자주 바뀌는 복잡한 교차 테이블 로직이나 임시 제한은 API에 두는 것이 좋습니다.

Koder.ai를 사용한다면 우선 플래닝 모드에서 엔티티와 마이그레이션에 합의한 뒤 Go + PostgreSQL 백엔드를 생성하는 것이 합리적입니다. 변경이 꼬이면 스냅샷과 롤백으로 알려진 안정 버전으로 빨리 돌아간 뒤 스키마 계획을 조정하세요.

자주 묻는 질문

왜 모델과 엔드포인트를 생성하기 전에 Postgres 스키마를 계획해야 하나요?

먼저 스키마를 계획하세요. 스키마는 안정적인 데이터 계약(테이블, 키, 제약)을 정해 생성된 모델과 엔드포인트가 나중에 계속 이름 바꾸거나 재작성할 필요가 없게 합니다.

실무 팁: 엔티티, 관계, 핵심 쿼리를 적고 제약, 인덱스, 마이그레이션을 고정한 뒤 코드를 생성하세요.

다이어그램에 집착하지 않고 스키마 계획을 빠르게 시작하려면?

앱이 무엇을 기억해야 하고 사용자가 무엇을 할 수 있어야 하는지 2–3문장으로 적는 것이 가장 빠른 시작입니다.

그다음에 적으세요:

  • 데이터를 생성/변경하는 행동(동사)
  • 사람들이 실제로 사용할 화면/리포트(읽기 경로)
  • 감사 기록, 소프트 삭제, 멀티테넌시, 프라이버시 같은 비기능적 요구사항

이 정도면 과도하게 설계하지 않고 테이블을 디자인할 충분한 명확성이 생깁니다.

핵심 엔티티는 어떻게 식별하나요?

반복해서 나오는 명사를 먼저 나열하세요 (user, project, invoice, task 등). 각 엔티티마다 ‘무엇이고 왜 존재하는가’ 한 문장을 추가하세요.

명확히 설명할 수 없다면 itemsmisc 같은 모호한 테이블이 생길 가능성이 큽니다.

Postgres에서 UUID와 bigint 중 무엇을 써야 하나요?

스키마 전반에 일관된 ID 전략을 사용하는 것이 중요합니다.

  • UUID: 웹/모바일/백그라운드 잡 등 다양한 곳에서 생성되거나 예측 불가능한 ID가 필요할 때 적합합니다.
  • bigint: 서버에서만 생성되는 경우 더 작고 조금 더 빠릅니다.

사람이 읽기 쉬운 식별자가 필요하면 기본 키로 쓰지 말고 별도의 고유 컬럼(예: project_code)으로 두세요.

외래 키의 삭제 동작(CASCADE vs RESTRICT)은 어떻게 선택하나요?

관계마다 사용자 기대와 보존 필요성을 기준으로 결정하세요.

보편적인 기본값:

  • 기록을 보관해야 하면 RESTRICT/NO ACTION 사용(예: 고객 → 주문)
  • 부모 없이는 의미가 없는 종속 데이터는 CASCADE 사용(예: 주문 → 주문 항목)

삭제 동작은 API 동작과 엣지 케이스에 큰 영향을 주므로 초기에 결정하세요.

첫날에 어떤 제약을 추가해야 하나요?

데이터베이스에 영구 규칙을 넣어 모든 쓰기 주체(API, 스크립트, 임포트, 관리자 도구)가 같은 규칙을 따르도록 하세요.

우선순위:

  • 모든 테이블에 PRIMARY KEY
  • 모든 belongs to 컬럼에 FOREIGN KEY
  • 중복이 문제인 곳에 UNIQUE (이메일, (team_id, user_id) 등)
  • 간단한 규칙은 CHECK(예: 음수 금액 금지, 허용 상태)
  • 반드시 존재해야 하는 필드는 NOT NULL
과도한 인덱싱 없이 인덱스를 어떻게 선택하나요?

추측이 아니라 실제 쿼리 패턴에서 시작하세요.

먼저 5–10개의 평문 쿼리를 적으세요(필터 + 정렬). 그다음 인덱스를 고르세요:

  • 조인에 쓰이는 외래 키 컬럼
  • status, user_id, created_at처럼 자주 필터되는 컬럼
  • 안정적인 멀티-필터 패턴을 위한 복합 인덱스(예: (account_id, created_at))

모두에 인덱스를 다는 것을 피하세요. 인덱스는 INSERT/UPDATE 비용을 늘립니다.

다대다 관계를 어떻게 모델링해야 하나요?

조인 테이블을 만들고 두 외래 키와 복합 고유 제약을 추가하세요.

예시 패턴:

  • team_members(team_id, user_id, role, joined_at)
  • UNIQUE(team_id, user_id) 추가로 중복 방지

이렇게 하면 같은 관계가 두 번 저장되는 것 같은 미묘한 버그를 예방할 수 있습니다.

Postgres에서 어떤 타입과 컬럼 패턴을 기본으로 써야 하나요?

기본 권장 타입:

  • 타임스탬프에는 timestamptz (시간대 관련 문제를 줄여줌)
  • 금액은 numeric(12,2) 또는 센트 정수(부동소수점 금지)
  • 상태값은 Postgres enum 또는 CHECK로 강제

같은 개념에는 같은 타입을 사용해 일관성을 유지하세요.

생성된 코드가 나중에 깨지지 않게 하려면 어떤 마이그레이션 전략이 좋나요?

작고 검토 가능한 마이그레이션을 사용하고 한 번에 큰 파괴적 변경을 피하세요.

안전한 절차:

  • 먼저 새 컬럼 추가(필요하면 NULL 허용)
  • 데이터 백필
  • 서비스 중단 없이 필요하면 잠시 이중 쓰기(dual-write)
  • 읽기를 새 컬럼으로 전환
  • 이후에 오래된 컬럼 제거

시드/참조 데이터도 마이그레이션에 포함해 모든 환경이 동일하도록 하세요.

Related posts