KR_PostgreSQL - somaz94/python-study GitHub Wiki

Python PostgreSQL ๊ฐœ๋… ์ •๋ฆฌ


1๏ธโƒฃ PostgreSQL ๊ธฐ์ดˆ

PostgreSQL์€ ๊ฐ•๋ ฅํ•œ ์˜คํ”ˆ์†Œ์Šค ๊ฐ์ฒด-๊ด€๊ณ„ํ˜• ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์‹œ์Šคํ…œ์ด๋‹ค.

import psycopg2
from psycopg2.extras import DictCursor

# ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ
def get_connection():
    return psycopg2.connect(
        dbname="mydatabase",
        user="username",
        password="password",
        host="localhost",
        port="5432"
    )

# ๊ธฐ๋ณธ ์—ฐ๊ฒฐ ์‚ฌ์šฉ
conn = get_connection()
cur = conn.cursor(cursor_factory=DictCursor)

# ์—ฐ๊ฒฐ ์ข…๋ฃŒ ์ฒ˜๋ฆฌ
def close_connection(conn, cur):
    if cur is not None:
        cur.close()
    if conn is not None:
        conn.close()
    print("Database connection closed.")

# ์—๋Ÿฌ ์ฒ˜๋ฆฌ ์˜ˆ์‹œ
try:
    conn = get_connection()
    cur = conn.cursor()
    # ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ž‘์—… ์ˆ˜ํ–‰
except psycopg2.DatabaseError as e:
    print(f"Database error: {e}")
finally:
    close_connection(conn, cur)

โœ… ํŠน์ง•:

  • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ์„ค์ • ๋ฐ ๊ด€๋ฆฌ
  • ๋‹ค์–‘ํ•œ ์ปค์„œ ์˜ต์…˜ ์ง€์› (๊ธฐ๋ณธ, ๋”•์…”๋„ˆ๋ฆฌ, ๋ช…๋ช…๋œ ์ปค์„œ)
  • ๊ฐ•๋ ฅํ•œ ์—๋Ÿฌ ์ฒ˜๋ฆฌ ๋ฉ”์ปค๋‹ˆ์ฆ˜
  • ์ž๋™ ๋ฐ ์ˆ˜๋™ ํŠธ๋žœ์žญ์…˜ ๊ด€๋ฆฌ
  • ๋น„๋™๊ธฐ ์ž‘์—… ์ง€์›
  • ๋‹ค์–‘ํ•œ ๋ฐ์ดํ„ฐ ํƒ€์ž… ์ง€์›
  • ์—ฐ๊ฒฐ ํ’€๋ง ํ†ตํ•ฉ
  • SSL ์ง€์› ๋ณด์•ˆ ์—ฐ๊ฒฐ


2๏ธโƒฃ ๊ธฐ๋ณธ CRUD ์ž‘์—…

PostgreSQL์—์„œ์˜ ๊ธฐ๋ณธ์ ์ธ ๋ฐ์ดํ„ฐ ์ƒ์„ฑ, ์กฐํšŒ, ์ˆ˜์ •, ์‚ญ์ œ ์ž‘์—…์ด๋‹ค.

# ํ…Œ์ด๋ธ” ์ƒ์„ฑ
def create_tables():
    commands = [
        """
        CREATE TABLE IF NOT EXISTS users (
            id SERIAL PRIMARY KEY,
            name VARCHAR(100) NOT NULL,
            email VARCHAR(100) UNIQUE,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
        """,
        """
        CREATE TABLE IF NOT EXISTS posts (
            id SERIAL PRIMARY KEY,
            user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
            title VARCHAR(200) NOT NULL,
            content TEXT,
            published BOOLEAN DEFAULT FALSE,
            views INTEGER DEFAULT 0,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
        """
    ]
    
    try:
        for command in commands:
            cur.execute(command)
        conn.commit()
        print("Tables created successfully")
    except (Exception, psycopg2.DatabaseError) as error:
        print(f"Error: {error}")
        conn.rollback()

# ๋ฐ์ดํ„ฐ ์‚ฝ์ž… (Create)
def insert_user(name, email):
    sql = """
        INSERT INTO users (name, email)
        VALUES (%s, %s)
        RETURNING id, name, email, created_at
    """
    try:
        cur.execute(sql, (name, email))
        user = cur.fetchone()
        conn.commit()
        return dict(user) if isinstance(cur, DictCursor) else user
    except psycopg2.IntegrityError:
        conn.rollback()
        raise ValueError("์ค‘๋ณต๋œ ์ด๋ฉ”์ผ์ด๋‹ค.")

# ๋ฐ์ดํ„ฐ ์กฐํšŒ (Read)
def get_user(user_id):
    cur.execute("""
        SELECT id, name, email, created_at
        FROM users
        WHERE id = %s
    """, (user_id,))
    return cur.fetchone()

