포스트

[CS 기초 #16] 데이터베이스 이론: RDBMS부터 NoSQL까지 완벽 정리

[CS 기초 #16] 데이터베이스 이론: RDBMS부터 NoSQL까지 완벽 정리

데이터를 지배하는 자! RDBMS부터 NoSQL까지, 데이터베이스의 모든 것을 배웁니다.

🎯 이 글을 읽고 나면

  • RDBMS와 NoSQL의 차이를 명확히 설명할 수 있습니다
  • 정규화의 필요성과 1NF~3NF를 이해합니다
  • 인덱스가 검색을 빠르게 하는 원리를 안다
  • ACID와 CAP 정리를 설명할 수 있습니다
  • 상황에 맞는 데이터베이스를 선택할 수 있습니다

📚 사전 지식

  • SQL 기초: SELECT, INSERT, UPDATE, DELETE 정도
  • 트리 구조: 트리 - B-Tree 인덱스 이해
  • 해시 테이블: 해시 - 해시 인덱스 이해

💡 핵심 개념 미리보기

데이터베이스는 데이터를 효율적으로 저장/관리하는 시스템입니다. RDBMS(MySQL, PostgreSQL)는 테이블과 SQL로 정형 데이터를 다루고, NoSQL(MongoDB, Redis)은 유연한 스키마로 비정형 데이터를 처리합니다. 인덱스로 검색 속도를 높이고, 트랜잭션으로 데이터 일관성을 보장합니다!


🗄️ 들어가며: 데이터베이스의 중요성

인스타그램은 어떻게 수십억 개의 사진을 관리할까요? 은행은 어떻게 수백만 건의 거래를 안전하게 처리할까요?

오늘은 현대 소프트웨어의 핵심인 데이터베이스를 완벽하게 정리해보겠습니다. RDBMS의 기초부터 NoSQL, 그리고 최신 트렌드까지 모두 다룹니다!

📊 데이터베이스 기초

데이터베이스 개념

graph TB
    subgraph "데이터베이스 구조"
        DB[Database]
        DB --> T1[Table 1]
        DB --> T2[Table 2]
        DB --> T3[Table 3]
        
        T1 --> R1[Rows/Records]
        T1 --> C1[Columns/Fields]
        
        T2 --> FK[Foreign Key]
        FK --> T1
    end
    
    subgraph "DBMS 계층"
        APP[Application]
        APP --> DBMS[DBMS]
        DBMS --> DB
        DBMS --> STORAGE[Storage]
    end

데이터베이스 종류

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
def database_types():
    """데이터베이스 종류와 특징"""
    
    db_types = {
        "관계형 DB (RDBMS)": {
            "구조": "테이블, 행, 열",
            "특징": "ACID, SQL, 정규화",
            "예시": "MySQL, PostgreSQL, Oracle",
            "사용처": "정형 데이터, 트랜잭션"
        },
        "NoSQL - 문서형": {
            "구조": "JSON/BSON 문서",
            "특징": "스키마 자유, 중첩 데이터",
            "예시": "MongoDB, CouchDB",
            "사용처": "웹 애플리케이션, CMS"
        },
        "NoSQL - 키-값": {
            "구조": "키-값 쌍",
            "특징": "단순, 빠름",
            "예시": "Redis, DynamoDB",
            "사용처": "캐시, 세션 저장"
        },
        "NoSQL - 컬럼형": {
            "구조": "컬럼 패밀리",
            "특징": "분산, 확장성",
            "예시": "Cassandra, HBase",
            "사용처": "시계열 데이터, 로그"
        },
        "NoSQL - 그래프": {
            "구조": "노드, 엣지",
            "특징": "관계 중심",
            "예시": "Neo4j, Amazon Neptune",
            "사용처": "소셜 네트워크, 추천"
        }
    }
    
    print("데이터베이스 종류:")
    print("=" * 70)
    for db_type, details in db_types.items():
        print(f"\n{db_type}:")
        for key, value in details.items():
            print(f"  {key}: {value}")

database_types()

🔤 SQL 기초

DDL (Data Definition Language)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
-- 테이블 생성
CREATE TABLE users (
    id INT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50) UNIQUE NOT NULL,
    email VARCHAR(100) UNIQUE NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_email (email)
);

-- 테이블 구조 변경
ALTER TABLE users 
ADD COLUMN age INT,
ADD COLUMN status ENUM('active', 'inactive') DEFAULT 'active';

-- 테이블 삭제
DROP TABLE IF EXISTS users;

-- 인덱스 생성
CREATE INDEX idx_username ON users(username);
CREATE UNIQUE INDEX idx_email ON users(email);

DML (Data Manipulation Language)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
import sqlite3

def sql_operations_demo():
    """SQL 기본 연산 데모"""
    
    # 연결 및 테이블 생성
    conn = sqlite3.connect(':memory:')
    cursor = conn.cursor()
    
    # 테이블 생성
    cursor.execute('''
        CREATE TABLE employees (
            id INTEGER PRIMARY KEY,
            name TEXT NOT NULL,
            department TEXT,
            salary INTEGER,
            hire_date DATE
        )
    ''')
    
    # INSERT - 데이터 삽입
    employees = [
        ('김철수', 'Engineering', 70000, '2020-01-15'),
        ('이영희', 'Marketing', 65000, '2019-03-20'),
        ('박민수', 'Engineering', 80000, '2018-07-10'),
        ('정수진', 'HR', 60000, '2021-02-01'),
        ('최동훈', 'Engineering', 75000, '2019-11-30')
    ]
    
    cursor.executemany(
        'INSERT INTO employees (name, department, salary, hire_date) VALUES (?, ?, ?, ?)',
        employees
    )
    
    # SELECT - 데이터 조회
    print("모든 직원:")
    cursor.execute('SELECT * FROM employees')
    for row in cursor.fetchall():
        print(f"  {row}")
    
    # WHERE 절
    print("\nEngineering 부서 직원:")
    cursor.execute('SELECT name, salary FROM employees WHERE department = ?', ('Engineering',))
    for row in cursor.fetchall():
        print(f"  {row[0]}: ${row[1]:,}")
    
    # UPDATE - 데이터 수정
    cursor.execute('UPDATE employees SET salary = salary * 1.1 WHERE department = ?', ('Engineering',))
    print(f"\nEngineering 부서 급여 10% 인상")
    
    # DELETE - 데이터 삭제
    cursor.execute('DELETE FROM employees WHERE salary < ?', (65000,))
    print(f"급여 65000 미만 직원 삭제")
    
    # 집계 함수
    cursor.execute('''
        SELECT 
            department,
            COUNT(*) as count,
            AVG(salary) as avg_salary,
            MAX(salary) as max_salary,
            MIN(salary) as min_salary
        FROM employees
        GROUP BY department
    ''')
    
    print("\n부서별 통계:")
    for row in cursor.fetchall():
        print(f"  {row[0]}: 인원={row[1]}, 평균=${row[2]:,.0f}, 최대=${row[3]:,}, 최소=${row[4]:,}")
    
    conn.close()

sql_operations_demo()

JOIN 연산

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
def join_operations_demo():
    """JOIN 연산 데모"""
    
    conn = sqlite3.connect(':memory:')
    cursor = conn.cursor()
    
    # 테이블 생성
    cursor.execute('''
        CREATE TABLE departments (
            id INTEGER PRIMARY KEY,
            name TEXT UNIQUE,
            location TEXT
        )
    ''')
    
    cursor.execute('''
        CREATE TABLE employees (
            id INTEGER PRIMARY KEY,
            name TEXT,
            dept_id INTEGER,
            salary INTEGER,
            FOREIGN KEY (dept_id) REFERENCES departments(id)
        )
    ''')
    
    cursor.execute('''
        CREATE TABLE projects (
            id INTEGER PRIMARY KEY,
            name TEXT,
            emp_id INTEGER,
            FOREIGN KEY (emp_id) REFERENCES employees(id)
        )
    ''')
    
    # 데이터 삽입
    departments = [
        (1, 'Engineering', 'Seoul'),
        (2, 'Marketing', 'Busan'),
        (3, 'HR', 'Seoul'),
        (4, 'Finance', 'Jeju')
    ]
    
    employees = [
        (1, '김철수', 1, 70000),
        (2, '이영희', 2, 65000),
        (3, '박민수', 1, 80000),
        (4, '정수진', 3, 60000),
        (5, '최동훈', None, 75000)  # 부서 미배정
    ]
    
    projects = [
        (1, 'Project A', 1),
        (2, 'Project B', 1),
        (3, 'Project C', 3)
    ]
    
    cursor.executemany('INSERT INTO departments VALUES (?, ?, ?)', departments)
    cursor.executemany('INSERT INTO employees VALUES (?, ?, ?, ?)', employees)
    cursor.executemany('INSERT INTO projects VALUES (?, ?, ?)', projects)
    
    # INNER JOIN
    print("INNER JOIN - 부서가 있는 직원만:")
    cursor.execute('''
        SELECT e.name, d.name, d.location
        FROM employees e
        INNER JOIN departments d ON e.dept_id = d.id
    ''')
    for row in cursor.fetchall():
        print(f"  {row[0]} - {row[1]} ({row[2]})")
    
    # LEFT JOIN
    print("\nLEFT JOIN - 모든 직원 (부서 없어도):")
    cursor.execute('''
        SELECT e.name, COALESCE(d.name, '미배정') as dept
        FROM employees e
        LEFT JOIN departments d ON e.dept_id = d.id
    ''')
    for row in cursor.fetchall():
        print(f"  {row[0]} - {row[1]}")
    
    # RIGHT JOIN (SQLite는 지원 안함, LEFT JOIN으로 대체)
    print("\nRIGHT JOIN 효과 - 모든 부서:")
    cursor.execute('''
        SELECT d.name, COUNT(e.id) as emp_count
        FROM departments d
        LEFT JOIN employees e ON d.id = e.dept_id
        GROUP BY d.id
    ''')
    for row in cursor.fetchall():
        print(f"  {row[0]}: {row[1]}")
    
    # 다중 JOIN
    print("\n다중 JOIN - 직원-부서-프로젝트:")
    cursor.execute('''
        SELECT e.name, d.name, p.name
        FROM employees e
        LEFT JOIN departments d ON e.dept_id = d.id
        LEFT JOIN projects p ON e.id = p.emp_id
        WHERE p.name IS NOT NULL
    ''')
    for row in cursor.fetchall():
        print(f"  {row[0]} ({row[1]}) - {row[2]}")
    
    conn.close()

join_operations_demo()

📐 정규화 (Normalization)

정규화 단계

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
def normalization_examples():
    """정규화 예시"""
    
    print("정규화 단계별 예시")
    print("=" * 70)
    
    # 비정규형
    print("\n비정규형 (Unnormalized):")
    print("Orders 테이블:")
    print("| OrderID | Customer | Products              | Prices      |")
    print("|---------|----------|----------------------|-------------|")
    print("| 1       | 김철수    | 노트북, 마우스         | 1000, 30    |")
    print("| 2       | 이영희    | 키보드                | 50          |")
    print("문제: 반복 그룹 존재")
    
    # 1NF
    print("\n\n제1정규형 (1NF) - 원자값:")
    print("Orders 테이블:")
    print("| OrderID | Customer | Product | Price |")
    print("|---------|----------|---------|-------|")
    print("| 1       | 김철수    | 노트북   | 1000  |")
    print("| 1       | 김철수    | 마우스   | 30    |")
    print("| 2       | 이영희    | 키보드   | 50    |")
    print("✓ 각 필드가 원자값")
    
    # 2NF
    print("\n\n제2정규형 (2NF) - 부분 종속 제거:")
    print("Orders 테이블:")
    print("| OrderID | Customer |")
    print("|---------|----------|")
    print("| 1       | 김철수    |")
    print("| 2       | 이영희    |")
    print("\nOrderItems 테이블:")
    print("| OrderID | ProductID | Quantity |")
    print("|---------|-----------|----------|")
    print("| 1       | 101       | 1        |")
    print("| 1       | 102       | 1        |")
    print("\nProducts 테이블:")
    print("| ProductID | Product | Price |")
    print("|-----------|---------|-------|")
    print("| 101       | 노트북   | 1000  |")
    print("| 102       | 마우스   | 30    |")
    print("✓ 기본키의 일부에만 종속된 속성 분리")
    
    # 3NF
    print("\n\n제3정규형 (3NF) - 이행 종속 제거:")
    print("Customers 테이블:")
    print("| CustomerID | Name   | CityID |")
    print("|------------|--------|--------|")
    print("| 1          | 김철수  | 1      |")
    print("\nCities 테이블:")
    print("| CityID | City | Country |")
    print("|--------|------|---------|")
    print("| 1      | 서울  | 한국     |")
    print("✓ 비키 속성 간 종속 제거")
    
    # BCNF
    print("\n\nBCNF - 모든 결정자가 후보키:")
    print("✓ 모든 함수 종속에서 결정자가 후보키")

normalization_examples()

정규화 장단점

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
def normalization_tradeoffs():
    """정규화 트레이드오프"""
    
    tradeoffs = {
        "정규화 장점": [
            "데이터 중복 최소화",
            "데이터 무결성 향상",
            "이상 현상(Anomaly) 방지",
            "저장 공간 절약",
            "데이터 일관성 유지"
        ],
        "정규화 단점": [
            "JOIN 연산 증가",
            "쿼리 복잡도 증가",
            "성능 저하 가능성",
            "개발 복잡도 증가"
        ],
        "역정규화 시기": [
            "읽기 성능이 중요한 경우",
            "JOIN이 너무 많은 경우",
            "실시간 집계가 필요한 경우",
            "히스토리 데이터 저장"
        ]
    }
    
    print("정규화 트레이드오프:")
    print("=" * 50)
    for category, items in tradeoffs.items():
        print(f"\n{category}:")
        for item in items:
            print(f"{item}")

normalization_tradeoffs()

🔍 인덱스 (Index)

인덱스 종류와 구조

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
class BTreeIndex:
    """B-Tree 인덱스 시뮬레이션"""
    
    def __init__(self, order=3):
        self.order = order
        self.root = None
    
    class Node:
        def __init__(self, leaf=True):
            self.keys = []
            self.children = []
            self.leaf = leaf
    
    def search(self, key):
        """인덱스 검색 - O(log n)"""
        return self._search_helper(self.root, key)
    
    def _search_helper(self, node, key):
        if node is None:
            return False
        
        i = 0
        while i < len(node.keys) and key > node.keys[i]:
            i += 1
        
        if i < len(node.keys) and key == node.keys[i]:
            return True
        
        if node.leaf:
            return False
        
        return self._search_helper(node.children[i], key)

def index_types():
    """인덱스 종류"""
    
    index_info = {
        "B-Tree 인덱스": {
            "구조": "균형 트리",
            "특징": "범위 검색 효율적",
            "사용": "대부분의 RDBMS 기본",
            "시간복잡도": "O(log n)"
        },
        "Hash 인덱스": {
            "구조": "해시 테이블",
            "특징": "동등 비교만 가능",
            "사용": "Memory 테이블",
            "시간복잡도": "O(1)"
        },
        "Bitmap 인덱스": {
            "구조": "비트맵",
            "특징": "카디널리티 낮은 컬럼",
            "사용": "데이터 웨어하우스",
            "시간복잡도": "O(1)"
        },
        "Full-text 인덱스": {
            "구조": "역 인덱스",
            "특징": "텍스트 검색",
            "사용": "검색 엔진",
            "시간복잡도": "O(n)"
        }
    }
    
    print("인덱스 종류:")
    print("=" * 60)
    for idx_type, details in index_info.items():
        print(f"\n{idx_type}:")
        for key, value in details.items():
            print(f"  {key}: {value}")

index_types()

인덱스 최적화

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
def index_optimization_demo():
    """인덱스 최적화 예시"""
    
    conn = sqlite3.connect(':memory:')
    cursor = conn.cursor()
    
    # 대량 데이터 테이블 생성
    cursor.execute('''
        CREATE TABLE users (
            id INTEGER PRIMARY KEY,
            username TEXT,
            email TEXT,
            age INTEGER,
            created_at DATE
        )
    ''')
    
    # 데이터 삽입 (실제로는 더 많은 데이터)
    import random
    from datetime import datetime, timedelta
    
    users = []
    for i in range(10000):
        username = f"user{i}"
        email = f"user{i}@example.com"
        age = random.randint(18, 80)
        created = datetime.now() - timedelta(days=random.randint(0, 365))
        users.append((username, email, age, created.isoformat()))
    
    cursor.executemany(
        'INSERT INTO users (username, email, age, created_at) VALUES (?, ?, ?, ?)',
        users
    )
    
    # 인덱스 없이 쿼리
    print("인덱스 최적화 예시:")
    print("-" * 50)
    
    # EXPLAIN QUERY PLAN 시뮬레이션
    print("\n1. 인덱스 없는 쿼리:")
    print("   SELECT * FROM users WHERE email = 'user5000@example.com'")
    print("   → Full Table Scan: 10,000개 행 검색")
    
    # 인덱스 생성
    cursor.execute('CREATE INDEX idx_email ON users(email)')
    print("\n2. 인덱스 생성 후:")
    print("   CREATE INDEX idx_email ON users(email)")
    print("   → Index Scan: 1개 행 직접 접근")
    
    # 복합 인덱스
    cursor.execute('CREATE INDEX idx_age_created ON users(age, created_at)')
    print("\n3. 복합 인덱스:")
    print("   CREATE INDEX idx_age_created ON users(age, created_at)")
    print("   WHERE age = 25 AND created_at > '2024-01-01'")
    print("   → 복합 인덱스 활용")
    
    # 커버링 인덱스
    print("\n4. 커버링 인덱스:")
    print("   CREATE INDEX idx_covering ON users(email, username, age)")
    print("   SELECT username, age FROM users WHERE email = 'user@example.com'")
    print("   → 인덱스만으로 쿼리 완료 (테이블 접근 불필요)")
    
    conn.close()

index_optimization_demo()

🔐 트랜잭션과 ACID

ACID 속성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
def acid_properties():
    """ACID 속성 설명과 예시"""
    
    print("ACID 속성:")
    print("=" * 70)
    
    # Atomicity
    print("\n1. Atomicity (원자성):")
    print("   정의: 트랜잭션의 모든 연산이 완전히 수행되거나 전혀 수행되지 않음")
    print("   예시: 계좌 이체")
    print("   ```sql")
    print("   BEGIN TRANSACTION;")
    print("   UPDATE accounts SET balance = balance - 100 WHERE id = 1;")
    print("   UPDATE accounts SET balance = balance + 100 WHERE id = 2;")
    print("   COMMIT;  -- 둘 다 성공하거나 둘 다 실패")
    print("   ```")
    
    # Consistency
    print("\n2. Consistency (일관성):")
    print("   정의: 트랜잭션 전후로 데이터베이스가 일관된 상태 유지")
    print("   예시: 계좌 총액 불변")
    print("   ```sql")
    print("   -- 제약 조건: balance >= 0")
    print("   UPDATE accounts SET balance = balance - 1000 WHERE id = 1;")
    print("   -- 잔액 부족시 트랜잭션 롤백")
    print("   ```")
    
    # Isolation
    print("\n3. Isolation (격리성):")
    print("   정의: 동시 실행 트랜잭션들이 서로 영향을 주지 않음")
    print("   격리 수준:")
    print("   • Read Uncommitted: Dirty Read 가능")
    print("   • Read Committed: Dirty Read 방지")
    print("   • Repeatable Read: Non-repeatable Read 방지")
    print("   • Serializable: Phantom Read 방지")
    
    # Durability
    print("\n4. Durability (지속성):")
    print("   정의: 커밋된 트랜잭션의 결과는 영구적으로 보존")
    print("   예시: 시스템 장애 후에도 데이터 유지")
    print("   구현: Write-Ahead Logging (WAL)")

acid_properties()

트랜잭션 격리 수준

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
def isolation_levels_demo():
    """트랜잭션 격리 수준 데모"""
    
    problems = {
        "Dirty Read": {
            "설명": "커밋되지 않은 데이터 읽기",
            "예시": "T1이 수정 중인 데이터를 T2가 읽음",
            "방지": "Read Committed 이상"
        },
        "Non-repeatable Read": {
            "설명": "같은 쿼리가 다른 결과 반환",
            "예시": "T1이 두 번 읽는 사이 T2가 수정",
            "방지": "Repeatable Read 이상"
        },
        "Phantom Read": {
            "설명": "없던 행이 생김",
            "예시": "T1이 범위 조회 중 T2가 삽입",
            "방지": "Serializable"
        }
    }
    
    levels = {
        "Read Uncommitted": {
            "Dirty Read": "O",
            "Non-repeatable": "O",
            "Phantom": "O",
            "성능": "최고"
        },
        "Read Committed": {
            "Dirty Read": "X",
            "Non-repeatable": "O",
            "Phantom": "O",
            "성능": "좋음"
        },
        "Repeatable Read": {
            "Dirty Read": "X",
            "Non-repeatable": "X",
            "Phantom": "O",
            "성능": "보통"
        },
        "Serializable": {
            "Dirty Read": "X",
            "Non-repeatable": "X",
            "Phantom": "X",
            "성능": "최저"
        }
    }
    
    print("트랜잭션 격리 수준:")
    print("=" * 70)
    
    print("\n문제 현상:")
    for problem, details in problems.items():
        print(f"\n{problem}:")
        for key, value in details.items():
            print(f"  {key}: {value}")
    
    print("\n\n격리 수준별 특징:")
    print("-" * 60)
    print(f"{'격리 수준':<20} {'Dirty':<10} {'Non-rep':<10} {'Phantom':<10} {'성능':<10}")
    print("-" * 60)
    for level, features in levels.items():
        print(f"{level:<20} {features['Dirty Read']:<10} {features['Non-repeatable']:<10} "
              f"{features['Phantom']:<10} {features['성능']:<10}")

isolation_levels_demo()

🌐 NoSQL 데이터베이스

CAP 정리

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
def cap_theorem():
    """CAP 정리 설명"""
    
    print("CAP 정리 (Brewer's Theorem):")
    print("=" * 70)
    print("분산 시스템에서 다음 3가지 중 최대 2개만 보장 가능:\n")
    
    properties = {
        "Consistency (일관성)": {
            "정의": "모든 노드가 같은 데이터를 가짐",
            "예시": "강한 일관성 vs 최종 일관성"
        },
        "Availability (가용성)": {
            "정의": "모든 요청이 응답을 받음",
            "예시": "장애 시에도 서비스 지속"
        },
        "Partition Tolerance (분할 내성)": {
            "정의": "네트워크 분할 시에도 동작",
            "예시": "노드 간 통신 장애 처리"
        }
    }
    
    for prop, details in properties.items():
        print(f"\n{prop}:")
        for key, value in details.items():
            print(f"  {key}: {value}")
    
    print("\n\n시스템별 선택:")
    systems = {
        "CA (일관성 + 가용성)": {
            "특징": "단일 노드 시스템",
            "예시": "전통적 RDBMS (MySQL, PostgreSQL)",
            "단점": "분산 불가"
        },
        "CP (일관성 + 분할내성)": {
            "특징": "강한 일관성 분산 시스템",
            "예시": "MongoDB, HBase, Redis",
            "단점": "가용성 희생"
        },
        "AP (가용성 + 분할내성)": {
            "특징": "최종 일관성 분산 시스템",
            "예시": "Cassandra, DynamoDB, CouchDB",
            "단점": "일관성 희생"
        }
    }
    
    for system, details in systems.items():
        print(f"\n{system}:")
        for key, value in details.items():
            print(f"  {key}: {value}")

cap_theorem()

NoSQL 데이터 모델링

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# MongoDB 스타일 문서형 DB
def document_db_example():
    """문서형 DB 예시"""
    
    # RDBMS에서는 여러 테이블로 분리될 데이터
    user_document = {
        "_id": "user123",
        "username": "johndoe",
        "email": "john@example.com",
        "profile": {
            "firstName": "John",
            "lastName": "Doe",
            "age": 30,
            "address": {
                "street": "123 Main St",
                "city": "Seoul",
                "country": "Korea"
            }
        },
        "orders": [
            {
                "orderId": "order1",
                "date": "2024-01-15",
                "total": 150.00,
                "items": [
                    {"productId": "prod1", "quantity": 2, "price": 50.00},
                    {"productId": "prod2", "quantity": 1, "price": 50.00}
                ]
            }
        ],
        "preferences": {
            "newsletter": True,
            "notifications": {
                "email": True,
                "sms": False
            }
        },
        "tags": ["premium", "active", "verified"]
    }
    
    print("문서형 DB 예시 (MongoDB):")
    print("-" * 50)
    import json
    print(json.dumps(user_document, indent=2, ensure_ascii=False))
    
    print("\n\n장점:")
    print("• 스키마 유연성")
    print("• 중첩 데이터 저장")
    print("• JOIN 불필요")
    print("• 수평 확장 용이")

document_db_example()

Redis 예시

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
def redis_patterns():
    """Redis 사용 패턴"""
    
    print("Redis 사용 패턴:")
    print("=" * 60)
    
    patterns = {
        "캐싱": {
            "용도": "DB 쿼리 결과 캐시",
            "예시": """
            # 캐시 확인
            cached = redis.get(f"user:{user_id}")
            if cached:
                return json.loads(cached)
            
            # DB 조회 후 캐시 저장
            user = db.query(f"SELECT * FROM users WHERE id = {user_id}")
            redis.setex(f"user:{user_id}", 3600, json.dumps(user))
            """,
            "TTL": "1시간"
        },
        "세션 저장": {
            "용도": "사용자 세션 관리",
            "예시": """
            # 세션 저장
            redis.setex(f"session:{session_id}", 1800, user_data)
            
            # 세션 조회
            session = redis.get(f"session:{session_id}")
            """,
            "TTL": "30분"
        },
        "실시간 랭킹": {
            "용도": "리더보드, 순위",
            "예시": """
            # 점수 추가/업데이트
            redis.zadd("leaderboard", {user_id: score})
            
            # 상위 10명 조회
            top10 = redis.zrevrange("leaderboard", 0, 9, withscores=True)
            """,
            "자료구조": "Sorted Set"
        },
        "발행/구독": {
            "용도": "실시간 메시징",
            "예시": """
            # 발행
            redis.publish("channel:news", message)
            
            # 구독
            pubsub = redis.pubsub()
            pubsub.subscribe("channel:news")
            """,
            "패턴": "Pub/Sub"
        },
        "분산 락": {
            "용도": "동시성 제어",
            "예시": """
            # 락 획득
            if redis.set("lock:resource", "1", nx=True, ex=10):
                # 임계 영역
                process_resource()
                redis.delete("lock:resource")
            """,
            "TTL": "10초"
        }
    }
    
    for pattern, details in patterns.items():
        print(f"\n{pattern}:")
        for key, value in details.items():
            if key == "예시":
                print(f"  {key}:{value}")
            else:
                print(f"  {key}: {value}")

redis_patterns()

🔄 데이터베이스 최적화

쿼리 최적화

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
def query_optimization_tips():
    """쿼리 최적화 팁"""
    
    tips = {
        "SELECT 최적화": [
            "SELECT * 대신 필요한 컬럼만 선택",
            "LIMIT 사용으로 결과 제한",
            "EXISTS 대신 IN 사용 고려",
            "서브쿼리보다 JOIN 선호"
        ],
        "JOIN 최적화": [
            "작은 테이블을 먼저 JOIN",
            "ON 절에 인덱스 컬럼 사용",
            "불필요한 JOIN 제거",
            "적절한 JOIN 타입 선택"
        ],
        "WHERE 최적화": [
            "인덱스 컬럼 우선 사용",
            "함수 사용 자제 (WHERE YEAR(date) 대신 범위 사용)",
            "OR 대신 IN 사용",
            "NULL 비교 최소화"
        ],
        "인덱스 활용": [
            "카디널리티 높은 컬럼에 인덱스",
            "복합 인덱스 순서 고려",
            "커버링 인덱스 활용",
            "불필요한 인덱스 제거"
        ],
        "기타 최적화": [
            "배치 INSERT 사용",
            "EXPLAIN으로 실행 계획 확인",
            "쿼리 캐시 활용",
            "파티셔닝 고려"
        ]
    }
    
    print("쿼리 최적화 팁:")
    print("=" * 60)
    for category, items in tips.items():
        print(f"\n{category}:")
        for item in items:
            print(f"{item}")

query_optimization_tips()

샤딩과 복제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
def sharding_replication():
    """샤딩과 복제 전략"""
    
    print("데이터베이스 확장 전략:")
    print("=" * 70)
    
    # 수직 확장 vs 수평 확장
    print("\n1. 확장 방식:")
    print("  수직 확장 (Scale-up): 서버 성능 향상")
    print("  수평 확장 (Scale-out): 서버 수 증가")
    
    # 복제
    print("\n2. 복제 (Replication):")
    print("  Master-Slave:")
    print("    • Master: 쓰기 전담")
    print("    • Slave: 읽기 전담")
    print("    • 읽기 성능 향상")
    print("  Master-Master:")
    print("    • 양방향 복제")
    print("    • 충돌 해결 필요")
    
    # 샤딩
    print("\n3. 샤딩 (Sharding):")
    print("  수평 샤딩:")
    print("    • 행 단위로 분할")
    print("    • 예: user_id % 4로 샤드 결정")
    print("  수직 샤딩:")
    print("    • 테이블/컬럼 단위로 분할")
    print("    • 예: 사용자 정보와 주문 정보 분리")
    
    # 샤딩 전략
    print("\n4. 샤딩 키 전략:")
    strategies = {
        "Range": "날짜, ID 범위로 분할",
        "Hash": "해시 함수로 균등 분산",
        "List": "지역, 카테고리별 분할",
        "Composite": "여러 키 조합"
    }
    
    for strategy, description in strategies.items():
        print(f"  {strategy}: {description}")

sharding_replication()

🎯 핵심 정리

꼭 기억해야 할 5가지

  1. ACID: 트랜잭션의 4가지 속성
  2. 정규화: 중복 제거와 무결성 vs 성능
  3. 인덱스: B-Tree 구조와 최적화
  4. CAP 정리: 분산 시스템의 트레이드오프
  5. SQL vs NoSQL: 각각의 장단점과 사용 시기

데이터베이스 선택 가이드

  • RDBMS: 정형 데이터, ACID 필요, 복잡한 쿼리
  • 문서형 DB: 유연한 스키마, 중첩 데이터
  • 키-값 DB: 단순 조회, 캐싱, 세션
  • 그래프 DB: 관계 중심 데이터, 추천 시스템

📚 추가 학습 자료

추천 자료

  • “Database System Concepts” - Silberschatz
  • “Designing Data-Intensive Applications” - Martin Kleppmann
  • SQL 연습: HackerRank, LeetCode Database

🚀 다음 시간 예고

다음 포스트에서는 소프트웨어 공학을 다룹니다. 개발 방법론, 디자인 패턴, 테스팅, CI/CD 등을 알아보겠습니다!


📌 시리즈 네비게이션

순서 제목 링크
15 그리디와 분할정복 ← 이전 글
16 데이터베이스 이론: RDBMS부터 NoSQL까지 현재 글
17 소프트웨어 공학: 체계적인 개발 방법론 다음 글 →
📋 전체 시리즈 목차 보기

기초 이론편

  1. CS 입문 ✅
  2. 컴퓨터 구조 ✅
  3. 이진수와 논리 게이트 ✅
  4. 운영체제 기초 ✅
  5. 네트워크 기초 ✅

자료구조편

  1. 배열과 연결 리스트 ✅
  2. 스택과 큐 ✅
  3. 트리 구조 ✅
  4. 그래프 ✅
  5. 해시 테이블 ✅

알고리즘편

  1. 복잡도 분석 ✅
  2. 정렬 알고리즘 ✅
  3. 탐색 알고리즘 ✅
  4. 동적 계획법 ✅
  5. 그리디와 분할정복 ✅

실무 응용편

  1. 데이터베이스 이론 ✅
  2. 소프트웨어 공학
  3. 보안 기초
  4. 분산 시스템
  5. CS 종합과 면접 준비
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.