🗄️ 데이터베이스

Database

데이터베이스

구조화된 데이터의 조직적 저장소. 관계형(RDBMS)과 비관계형(NoSQL)으로 구분되며, DBMS를 통해 데이터를 효율적으로 저장, 조회, 수정, 삭제합니다.

📖 상세 설명

데이터베이스(Database)는 체계적으로 구조화된 데이터의 집합으로, DBMS(Database Management System)를 통해 관리됩니다. 애플리케이션의 영속적 데이터 저장, 빠른 검색, 동시 접근 제어, 데이터 무결성 보장 등의 핵심 기능을 제공합니다.

데이터베이스 분류

관계형 데이터베이스 (RDBMS)

테이블(행/열) 기반, SQL 쿼리 사용, 스키마 정의 필수, ACID 트랜잭션 보장. 정형화된 데이터와 복잡한 JOIN 연산에 적합.

PostgreSQL
오픈소스, 확장성
MySQL
가장 널리 사용
Oracle
엔터프라이즈
SQL Server
Microsoft

비관계형 데이터베이스 (NoSQL)

스키마 유연, 수평 확장 용이, BASE 특성. 대용량 비정형 데이터와 높은 쓰기 처리량에 적합.

MongoDB
문서형
Redis
키-값 (인메모리)
Cassandra
와이드 컬럼
Neo4j
그래프

ACID vs BASE

특성 ACID (관계형) BASE (NoSQL)
일관성 강한 일관성 최종적 일관성
가용성 일관성 우선 가용성 우선
확장 수직 확장 (Scale-up) 수평 확장 (Scale-out)
적합 케이스 금융, 주문 시스템 로그, 소셜, IoT

💻 코드 예제

PostgreSQL - 테이블 설계와 CRUD
-- 테이블 생성 (정규화된 스키마) CREATE TABLE users ( id SERIAL PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL, name VARCHAR(100) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE orders ( id SERIAL PRIMARY KEY, user_id INTEGER REFERENCES users(id) ON DELETE CASCADE, total_amount DECIMAL(10, 2) NOT NULL, status VARCHAR(20) DEFAULT 'pending', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- 인덱스 생성 CREATE INDEX idx_orders_user_id ON orders(user_id); CREATE INDEX idx_orders_status ON orders(status); -- CRUD 작업 INSERT INTO users (email, name) VALUES ('user@example.com', '홍길동'); SELECT u.name, COUNT(o.id) AS order_count, SUM(o.total_amount) AS total_spent FROM users u LEFT JOIN orders o ON u.id = o.user_id GROUP BY u.id HAVING COUNT(o.id) > 0;
MongoDB - 문서 기반 스키마와 집계
// 컬렉션에 문서 삽입 db.users.insertOne({ email: "user@example.com", name: "홍길동", profile: { age: 30, interests: ["AI", "Database"] }, orders: [ { product: "서버", amount: 1000000, date: new Date() } ], created_at: new Date() }); // 집계 파이프라인 - 사용자별 총 주문 금액 db.users.aggregate([ { $unwind: "$orders" }, { $group: { _id: "$email", total_spent: { $sum: "$orders.amount" }, order_count: { $sum: 1 } }}, { $sort: { total_spent: -1 } }, { $limit: 10 } ]);
Python - SQLAlchemy를 사용한 ORM 패턴
from sqlalchemy import create_engine, Column, Integer, String, ForeignKey from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker, relationship # 데이터베이스 연결 DATABASE_URL = "postgresql://user:password@localhost:5432/mydb" engine = create_engine(DATABASE_URL) Session = sessionmaker(bind=engine) Base = declarative_base() # 모델 정의 class User(Base): __tablename__ = 'users' id = Column(Integer, primary_key=True) email = Column(String(255), unique=True, nullable=False) name = Column(String(100), nullable=False) orders = relationship("Order", back_populates="user", lazy="selectin") class Order(Base): __tablename__ = 'orders' id = Column(Integer, primary_key=True) user_id = Column(Integer, ForeignKey('users.id')) total_amount = Column(Integer) user = relationship("User", back_populates="orders") # CRUD 작업 session = Session() # Create new_user = User(email="test@example.com", name="테스트") session.add(new_user) session.commit() # Read with relationship (N+1 방지) users = session.query(User).options( selectinload(User.orders) ).filter(User.name.like('%테스트%')).all() # Transaction try: session.begin() user = session.query(User).filter_by(id=1).first() user.name = "수정된 이름" session.commit() except Exception: session.rollback() raise
Redis - 캐싱과 세션 저장
import redis import json r = redis.Redis(host='localhost', port=6379, db=0) # 캐시 패턴: Cache-Aside def get_user(user_id: int): cache_key = f"user:{user_id}" # 1. 캐시 확인 cached = r.get(cache_key) if cached: return json.loads(cached) # 2. DB 조회 (캐시 미스) user = db.query(User).get(user_id) # SQLAlchemy # 3. 캐시 저장 (TTL 1시간) r.setex(cache_key, 3600, json.dumps(user.to_dict())) return user # 세션 저장 r.hset("session:abc123", mapping={ "user_id": "1", "email": "user@example.com", "login_at": "2024-01-01T00:00:00" }) r.expire("session:abc123", 86400) # 24시간 만료

💬 현업 대화 예시

신입 개발자
"이 서비스에 어떤 DB를 써야 할까요? PostgreSQL? MongoDB?"
시니어 개발자
"데이터 모델부터 봐. 관계가 복잡하고 트랜잭션이 중요하면 PostgreSQL, 스키마가 자주 바뀌고 문서 단위 조회가 많으면 MongoDB. 우리 서비스는 주문-결제 시스템이라 ACID가 필수니까 PostgreSQL로 가자."
백엔드 개발자
"DB 응답이 느린데 어디서부터 봐야 하나요?"
DBA
"EXPLAIN ANALYZE 먼저 찍어봐. 인덱스 탄다고 전부 빠른 게 아냐. Full Table Scan인지, 인덱스는 있는데 안 타는 건지, 커버링 인덱스로 최적화 가능한지 확인해야지."
아키텍트
"트래픽 10배 증가 예상되는데 DB가 버틸까요?"
팀리드
"Read는 리플리카 추가하고, 핫 데이터는 Redis 캐시 레이어 넣어. Write가 병목이면 샤딩 고려해야 하는데, 그 전에 Connection Pool 설정이랑 쿼리 최적화부터 하자."

⚠️ 주의사항

⚠️ N+1 쿼리 문제

ORM에서 관계 데이터를 조회할 때 반복문마다 추가 쿼리가 발생하는 N+1 문제 주의! Eager Loading(selectinload, joinedload)이나 데이터로더 패턴을 사용하세요.

⚠️ 인덱스 과도 생성

인덱스가 많을수록 INSERT/UPDATE가 느려집니다. 실제 쿼리 패턴을 분석해서 필요한 인덱스만 생성하고, 복합 인덱스의 컬럼 순서(선택도 높은 것 먼저)를 신중히 결정하세요.

⚠️ Connection Pool 고갈

데이터베이스 커넥션은 비싼 리소스입니다. Connection Pool을 적절히 설정하고, 쿼리 후 반드시 커넥션을 반환하세요. 장기 트랜잭션은 커넥션을 점유하므로 피해야 합니다.

🔗 관련 용어

📚 더 배우기