def get_all_users(limit=100, offset=0):
    cur.execute("""
        SELECT id, name, email, created_at
        FROM users
        ORDER BY created_at DESC
        LIMIT %s OFFSET %s
    """, (limit, offset))
    return cur.fetchall()

# ๋ฐ์ดํ„ฐ ์ˆ˜์ • (Update)
def update_user(user_id, name=None, email=None):
    updates = []
    params = []
    
    if name:
        updates.append("name = %s")
        params.append(name)
    
    if email:
        updates.append("email = %s")
        params.append(email)
    
    if not updates:
        return None
    
    params.append(user_id)
    
    sql = f"""
        UPDATE users
        SET {", ".join(updates)}
        WHERE id = %s
        RETURNING id, name, email, created_at
    """
    
    cur.execute(sql, params)
    conn.commit()
    return cur.fetchone()

# ๋ฐ์ดํ„ฐ ์‚ญ์ œ (Delete)
def delete_user(user_id):
    cur.execute("DELETE FROM users WHERE id = %s RETURNING id", (user_id,))
    deleted_id = cur.fetchone()
    conn.commit()
    return deleted_id is not None

โœ… ํŠน์ง•:

  • ํ…Œ์ด๋ธ” ์ƒ์„ฑ ๋ฐ ์Šคํ‚ค๋งˆ ๊ด€๋ฆฌ
  • ํŒŒ๋ผ๋ฏธํ„ฐํ™”๋œ ์ฟผ๋ฆฌ๋ฅผ ํ†ตํ•œ SQL ์ธ์ ์…˜ ๋ฐฉ์ง€
  • ๋ฐ์ดํ„ฐ ์‚ฝ์ž… ๋ฐ ๋ฐ˜ํ™˜ ๊ฐ’ ์ฒ˜๋ฆฌ
  • ๋‹ค์–‘ํ•œ ์กฐํšŒ ์˜ต์…˜ (๋‹จ์ผ, ๋‹ค์ค‘, ํŽ˜์ด์ง€๋„ค์ด์…˜)
  • ๋™์  ์—…๋ฐ์ดํŠธ ์ฟผ๋ฆฌ ๊ตฌ์„ฑ
  • ์•ˆ์ „ํ•œ ์‚ญ์ œ ์ž‘์—… ๋ฐ ๊ฒฐ๊ณผ ํ™•์ธ
  • ์™ธ๋ž˜ ํ‚ค ๋ฐ ์ฐธ์กฐ ๋ฌด๊ฒฐ์„ฑ ๊ด€๋ฆฌ
  • ํŠธ๋žœ์žญ์…˜ ํ™œ์šฉ ๋ฐ์ดํ„ฐ ์ผ๊ด€์„ฑ ์œ ์ง€


3๏ธโƒฃ ๊ณ ๊ธ‰ ๊ธฐ๋Šฅ

PostgreSQL๋งŒ์˜ ๊ณ ๊ธ‰ ๊ธฐ๋Šฅ์„ ํ™œ์šฉํ•˜์—ฌ ๋ณต์žกํ•œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•œ๋‹ค.

import json
from psycopg2.extras import Json, RealDictCursor

# JSON ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ
def create_profile(user_id, profile_data):
    """์‚ฌ์šฉ์ž ํ”„๋กœํ•„์„ JSON ํ˜•ํƒœ๋กœ ์ €์žฅํ•œ๋‹ค."""
    sql = """
        ALTER TABLE users 
        ADD COLUMN IF NOT EXISTS profile JSONB;
        
        UPDATE users
        SET profile = %s::jsonb
        WHERE id = %s
        RETURNING id, profile
    """
    try:
        # Json ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ Python ๊ฐ์ฒด๋ฅผ PostgreSQL JSONB๋กœ ๋ณ€ํ™˜
        cur.execute(sql, (Json(profile_data), user_id))
        result = cur.fetchone()
        conn.commit()
        return result
    except Exception as e:
        conn.rollback()
        raise e

# JSON ํ•„๋“œ ์ฟผ๋ฆฌ
def find_users_by_skill(skill):
    """ํŠน์ • ๊ธฐ์ˆ ์„ ๊ฐ€์ง„ ์‚ฌ์šฉ์ž๋ฅผ ์ฐพ๋Š”๋‹ค."""
    sql = """
        SELECT id, name, email, profile
        FROM users
        WHERE profile @> %s
    """
    cur.execute(sql, (json.dumps({"skills": [skill]}),))
    return cur.fetchall()

