โ† Back to list
eco2-team

postgres-schema

by eco2-team

๐ŸŒฑ ์ด์ฝ”์—์ฝ”(Ecoยฒ) BE

โญ 0๐Ÿด 0๐Ÿ“… Jan 25, 2026

SKILL.md


name: postgres-schema description: PostgreSQL ์Šคํ‚ค๋งˆ ์„ค๊ณ„ ๊ฐ€์ด๋“œ. DDL ์ž‘์„ฑ, ํ…Œ์ด๋ธ” ์„ค๊ณ„, ์ธ๋ฑ์Šค ์ „๋žต, ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์‹œ ์ฐธ์กฐ. "schema", "ddl", "table", "migration", "index", "constraint" ํ‚ค์›Œ๋“œ๋กœ ํŠธ๋ฆฌ๊ฑฐ.

PostgreSQL Schema Design Guide

Quick Reference

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                     Schema Design Principles                     โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚                                                                  โ”‚
โ”‚  1. TEXT ๊ธฐ๋ณธ โ†’ VARCHAR ์˜ˆ์™ธ (RFC ํ‘œ์ค€๋งŒ)                         โ”‚
โ”‚  2. TIMESTAMPTZ ํ•„์ˆ˜ (ํƒ€์ž„์กด ๋ณด์กด)                                โ”‚
โ”‚  3. ENUM ์„ ํƒ์  (๊ณ ์ • ๊ฐ’ ์ง‘ํ•ฉ๋งŒ)                                   โ”‚
โ”‚  4. Partial Index ํ™œ์šฉ (NULL ์ œ์™ธ)                                โ”‚
โ”‚  5. CASCADE ๋ช…์‹œ์  ์„ค์ •                                           โ”‚
โ”‚                                                                  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Naming Conventions

TypePatternExample
Schema๋„๋ฉ”์ธ ์†Œ๋ฌธ์žchat, users, auth
Tablesnake_case ๋ณต์ˆ˜ํ˜•sessions, social_accounts
Columnsnake_caseuser_id, created_at
PKidid UUID PRIMARY KEY
FK Column{entity}_idsession_id, user_id
Indexidx_{table}_{columns}idx_sessions_user_updated
Constraint{type}_{table}_{column}fk_user, chk_role

Column Type Strategy

1. TEXT ๊ธฐ๋ณธ ์›์น™

-- ์ข‹์€ ์˜ˆ: ๊ธธ์ด ๋ถˆํ™•์‹คํ•œ ์ปฌ๋Ÿผ์€ TEXT
content TEXT NOT NULL,
title TEXT NOT NULL,
description TEXT,

-- ๋‚˜์œ ์˜ˆ: ์ž„์˜ ๊ธธ์ด ์ œํ•œ
title VARCHAR(255) NOT NULL,  -- ์™œ 255์ธ๊ฐ€?

2. VARCHAR ์˜ˆ์™ธ (RFC/ํ‘œ์ค€ ๊ธฐ๋ฐ˜๋งŒ)

-- ์ด๋ฉ”์ผ: RFC 5321 (64 + 1 + 255 = 320)
email VARCHAR(320) NOT NULL,

-- ์ „ํ™”๋ฒˆํ˜ธ: E.164 ํ‘œ์ค€ (์ตœ๋Œ€ 15์ž๋ฆฌ + ๊ตญ๊ฐ€์ฝ”๋“œ)
phone_number VARCHAR(20) UNIQUE,

-- ISO ์ฝ”๋“œ๋ฅ˜
country_code CHAR(2),        -- ISO 3166-1 alpha-2
currency_code CHAR(3),       -- ISO 4217
language_code VARCHAR(5),    -- BCP 47 (ko-KR)

3. ENUM ์‚ฌ์šฉ ๊ธฐ์ค€

-- ์ข‹์€ ์˜ˆ: ๊ฐ’ ์ง‘ํ•ฉ์ด ๊ณ ์ •๋˜๊ณ  ๋ณ€๊ฒฝ์ด ๋“œ๋ฌธ ๊ฒฝ์šฐ
role VARCHAR(10) NOT NULL CHECK (role IN ('user', 'assistant')),
type VARCHAR(20) NOT NULL CHECK (type IN ('text', 'image', 'generated_image')),

-- ๋Œ€์•ˆ: PostgreSQL ENUM ํƒ€์ž… (๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ฃผ์˜)
CREATE TYPE message_role AS ENUM ('user', 'assistant');

ENUM vs CHECK ๋น„๊ต:

๋ฐฉ์‹์žฅ์ ๋‹จ์ 
CHECK๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์‰ฌ์›€, ๊ฐ’ ์ถ”๊ฐ€ ๊ฐ„๋‹จ์˜คํƒ€ ๊ฐ€๋Šฅ์„ฑ
ENUM TYPEํƒ€์ž… ์•ˆ์ „์„ฑ, ์ €์žฅ ๊ณต๊ฐ„ ํšจ์œจ๊ฐ’ ์ถ”๊ฐ€ ์‹œ ALTER TYPE ํ•„์š”

Timestamp Rules

