[CS 기초 #16] 데이터베이스 이론: RDBMS부터 NoSQL까지 완벽 정리
[CS 기초 #16] 데이터베이스 이론: RDBMS부터 NoSQL까지 완벽 정리
데이터를 지배하는 자! RDBMS부터 NoSQL까지, 데이터베이스의 모든 것을 배웁니다.
🎯 이 글을 읽고 나면
- RDBMS와 NoSQL의 차이를 명확히 설명할 수 있습니다
- 정규화의 필요성과 1NF~3NF를 이해합니다
- 인덱스가 검색을 빠르게 하는 원리를 안다
- ACID와 CAP 정리를 설명할 수 있습니다
- 상황에 맞는 데이터베이스를 선택할 수 있습니다
📚 사전 지식
💡 핵심 개념 미리보기
데이터베이스는 데이터를 효율적으로 저장/관리하는 시스템입니다. 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가지
- ACID: 트랜잭션의 4가지 속성
- 정규화: 중복 제거와 무결성 vs 성능
- 인덱스: B-Tree 구조와 최적화
- CAP 정리: 분산 시스템의 트레이드오프
- SQL vs NoSQL: 각각의 장단점과 사용 시기
데이터베이스 선택 가이드
- RDBMS: 정형 데이터, ACID 필요, 복잡한 쿼리
- 문서형 DB: 유연한 스키마, 중첩 데이터
- 키-값 DB: 단순 조회, 캐싱, 세션
- 그래프 DB: 관계 중심 데이터, 추천 시스템
📚 추가 학습 자료
추천 자료
- “Database System Concepts” - Silberschatz
- “Designing Data-Intensive Applications” - Martin Kleppmann
- SQL 연습: HackerRank, LeetCode Database
🚀 다음 시간 예고
다음 포스트에서는 소프트웨어 공학을 다룹니다. 개발 방법론, 디자인 패턴, 테스팅, CI/CD 등을 알아보겠습니다!
📌 시리즈 네비게이션
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.
