NoSQL과 MongoDB 시작하기

본 글은 Claude Sonnet 4로 작성한 글입니다.


NoSQL이란?

NoSQL은 "Not Only SQL"의 줄임말로, 전통적인 관계형 데이터베이스가 아닌 데이터베이스 시스템을 의미한다. 관계형 데이터베이스가 테이블과 행, 열로 구성된 구조화된 데이터를 다룬다면, NoSQL은 더 유연한 데이터 구조를 지원한다.

NoSQL의 주요 특징

NoSQL 데이터베이스는 다음과 같은 특징을 가진다:

  • 스키마 자유로움: 미리 정의된 스키마 없이 데이터를 저장할 수 있다
  • 수평 확장: 서버를 추가하여 성능을 향상시키기 쉽다
  • 대용량 데이터 처리: 빅데이터 환경에 적합하다
  • 다양한 데이터 타입: JSON, XML 등 다양한 형태의 데이터를 저장할 수 있다

NoSQL 데이터베이스 유형

NoSQL 데이터베이스는 크게 네 가지로 분류된다:

  1. Document Store: MongoDB, CouchDB
  2. Key-Value Store: Redis, DynamoDB
  3. Column Family: Cassandra, HBase
  4. Graph Database: Neo4j, ArangoDB

MongoDB 소개

MongoDB는 가장 인기 있는 Document Store 타입의 NoSQL 데이터베이스다. JSON과 유사한 BSON(Binary JSON) 형태로 데이터를 저장하며, 스키마가 없어 개발 초기 단계에서 데이터 구조를 자주 변경해야 할 때 매우 유용하다.

MongoDB의 장점

  • 개발 속도: 스키마 설계에 시간을 들이지 않고 빠르게 개발할 수 있다
  • 확장성: 샤딩을 통해 수평 확장이 용이하다
  • 유연성: 복잡한 중첩 구조의 데이터를 자연스럽게 표현할 수 있다
  • 인덱싱: 다양한 인덱스 타입을 지원하여 쿼리 성능을 최적화할 수 있다

MongoDB 설치하기

Linux (Ubuntu/Debian)

# MongoDB GPG 키 추가
wget -qO - https://www.mongodb.org/static/pgp/server-7.0.asc | sudo apt-key add -

# MongoDB 저장소 추가
echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu jammy/mongodb-org/7.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-7.0.list

# 패키지 목록 업데이트 및 설치
sudo apt-get update
sudo apt-get install -y mongodb-org

# MongoDB 서비스 시작
sudo systemctl start mongod
sudo systemctl enable mongod

macOS

# Homebrew를 사용한 설치
brew tap mongodb/brew
brew install mongodb-community

# MongoDB 서비스 시작
brew services start mongodb-community

Windows

MongoDB 공식 웹사이트에서 Windows Installer를 다운로드하여 설치한다. 설치 후 MongoDB Compass GUI 도구도 함께 설치된다.

Python으로 MongoDB 사용하기

pip install pymongo

기본 연결 및 Key-Value 데이터 저장

MongoDB에서 Key-Value 형태의 데이터는 필드-값 쌍으로 저장된다. 다음은 기본적인 사용 예제다:

from pymongo import MongoClient
from datetime import datetime
import json

# MongoDB 연결
client = MongoClient('mongodb://localhost:27017/')

# 데이터베이스 선택 (없으면 자동 생성)
db = client['my_database']

# 컬렉션 선택 (테이블과 유사한 개념)
collection = db['key_value_store']

# Key-Value 데이터 저장
def store_key_value(key, value):
    """키-값 쌍을 MongoDB에 저장"""
    document = {
        'key': key,
        'value': value,
        'created_at': datetime.now(),
        'updated_at': datetime.now()
    }

    # 기존 키가 있으면 업데이트, 없으면 삽입
    result = collection.replace_one(
        {'key': key}, 
        document, 
        upsert=True
    )

    if result.upserted_id:
        print(f"새로운 키-값 저장됨: {key} = {value}")
    else:
        print(f"기존 키-값 업데이트됨: {key} = {value}")

# Key-Value 데이터 조회
def get_value(key):
    """키에 해당하는 값을 조회"""
    document = collection.find_one({'key': key})

    if document:
        return document['value']
    else:
        return None

# Key-Value 데이터 삭제
def delete_key(key):
    """키-값 쌍을 삭제"""
    result = collection.delete_one({'key': key})

    if result.deleted_count > 0:
        print(f"키 삭제됨: {key}")
        return True
    else:
        print(f"키를 찾을 수 없음: {key}")
        return False

# 모든 키 목록 조회
def list_all_keys():
    """저장된 모든 키 목록을 반환"""
    keys = []
    for document in collection.find({}, {'key': 1, '_id': 0}):
        keys.append(document['key'])
    return keys