# JSON ํ•„๋“œ ์—…๋ฐ์ดํŠธ
def add_user_skill(user_id, new_skill):
    """์‚ฌ์šฉ์ž ํ”„๋กœํ•„์— ์ƒˆ๋กœ์šด ๊ธฐ์ˆ ์„ ์ถ”๊ฐ€ํ•œ๋‹ค."""
    sql = """
        UPDATE users
        SET profile = jsonb_set(
            COALESCE(profile, '{}'::jsonb),
            '{skills}',
            COALESCE(profile->'skills', '[]'::jsonb) || %s::jsonb,
            true
        )
        WHERE id = %s
        RETURNING id, profile
    """
    cur.execute(sql, (Json([new_skill]), user_id))
    conn.commit()
    return cur.fetchone()

# ์ „์ฒด ํ…์ŠคํŠธ ๊ฒ€์ƒ‰ ์ธ๋ฑ์Šค ์ƒ์„ฑ
def setup_text_search():
    """๊ฒŒ์‹œ๋ฌผ ๊ฒ€์ƒ‰์„ ์œ„ํ•œ ์ธ๋ฑ์Šค๋ฅผ ์„ค์ •ํ•œ๋‹ค."""
    commands = [
        """
        ALTER TABLE posts 
        ADD COLUMN IF NOT EXISTS search_vector tsvector;
        """,
        """
        CREATE INDEX IF NOT EXISTS posts_search_idx 
        ON posts USING GIN(search_vector);
        """,
        """
        CREATE OR REPLACE FUNCTION posts_search_update() RETURNS trigger AS $$
        BEGIN
            NEW.search_vector := 
                setweight(to_tsvector('korean', COALESCE(NEW.title, '')), 'A') ||
                setweight(to_tsvector('korean', COALESCE(NEW.content, '')), 'B');
            RETURN NEW;
        END
        $$ LANGUAGE plpgsql;
        """,
        """
        DROP TRIGGER IF EXISTS posts_search_update ON posts;
        """,
        """
        CREATE TRIGGER posts_search_update
        BEFORE INSERT OR UPDATE ON posts
        FOR EACH ROW EXECUTE PROCEDURE posts_search_update();
        """
    ]
    
    try:
        for command in commands:
            cur.execute(command)
        conn.commit()
        print("Text search setup complete")
    except Exception as e:
        conn.rollback()
        print(f"Error setting up text search: {e}")

# ์ „์ฒด ํ…์ŠคํŠธ ๊ฒ€์ƒ‰ ์‹คํ–‰
def search_posts(query, limit=10):
    """๊ฒŒ์‹œ๋ฌผ ๋‚ด์šฉ์„ ๊ฒ€์ƒ‰ํ•œ๋‹ค."""
    sql = """
        SELECT id, title, content, ts_rank(search_vector, websearch_to_tsquery('korean', %s)) as rank
        FROM posts
        WHERE search_vector @@ websearch_to_tsquery('korean', %s)
        ORDER BY rank DESC
        LIMIT %s
    """
    cur.execute(sql, (query, query, limit))
    return cur.fetchall()

# ๋ฐฐ์—ด ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ
def add_tags(post_id, tags):
    """๊ฒŒ์‹œ๋ฌผ์— ํƒœ๊ทธ ๋ฐฐ์—ด์„ ์ถ”๊ฐ€ํ•œ๋‹ค."""
    # ๋จผ์ € ๋ฐฐ์—ด ์ปฌ๋Ÿผ ์ถ”๊ฐ€ ํ™•์ธ
    cur.execute("""
        ALTER TABLE posts 
        ADD COLUMN IF NOT EXISTS tags TEXT[] DEFAULT '{}'::TEXT[];
    """)
    
    # ํƒœ๊ทธ ์ถ”๊ฐ€ ์ž‘์—…
    sql = """
        UPDATE posts
        SET tags = %s
        WHERE id = %s
        RETURNING id, title, tags
    """
    cur.execute(sql, (tags, post_id))
    conn.commit()
    return cur.fetchone()

# ๋ฐฐ์—ด ๋ฐ์ดํ„ฐ ๊ฒ€์ƒ‰
def find_posts_by_tag(tag):
    """ํŠน์ • ํƒœ๊ทธ๊ฐ€ ํฌํ•จ๋œ ๊ฒŒ์‹œ๋ฌผ์„ ์ฐพ๋Š”๋‹ค."""
    sql = """
        SELECT id, title, content, tags
        FROM posts
        WHERE %s = ANY(tags)
    """
    cur.execute(sql, (tag,))
    return cur.fetchall()