-- ํ•„์ˆ˜: TIMESTAMPTZ ์‚ฌ์šฉ (ํƒ€์ž„์กด ๋ณด์กด)
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),

-- ๊ธˆ์ง€: TIMESTAMP WITHOUT TIME ZONE
created_at TIMESTAMP NOT NULL,  -- ํƒ€์ž„์กด ์ •๋ณด ์†์‹ค

SQLAlchemy์—์„œ ์ž๋™ ๊ฐฑ์‹ :

updated_at: Mapped[datetime] = mapped_column(
    TIMESTAMP(timezone=True),
    server_default=func.now(),
    onupdate=func.now(),
)

Constraint Patterns

Foreign Key

-- CASCADE: ๋ถ€๋ชจ ์‚ญ์ œ ์‹œ ์ž์‹๋„ ์‚ญ์ œ (1:N ์ข…์† ๊ด€๊ณ„)
CONSTRAINT fk_session FOREIGN KEY (session_id)
    REFERENCES chat.sessions(id) ON DELETE CASCADE,

-- SET NULL: ๋ถ€๋ชจ ์‚ญ์ œ ์‹œ NULL๋กœ ์„ค์ • (์„ ํƒ์  ์ฐธ์กฐ)
CONSTRAINT fk_assignee FOREIGN KEY (assignee_id)
    REFERENCES users.accounts(id) ON DELETE SET NULL,

-- RESTRICT: ์ž์‹ ์žˆ์œผ๋ฉด ๋ถ€๋ชจ ์‚ญ์ œ ๋ฐฉ์ง€ (๋ณดํ˜ธ)
CONSTRAINT fk_owner FOREIGN KEY (owner_id)
    REFERENCES users.accounts(id) ON DELETE RESTRICT,

Check Constraints

-- ๊ฐ’ ๊ฒ€์ฆ
CONSTRAINT chk_role CHECK (role IN ('user', 'assistant')),
CONSTRAINT chk_type CHECK (type IN ('text', 'image', 'generated_image')),

-- ๋ฒ”์œ„ ๊ฒ€์ฆ
CONSTRAINT chk_message_count CHECK (message_count >= 0),

Unique Constraints

-- ๋‹จ์ผ ์œ ๋‹ˆํฌ
email VARCHAR(320) UNIQUE NOT NULL,

-- ๋ณตํ•ฉ ์œ ๋‹ˆํฌ (๋ช…๋ช…๋œ ์ œ์•ฝ์กฐ๊ฑด)
CONSTRAINT uq_social_provider UNIQUE (provider, provider_user_id),

Index Strategy

๊ธฐ๋ณธ ์ธ๋ฑ์Šค

-- ์กฐํšŒ ํŒจํ„ด ๊ธฐ๋ฐ˜ ์ธ๋ฑ์Šค
CREATE INDEX idx_sessions_user_updated
    ON chat.sessions(user_id, updated_at DESC);

-- ํžˆ์Šคํ† ๋ฆฌ ์กฐํšŒ์šฉ
CREATE INDEX idx_messages_session_ts
    ON chat.messages(session_id, timestamp DESC);

Partial Index (์กฐ๊ฑด๋ถ€ ์ธ๋ฑ์Šค)

-- NULL ์ œ์™ธ๋กœ ์ธ๋ฑ์Šค ํฌ๊ธฐ ์ตœ์ ํ™”
CREATE INDEX idx_accounts_nickname
    ON users.accounts(nickname)
    WHERE nickname IS NOT NULL;

-- Soft delete ์ง€์›
CREATE INDEX idx_sessions_active
    ON chat.sessions(user_id, updated_at DESC)
    WHERE is_deleted = FALSE;

Soft Delete Pattern

-- ๊ถŒ์žฅ: Boolean ํ”Œ๋ž˜๊ทธ
is_deleted BOOLEAN NOT NULL DEFAULT FALSE,

-- ๋Œ€์•ˆ: ์‚ญ์ œ ์‹œ๊ฐ„ ๊ธฐ๋ก
deleted_at TIMESTAMPTZ,

-- ์ธ๋ฑ์Šค์—์„œ ์‚ญ์ œ๋œ ํ–‰ ์ œ์™ธ
CREATE INDEX idx_sessions_user_updated
    ON chat.sessions(user_id, updated_at DESC)
    WHERE is_deleted = FALSE;

Standard DDL Template

-- 1. Schema ์ƒ์„ฑ
CREATE SCHEMA IF NOT EXISTS {schema_name};

-- 2. Table ์ƒ์„ฑ
CREATE TABLE {schema_name}.{table_name} (
    -- PK
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),

    -- FK
    {parent}_id UUID NOT NULL,

    -- Data columns
    {column} {TYPE} [NOT NULL] [DEFAULT value],

    -- Timestamps
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),

    -- Soft delete (์„ ํƒ)
    is_deleted BOOLEAN NOT NULL DEFAULT FALSE,

    -- Constraints
    CONSTRAINT fk_{parent} FOREIGN KEY ({parent}_id)
        REFERENCES {parent_schema}.{parent_table}(id) ON DELETE CASCADE,
    CONSTRAINT chk_{column} CHECK ({column} IN ('val1', 'val2'))
);