# 사용 예제
if __name__ == "__main__":
    # 다양한 타입의 값 저장
    store_key_value("user:1001", {"name": "김철수", "age": 25, "email": "kim@example.com"})
    store_key_value("config:max_users", 1000)
    store_key_value("cache:popular_items", ["laptop", "mouse", "keyboard"])
    store_key_value("session:abc123", {"user_id": 1001, "login_time": "2024-01-15T10:30:00"})

    # 값 조회
    user_data = get_value("user:1001")
    print(f"사용자 데이터: {user_data}")

    max_users = get_value("config:max_users")
    print(f"최대 사용자 수: {max_users}")

    # 존재하지 않는 키 조회
    unknown_value = get_value("unknown_key")
    print(f"존재하지 않는 키의 값: {unknown_value}")

    # 모든 키 목록 출력
    all_keys = list_all_keys()
    print(f"저장된 모든 키: {all_keys}")

    # 키 삭제
    delete_key("cache:popular_items")

    # 연결 종료
    client.close()

복합 쿼리 예제

MongoDB의 강력한 쿼리 기능을 활용한 예제다:

# 특정 패턴의 키들만 조회
def get_keys_by_pattern(pattern):
    """정규표현식 패턴으로 키를 조회"""
    import re

    regex_pattern = re.compile(pattern)
    documents = collection.find({'key': regex_pattern})

    result = {}
    for doc in documents:
        result[doc['key']] = doc['value']

    return result

# 최근 생성된 키-값 쌍들 조회
def get_recent_entries(limit=10):
    """최근 생성된 항목들을 조회"""
    documents = collection.find().sort('created_at', -1).limit(limit)

    result = []
    for doc in documents:
        result.append({
            'key': doc['key'],
            'value': doc['value'],
            'created_at': doc['created_at']
        })

    return result

# 사용 예제
user_keys = get_keys_by_pattern(r'^user:')
print(f"사용자 관련 키들: {user_keys}")

recent_entries = get_recent_entries(5)
print(f"최근 5개 항목: {recent_entries}")

실전 활용 시나리오

1. 세션 저장소

웹 애플리케이션의 사용자 세션을 저장하는 용도로 활용할 수 있다:

def store_session(session_id, user_data, expire_minutes=30):
    """사용자 세션을 저장"""
    from datetime import timedelta

    expire_time = datetime.now() + timedelta(minutes=expire_minutes)

    session_document = {
        'key': f"session:{session_id}",
        'value': user_data,
        'created_at': datetime.now(),
        'expires_at': expire_time
    }

    collection.replace_one(
        {'key': f"session:{session_id}"}, 
        session_document, 
        upsert=True
    )

# 세션 저장 예제
store_session("abc123", {
    "user_id": 1001, 
    "username": "john_doe",
    "roles": ["user", "admin"]
})

2. 캐시 시스템

자주 조회되는 데이터를 캐싱하는 용도로 사용할 수 있다:

def cache_data(cache_key, data, ttl_seconds=3600):
    """데이터를 캐시에 저장"""
    from datetime import timedelta

    expire_time = datetime.now() + timedelta(seconds=ttl_seconds)

    cache_document = {
        'key': f"cache:{cache_key}",
        'value': data,
        'created_at': datetime.now(),
        'expires_at': expire_time
    }

    collection.replace_one(
        {'key': f"cache:{cache_key}"}, 
        cache_document, 
        upsert=True
    )

def get_cached_data(cache_key):
    """캐시에서 데이터 조회"""
    document = collection.find_one({'key': f"cache:{cache_key}"})

    if document and document.get('expires_at', datetime.now()) > datetime.now():
        return document['value']
    elif document:
        # 만료된 캐시 삭제
        collection.delete_one({'key': f"cache:{cache_key}"})

    return None

성능 최적화 팁

인덱스 생성

자주 조회되는 키에 대해 인덱스를 생성하면 조회 성능이 크게 향상된다:

# 키 필드에 인덱스 생성
collection.create_index('key', unique=True)

# 복합 인덱스 생성 (키와 생성시간)
collection.create_index([('key', 1), ('created_at', -1)])

# 인덱스 확인
indexes = collection.list_indexes()
for index in indexes:
    print(f"인덱스: {index}")

배치 처리

여러 개의 키-값을 한 번에 처리할 때는 배치 연산을 사용한다:

def store_multiple_key_values(key_value_pairs):
    """여러 키-값 쌍을 배치로 저장"""
    operations = []
    current_time = datetime.now()

    for key, value in key_value_pairs.items():
        document = {
            'key': key,
            'value': value,
            'created_at': current_time,
            'updated_at': current_time
        }

        operations.append(
            pymongo.ReplaceOne(
                {'key': key}, 
                document, 
                upsert=True
            )
        )

    # 배치 실행
    result = collection.bulk_write(operations)
    print(f"배치 처리 완료: {result.bulk_api_result}")

# 사용 예제
batch_data = {
    "config:app_name": "My Application",
    "config:debug_mode": False,
    "config:max_connections": 100,
    "config:timeout": 30
}

store_multiple_key_values(batch_data)

MongoDB를 Key-Value 저장소로 활용하면 관계형 데이터베이스보다 훨씬 유연하고 빠른 데이터 저장이 가능하다. 특히 스키마가 자주 변경되는 개발 환경이나, 대용량의 비정형 데이터를 다룰 때 그 진가를 발휘한다.

다음 단계로는 MongoDB의 집계(Aggregation) 파이프라인이나 샤딩(Sharding) 기능을 학습하면 더 고급 활용이 가능하다. MongoDB의 강력한 기능들을 차근차근 익혀가며 프로젝트에 적용해보자.