โœ… ํŠน์ง•:

  • ๊ณ ๊ธ‰ JSON/JSONB ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ๋ฐ ์ฟผ๋ฆฌ
  • ๊ฐ•๋ ฅํ•œ ์ „์ฒด ํ…์ŠคํŠธ ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ ๊ตฌํ˜„
  • ํŠธ๋ฆฌ๊ฑฐ์™€ ํ•จ์ˆ˜๋ฅผ ํ™œ์šฉํ•œ ์ž๋™ ์ธ๋ฑ์‹ฑ
  • ๋ฐฐ์—ด ํƒ€์ž… ๋ฐ์ดํ„ฐ ์ €์žฅ ๋ฐ ๊ฒ€์ƒ‰
  • ๋ณต์žกํ•œ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ ์กฐ์ž‘
  • ๋‹ค๊ตญ์–ด ํ…์ŠคํŠธ ๊ฒ€์ƒ‰ ์ง€์›
  • ์Šคํ‚ค๋งˆ ๋™์  ๋ณ€๊ฒฝ ๋ฐ ๊ด€๋ฆฌ
  • ์ตœ์ ํ™”๋œ ๊ฒ€์ƒ‰ ์•Œ๊ณ ๋ฆฌ์ฆ˜ ํ™œ์šฉ


4๏ธโƒฃ ํŠธ๋žœ์žญ์…˜๊ณผ ๋™์‹œ์„ฑ

PostgreSQL์˜ ํŠธ๋žœ์žญ์…˜๊ณผ ๋™์‹œ์„ฑ ์ œ์–ด๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ ์ผ๊ด€์„ฑ๊ณผ ์„ฑ๋Šฅ์„ ๊ด€๋ฆฌํ•œ๋‹ค.

# ๊ธฐ๋ณธ ํŠธ๋žœ์žญ์…˜ ๊ด€๋ฆฌ
def basic_transaction_example():
    """๊ธฐ๋ณธ์ ์ธ ํŠธ๋žœ์žญ์…˜ ๊ด€๋ฆฌ ์˜ˆ์‹œ"""
    conn = get_connection()
    try:
        # ์ž๋™ ์ปค๋ฐ‹ ๋น„ํ™œ์„ฑํ™”
        conn.autocommit = False
        cur = conn.cursor()
        
        # ํŠธ๋žœ์žญ์…˜ ๋‚ด ์ž‘์—…
        cur.execute("INSERT INTO users (name, email) VALUES (%s, %s)", 
                    ("์‚ฌ์šฉ์ž1", "[email protected]"))
        cur.execute("INSERT INTO users (name, email) VALUES (%s, %s)", 
                    ("์‚ฌ์šฉ์ž2", "[email protected]"))
        
        # ํŠธ๋žœ์žญ์…˜ ์™„๋ฃŒ
        conn.commit()
        print("Transaction committed successfully")
    except Exception as e:
        # ์˜ค๋ฅ˜ ๋ฐœ์ƒ ์‹œ ๋กค๋ฐฑ
        conn.rollback()
        print(f"Transaction rolled back: {e}")
    finally:
        cur.close()
        conn.close()