-- 3. Index ์ƒ์„ฑ
CREATE INDEX idx_{table}_{columns}
    ON {schema_name}.{table_name}({columns})
    [WHERE condition];

-- 4. Comment (์„ ํƒ)
COMMENT ON TABLE {schema_name}.{table_name} IS 'ํ…Œ์ด๋ธ” ์„ค๋ช…';
COMMENT ON COLUMN {schema_name}.{table_name}.{column} IS '์ปฌ๋Ÿผ ์„ค๋ช…';

Migration Structure

migrations/schemas/
โ”œโ”€โ”€ 001_users_schema.sql      # users ์Šคํ‚ค๋งˆ
โ”œโ”€โ”€ 002_auth_schema.sql       # auth ์Šคํ‚ค๋งˆ
โ”œโ”€โ”€ 003_chat_schema.sql       # chat ์Šคํ‚ค๋งˆ (์‹ ๊ทœ)
โ””โ”€โ”€ README.md                 # ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๊ฐ€์ด๋“œ

Review Checklist

DDL ๋ฆฌ๋ทฐ ์‹œ ํ™•์ธํ•  ํ•ญ๋ชฉ:

  • TEXT vs VARCHAR: VARCHAR๋Š” RFC ํ‘œ์ค€ ๊ธฐ๋ฐ˜์ธ๊ฐ€?
  • TIMESTAMPTZ: ๋ชจ๋“  ์‹œ๊ฐ„ ์ปฌ๋Ÿผ์ด ํƒ€์ž„์กด ํฌํ•จ์ธ๊ฐ€?
  • FK CASCADE: ์‚ญ์ œ ๋™์ž‘์ด ๋ช…์‹œ์ ์œผ๋กœ ์„ค์ •๋˜์—ˆ๋Š”๊ฐ€?
  • Index: ์ฃผ์š” ์กฐํšŒ ํŒจํ„ด์— ์ธ๋ฑ์Šค๊ฐ€ ์žˆ๋Š”๊ฐ€?
  • Partial Index: Soft delete๋‚˜ NULL ์ปฌ๋Ÿผ์— ์กฐ๊ฑด๋ถ€ ์ธ๋ฑ์Šค ์ ์šฉ?
  • Naming: ์ปจ๋ฒค์…˜์„ ๋”ฐ๋ฅด๋Š”๊ฐ€?
  • NOT NULL: ํ•„์ˆ˜ ์ปฌ๋Ÿผ์— NOT NULL ์ œ์•ฝ์ด ์žˆ๋Š”๊ฐ€?
  • DEFAULT: ์ ์ ˆํ•œ ๊ธฐ๋ณธ๊ฐ’์ด ์„ค์ •๋˜์—ˆ๋Š”๊ฐ€?

Reference Files

Ecoยฒ Project Notes

  • ๋ธ”๋กœ๊ทธ ์ฐธ์กฐ: https://rooftopsnow.tistory.com/132
  • ๊ธฐ์กด ์Šคํ‚ค๋งˆ: migrations/schemas/users_schema.sql
  • ORM: SQLAlchemy 2.0 with asyncpg

Score

Total Score

60/100

Based on repository quality metrics

โœ“SKILL.md

SKILL.mdใƒ•ใ‚กใ‚คใƒซใŒๅซใพใ‚Œใฆใ„ใ‚‹

+20
โœ“LICENSE

ใƒฉใ‚คใ‚ปใƒณใ‚นใŒ่จญๅฎšใ•ใ‚Œใฆใ„ใ‚‹

+10
โ—‹่ชฌๆ˜Žๆ–‡

100ๆ–‡ๅญ—ไปฅไธŠใฎ่ชฌๆ˜ŽใŒใ‚ใ‚‹

0/10
โ—‹ไบบๆฐ—

GitHub Stars 100ไปฅไธŠ

0/15
โ—‹ๆœ€่ฟ‘ใฎๆดปๅ‹•

3ใƒถๆœˆไปฅๅ†…ใซๆ›ดๆ–ฐใŒใ‚ใ‚‹

0/10
โ—‹ใƒ•ใ‚ฉใƒผใ‚ฏ

10ๅ›žไปฅไธŠใƒ•ใ‚ฉใƒผใ‚ฏใ•ใ‚Œใฆใ„ใ‚‹

0/5
โœ“Issue็ฎก็†

ใ‚ชใƒผใƒ—ใƒณIssueใŒ50ๆœชๆบ€

+5
โœ“่จ€่ชž

ใƒ—ใƒญใ‚ฐใƒฉใƒŸใƒณใ‚ฐ่จ€่ชžใŒ่จญๅฎšใ•ใ‚Œใฆใ„ใ‚‹

+5
โ—‹ใ‚ฟใ‚ฐ

1ใคไปฅไธŠใฎใ‚ฟใ‚ฐใŒ่จญๅฎšใ•ใ‚Œใฆใ„ใ‚‹

0/5

Reviews

๐Ÿ’ฌ

Reviews coming soon