# ๋™์‹œ์„ฑ ์ œ์–ด๋ฅผ ์œ„ํ•œ ํ–‰ ์ž ๊ธˆ
def transfer_money(from_account, to_account, amount):
    """๊ณ„์ขŒ ๊ฐ„ ์†ก๊ธˆ ์ž‘์—…์„ ํŠธ๋žœ์žญ์…˜์œผ๋กœ ์ฒ˜๋ฆฌํ•œ๋‹ค."""
    conn = get_connection()
    try:
        conn.autocommit = False
        cur = conn.cursor()
        
        # ํŠธ๋žœ์žญ์…˜ ๊ฒฉ๋ฆฌ ์ˆ˜์ค€ ์„ค์ •
        cur.execute("SET TRANSACTION ISOLATION LEVEL REPEATABLE READ")
        
        # FOR UPDATE๋ฅผ ์‚ฌ์šฉํ•œ ํ–‰ ์ž ๊ธˆ์œผ๋กœ ๋™์‹œ ์ ‘๊ทผ ์ œ์–ด
        cur.execute("""
            SELECT id, balance FROM accounts
            WHERE id = %s FOR UPDATE
        """, (from_account,))
        
        from_acc = cur.fetchone()
        if not from_acc:
            raise ValueError(f"๊ณ„์ขŒ {from_account}๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š๋Š”๋‹ค.")
        
        from_balance = from_acc[1]
        
        if from_balance < amount:
            raise ValueError(f"์ž”์•ก ๋ถ€์กฑ: ํ•„์š” {amount}, ๋ณด์œ  {from_balance}")
        
        # ์ˆ˜์ทจ ๊ณ„์ขŒ ํ™•์ธ ๋ฐ ์ž ๊ธˆ
        cur.execute("""
            SELECT id, balance FROM accounts
            WHERE id = %s FOR UPDATE
        """, (to_account,))
        
        to_acc = cur.fetchone()
        if not to_acc:
            raise ValueError(f"๊ณ„์ขŒ {to_account}๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š๋Š”๋‹ค.")
        
        # ์ถœ๊ธˆ ์ฒ˜๋ฆฌ
        cur.execute("""
            UPDATE accounts
            SET balance = balance - %s,
                updated_at = CURRENT_TIMESTAMP
            WHERE id = %s
        """, (amount, from_account))
        
        # ์ž…๊ธˆ ์ฒ˜๋ฆฌ
        cur.execute("""
            UPDATE accounts
            SET balance = balance + %s,
                updated_at = CURRENT_TIMESTAMP
            WHERE id = %s
        """, (amount, to_account))
        
        # ๊ฑฐ๋ž˜ ๊ธฐ๋ก ์ €์žฅ
        cur.execute("""
            INSERT INTO transactions 
            (from_account, to_account, amount, transaction_date, status)
            VALUES (%s, %s, %s, CURRENT_TIMESTAMP, 'completed')
            RETURNING id
        """, (from_account, to_account, amount))
        
        transaction_id = cur.fetchone()[0]
        
        # ๋ชจ๋“  ์ž‘์—…์ด ์„ฑ๊ณตํ•˜๋ฉด ์ปค๋ฐ‹
        conn.commit()
        return {
            "transaction_id": transaction_id,
            "from_account": from_account,
            "to_account": to_account,
            "amount": amount,
            "status": "completed"
        }
        
    except Exception as e:
        # ์˜ค๋ฅ˜ ๋ฐœ์ƒ ์‹œ ๋กค๋ฐฑ
        conn.rollback()
        
        # ์‹คํŒจํ•œ ๊ฑฐ๋ž˜ ๊ธฐ๋ก
        try:
            cur.execute("""
                INSERT INTO transactions 
                (from_account, to_account, amount, transaction_date, status, error_message)
                VALUES (%s, %s, %s, CURRENT_TIMESTAMP, 'failed', %s)
            """, (from_account, to_account, amount, str(e)))
            conn.commit()
        except:
            pass
            
        raise e
    finally:
        cur.close()
        conn.close()

# ๋ฐ๋“œ๋ฝ ๋ฐฉ์ง€ ์ „๋žต
def deadlock_prevention_example():
    """๋ฐ๋“œ๋ฝ์„ ๋ฐฉ์ง€ํ•˜๋Š” ํŠธ๋žœ์žญ์…˜ ํŒจํ„ด"""
    conn = get_connection()
    try:
        conn.autocommit = False
        cur = conn.cursor()
        
        # ํŠธ๋žœ์žญ์…˜ ํƒ€์ž„์•„์›ƒ ์„ค์ •
        cur.execute("SET statement_timeout = '5s'")
        
        # ํ•ญ์ƒ ID ์˜ค๋ฆ„์ฐจ์ˆœ์œผ๋กœ ์ž ๊ธˆ์„ ํš๋“ํ•˜์—ฌ ๋ฐ๋“œ๋ฝ ๋ฐฉ์ง€
        account_ids = sorted([account1_id, account2_id])
        
        # ์ฒซ ๋ฒˆ์งธ ๊ณ„์ขŒ ์ž ๊ธˆ
        cur.execute("""
            SELECT id, balance FROM accounts
            WHERE id = %s FOR UPDATE
        """, (account_ids[0],))
        
        # ๋‘ ๋ฒˆ์งธ ๊ณ„์ขŒ ์ž ๊ธˆ
        cur.execute("""
            SELECT id, balance FROM accounts
            WHERE id = %s FOR UPDATE
        """, (account_ids[1],))
        
        # ์ดํ›„ ์ž‘์—… ์ˆ˜ํ–‰...
        
        conn.commit()
    except Exception as e:
        conn.rollback()
        raise e
    finally:
        cur.close()
        conn.close()

# ๋‚™๊ด€์  ๋™์‹œ์„ฑ ์ œ์–ด
def optimistic_concurrency_control(user_id, new_data):
    """๋‚™๊ด€์  ๋™์‹œ์„ฑ ์ œ์–ด ํŒจํ„ด ๊ตฌํ˜„"""
    conn = get_connection()
    try:
        conn.autocommit = False
        cur = conn.cursor()
        
        # ๋ฒ„์ „ ํ™•์ธ
        cur.execute("""
            SELECT id, data, version FROM user_data
            WHERE id = %s
        """, (user_id,))
        
        result = cur.fetchone()
        if not result:
            raise ValueError("์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š๋Š”๋‹ค.")
        
        current_version = result[2]
        
        # ๋ฒ„์ „์„ ํ™•์ธํ•˜๋ฉฐ ์—…๋ฐ์ดํŠธ
        updated_rows = cur.execute("""
            UPDATE user_data
            SET data = %s, version = version + 1, updated_at = CURRENT_TIMESTAMP
            WHERE id = %s AND version = %s
        """, (Json(new_data), user_id, current_version))
        
        if cur.rowcount == 0:
            # ๋‹ค๋ฅธ ํŠธ๋žœ์žญ์…˜์ด ์ด๋ฏธ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณ€๊ฒฝํ•œ ๊ฒฝ์šฐ
            conn.rollback()
            return {"status": "conflict", "message": "Data was modified by another process"}
        
        conn.commit()
        return {"status": "success", "new_version": current_version + 1}
    except Exception as e:
        conn.rollback()
        raise e
    finally:
        cur.close()
        conn.close()

โœ… ํŠน์ง•:

  • ACID ์†์„ฑ์„ ๋ณด์žฅํ•˜๋Š” ํŠธ๋žœ์žญ์…˜ ๊ด€๋ฆฌ
  • ๋‹ค์–‘ํ•œ ํŠธ๋žœ์žญ์…˜ ๊ฒฉ๋ฆฌ ์ˆ˜์ค€ ํ™œ์šฉ
  • ํ–‰ ๋‹จ์œ„ ์ž ๊ธˆ์„ ํ†ตํ•œ ๋™์‹œ์„ฑ ์ œ์–ด
  • ๋ฐ๋“œ๋ฝ ๋ฐฉ์ง€ ์ „๋žต ๊ตฌํ˜„
  • ๋‚™๊ด€์ /๋น„๊ด€์  ๋™์‹œ์„ฑ ์ œ์–ด ํŒจํ„ด
  • ํŠธ๋žœ์žญ์…˜ ํƒ€์ž„์•„์›ƒ ์„ค์ •
  • ์˜ค๋ฅ˜ ๋ฐœ์ƒ ์‹œ ๋กค๋ฐฑ ๋ฉ”์ปค๋‹ˆ์ฆ˜
  • ํŠธ๋žœ์žญ์…˜ ๋กœ๊น… ๋ฐ ๋ชจ๋‹ˆํ„ฐ๋ง


5๏ธโƒฃ ์„ฑ๋Šฅ ์ตœ์ ํ™”

PostgreSQL ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์˜ ์„ฑ๋Šฅ์„ ์ตœ์ ํ™”ํ•˜๋Š” ๊ธฐ๋ฒ•์ด๋‹ค.

from psycopg2.extras import execute_values
import time
import statistics

# ๋Œ€๋Ÿ‰ ์‚ฝ์ž… ์ตœ์ ํ™”
def bulk_insert_users(users_data):
    """๋Œ€๋Ÿ‰์˜ ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ๋ฅผ ํšจ์œจ์ ์œผ๋กœ ์‚ฝ์ž…ํ•œ๋‹ค."""
    conn = get_connection()
    try:
        cur = conn.cursor()
        
        # execute_values ์‚ฌ์šฉํ•˜์—ฌ ๋Œ€๋Ÿ‰ ์‚ฝ์ž…
        insert_query = """
            INSERT INTO users (name, email)
            VALUES %s
            RETURNING id
        """
        
        # ๋ฐ์ดํ„ฐ ๋ณ€ํ™˜
        values = [(user['name'], user['email']) for user in users_data]
        
        # ๋Œ€๋Ÿ‰ ์‚ฝ์ž… ์‹คํ–‰
        result = execute_values(cur, insert_query, values, fetch=True)
        
        conn.commit()
        return [row[0] for row in result]  # ์‚ฝ์ž…๋œ ID ๋ชฉ๋ก ๋ฐ˜ํ™˜
    except Exception as e:
        conn.rollback()
        raise e
    finally:
        cur.close()
        conn.close()

# ์ฟผ๋ฆฌ ์„ฑ๋Šฅ ์ธก์ •
def measure_query_performance(query_func, *args, iterations=5):
    """์ฟผ๋ฆฌ ์„ฑ๋Šฅ์„ ์ธก์ •ํ•œ๋‹ค."""
    execution_times = []
    
    for i in range(iterations):
        start_time = time.time()
        query_func(*args)
        end_time = time.time()
        execution_times.append(end_time - start_time)
    
    avg_time = statistics.mean(execution_times)
    min_time = min(execution_times)
    max_time = max(execution_times)
    
    return {
        "average": avg_time,
        "min": min_time,
        "max": max_time,
        "total_iterations": iterations
    }

# ์ปค๋„ฅ์…˜ ํ’€๋ง ๊ตฌํ˜„
from contextlib import contextmanager
from psycopg2.pool import ThreadedConnectionPool

# ์‹ฑ๊ธ€ํ†ค ์ปค๋„ฅ์…˜ ํ’€
class ConnectionPool:
    _instance = None
    _pool = None
    
    @classmethod
    def get_instance(cls, min_conn=1, max_conn=10, **kwargs):
        if cls._instance is None:
            cls._instance = cls(min_conn, max_conn, **kwargs)
        return cls._instance
    
    def __init__(self, min_conn, max_conn, **kwargs):
        self._pool = ThreadedConnectionPool(
            min_conn,
            max_conn,
            dbname="mydatabase",
            user="username",
            password="password",
            host="localhost",
            port="5432",
            **kwargs
        )
    
    def get_connection(self):
        return self._pool.getconn()
    
    def release_connection(self, conn):
        self._pool.putconn(conn)
    
    def close_all(self):
        self._pool.closeall()

@contextmanager
def get_db_connection():
    """๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ์„ ์ปจํ…์ŠคํŠธ ๋งค๋‹ˆ์ €๋กœ ์ œ๊ณตํ•œ๋‹ค."""
    pool = ConnectionPool.get_instance()
    conn = pool.get_connection()
    try:
        yield conn
    finally:
        pool.release_connection(conn)

@contextmanager
def get_db_cursor(commit=True):
    """๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ปค์„œ๋ฅผ ์ปจํ…์ŠคํŠธ ๋งค๋‹ˆ์ €๋กœ ์ œ๊ณตํ•œ๋‹ค."""
    with get_db_connection() as conn:
        cur = conn.cursor()
        try:
            yield cur
            if commit:
                conn.commit()
        except:
            conn.rollback()
            raise
        finally:
            cur.close()

# ์ธ๋ฑ์Šค ์ƒ์„ฑ ๋ฐ ๊ด€๋ฆฌ
def optimize_database_indexes():
    """๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ธ๋ฑ์Šค๋ฅผ ์ตœ์ ํ™”ํ•œ๋‹ค."""
    with get_db_cursor() as cur:
        # ์ž์ฃผ ๊ฒ€์ƒ‰๋˜๋Š” ํ•„๋“œ์— ์ธ๋ฑ์Šค ์ƒ์„ฑ
        cur.execute("""
            CREATE INDEX IF NOT EXISTS users_email_idx ON users(email);
            CREATE INDEX IF NOT EXISTS posts_user_id_idx ON posts(user_id);
            CREATE INDEX IF NOT EXISTS posts_created_at_idx ON posts(created_at);
            CREATE INDEX IF NOT EXISTS posts_title_idx ON posts USING gin(to_tsvector('korean', title));
        """)
        
        # ๋ณตํ•ฉ ์ธ๋ฑ์Šค ์ƒ์„ฑ
        cur.execute("""
            CREATE INDEX IF NOT EXISTS posts_user_published_idx 
            ON posts(user_id, published);
        """)

โœ… ํŠน์ง•:

  • ๋Œ€๋Ÿ‰ ๋ฐ์ดํ„ฐ ์ž‘์—… ์ตœ์ ํ™”
  • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ํ’€๋ง ๊ตฌํ˜„
  • ์ธ๋ฑ์Šค ์ „๋žต ์ˆ˜๋ฆฝ ๋ฐ ๊ด€๋ฆฌ
  • ์ฟผ๋ฆฌ ์„ฑ๋Šฅ ์ธก์ • ๋ฐ ๋ถ„์„
  • ์ปจํ…์ŠคํŠธ ๋งค๋‹ˆ์ €๋ฅผ ํ™œ์šฉํ•œ ๋ฆฌ์†Œ์Šค ๊ด€๋ฆฌ
  • ํšจ์œจ์ ์ธ ํŠธ๋žœ์žญ์…˜ ์‚ฌ์šฉ
  • ๋น„๋™๊ธฐ ์ž‘์—… ์ฒ˜๋ฆฌ ๋ฐฉ๋ฒ•
  • ์„ฑ๋Šฅ ๋ฌธ์ œ ์ง„๋‹จ ๋ฐ ํ•ด๊ฒฐ ์ „๋žต


์ฃผ์š” ํŒ

โœ… ๋ชจ๋ฒ” ์‚ฌ๋ก€:

  • ์ปค๋„ฅ์…˜ ํ’€๋ง ์‚ฌ์šฉ
    • ์—ฐ๊ฒฐ ์ƒ์„ฑ๊ณผ ํ•ด์ œ ๋น„์šฉ ๊ฐ์†Œ
    • ๋™์‹œ ์—ฐ๊ฒฐ ์ˆ˜ ์ œํ•œ ๋ฐ ๊ด€๋ฆฌ
    • Thread-safe ์—ฐ๊ฒฐ ์ฒ˜๋ฆฌ
    • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„ฑ๋Šฅ ํ–ฅ์ƒ
  • ํŒŒ๋ผ๋ฏธํ„ฐํ™”๋œ ์ฟผ๋ฆฌ ์‚ฌ์šฉ
    • SQL ์ธ์ ์…˜ ๋ฐฉ์ง€
    • ์ฟผ๋ฆฌ ์ตœ์ ํ™” ๋ฐ ์žฌ์‚ฌ์šฉ
    • ์„œ๋ฒ„ ์ธก ์ค€๋น„๋œ ๊ตฌ๋ฌธ ํ™œ์šฉ
    • ๋ฐ์ดํ„ฐ ํƒ€์ž… ์•ˆ์ „์„ฑ ํ™•๋ณด
  • ํŠธ๋žœ์žญ์…˜ ์ ์ ˆํžˆ ํ™œ์šฉ
    • ๋ฐ์ดํ„ฐ ์ผ๊ด€์„ฑ ๋ณด์žฅ
    • ์ž‘์—… ๋‹จ์œ„ ๊ด€๋ฆฌ
    • ์˜ค๋ฅ˜ ๋ณต๊ตฌ ์šฉ์ด
    • ๋™์‹œ์„ฑ ๋ฌธ์ œ ํ•ด๊ฒฐ
  • ์ธ๋ฑ์Šค ์ตœ์ ํ™”
    • ์ž์ฃผ ์กฐํšŒ๋˜๋Š” ํ•„๋“œ ์ธ๋ฑ์‹ฑ
    • ๋ณตํ•ฉ ์ธ๋ฑ์Šค ์ „๋žต ์ˆ˜๋ฆฝ
    • ๋ถˆํ•„์š”ํ•œ ์ธ๋ฑ์Šค ์ œ๊ฑฐ
    • ์ฟผ๋ฆฌ ์‹คํ–‰ ๊ณ„ํš ๋ถ„์„
  • ์ฃผ๊ธฐ์ ์ธ ๋ฐฑ์—…
    • pg_dump ํ™œ์šฉ ์ž๋™ํ™”
    • ์ฆ๋ถ„ ๋ฐฑ์—… ์ „๋žต ๊ตฌํ˜„
    • ๋ณต๊ตฌ ํ”„๋กœ์„ธ์Šค ํ…Œ์ŠคํŠธ
    • ๋ฐฑ์—… ๋ฌด๊ฒฐ์„ฑ ๊ฒ€์ฆ
  • ๋ณด์•ˆ ์„ค์ • ํ™•์ธ
    • ์ตœ์†Œ ๊ถŒํ•œ ์›์น™ ์ ์šฉ
    • ์ ‘๊ทผ ์ œ์–ด ์„ค์ •
    • SSL ์—ฐ๊ฒฐ ์„ค์ •
    • ๋น„๋ฐ€๋ฒˆํ˜ธ ์ •์ฑ… ๊ตฌํ˜„
  • ์„ฑ๋Šฅ ๋ชจ๋‹ˆํ„ฐ๋ง
    • ์Šฌ๋กœ์šฐ ์ฟผ๋ฆฌ ๋กœ๊น…
    • ๋ฆฌ์†Œ์Šค ์‚ฌ์šฉ๋Ÿ‰ ๋ถ„์„
    • ๋ณ‘๋ชฉ ํ˜„์ƒ ์‹๋ณ„
    • EXPLAIN ANALYZE ํ™œ์šฉ
  • ์—๋Ÿฌ ์ฒ˜๋ฆฌ ๊ตฌํ˜„
    • ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ํŒจํ„ด ์ ์šฉ
    • ์„ธ๋ถ„ํ™”๋œ ์˜ค๋ฅ˜ ์ฝ”๋“œ ํ™œ์šฉ
    • ์—ฐ๊ฒฐ ์˜ค๋ฅ˜ ๊ด€๋ฆฌ
    • ์žฌ์‹œ๋„ ๋กœ์ง ๊ตฌํ˜„
  • ๋Œ€๋Ÿ‰ ์ž‘์—… ์ตœ์ ํ™”
    • execute_values ์‚ฌ์šฉ
    • ๋ฐฐ์น˜ ์ฒ˜๋ฆฌ ๊ตฌํ˜„
    • COPY ๋ช…๋ น ํ™œ์šฉ
    • ์ž„์‹œ ํ…Œ์ด๋ธ” ํ™œ์šฉ
  • ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ ํ™œ์šฉ
    • ๋ณ‘๋ ฌ ์ฟผ๋ฆฌ ์‹คํ–‰
    • ๋น„๋™๊ธฐ ์ปค๋„ฅ์…˜ ํ’€
    • ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…์ž
    • ์ด๋ฒคํŠธ ๋“œ๋ฆฌ๋ธ ์•„ํ‚คํ…์ฒ˜


โš ๏ธ **GitHub.com Fallback** โš ๏ธ