MongoDB로 SQL 관계형 데이터를 마이그레이션하는 단계별 가이드

포괄적인 단계별 가이드를 통해 SQL 관계형 데이터를 MongoDB로 마이그레이션하는 방법을 알아보세요. 기존 스키마를 효율적인 MongoDB 문서 구조로 변환하는 모범 사례를 살펴보고, 여기에는 필수 계획, 임베딩 및 참조와 같은 스키마 설계 전략, 데이터 추출, 변환 기술 및 MongoDB로의 로딩이 포함됩니다. 이 튜토리얼은 NoSQL 데이터베이스로의 원활하고 성공적인 전환을 위한 실질적인 예제와 실행 가능한 조언을 제공합니다.

SQL 관계형 데이터를 MongoDB로 마이그레이션하는 단계별 가이드

SQL 관계형 데이터를 MongoDB로 마이그레이션하는 것은 단순한 테이블 복사가 아닙니다. 어려운 부분은 어떤 관계를 임베디드 문서로 만들고, 어떤 관계를 참조로 유지할지, 그리고 애플리케이션이 새로운 형태를 어떻게 쿼리할지 결정하는 것입니다.

관계형 스키마는 종종 여러 테이블에 걸쳐 정규화됩니다. MongoDB의 문서 모델은 함께 읽히는 관련 데이터를 함께 저장할 때 가장 잘 작동하는 경우가 많습니다. 이 가이드는 모든 SQL 테이블이 하나의 MongoDB 컬렉션이 되어야 한다고 가정하지 않고 계획, 변환, 로드, 검증 및 애플리케이션 변경 사항을 안내합니다.

핵심 차이점 이해: 관계형 모델 vs 문서 모델

마이그레이션 프로세스를 시작하기 전에 개념적 차이점을 이해하는 것이 필수적입니다.

  • 관계형 모델: 데이터는 미리 정의된 스키마를 가진 테이블에 저장됩니다. 관계는 외래 키를 통해 관리되며, 관련 데이터를 검색하려면 JOIN 연산이 필요합니다. 정규화는 핵심 원칙입니다.
  • 문서 모델 (MongoDB): 데이터는 유연한 JSON과 유사한 문서에 저장됩니다. 문서는 다양한 구조를 가질 수 있습니다. 관련 데이터는 단일 문서 내에 임베딩(비정규화)되거나 애플리케이션 수준 조인 또는 MongoDB의 $lookup 집계 단계를 사용하여 참조될 수 있습니다.

이러한 데이터 모델링의 차이는 MongoDB 컬렉션과 문서를 설계하는 방식에 직접적인 영향을 미칩니다.

1단계: 계획 및 스키마 설계

이 단계는 가장 중요합니다. 잘 설계된 MongoDB 스키마는 MongoDB의 이점을 활용하는 데 핵심입니다. 목표는 SQL 테이블을 단순히 직접 변환하는 것이 아니라 애플리케이션 액세스 패턴을 기반으로 데이터를 모델링하는 것입니다.

1. 애플리케이션의 액세스 패턴 분석

  • 읽기 중심 vs 쓰기 중심 작업 식별: 데이터가 얼마나 자주 읽히며, 일반적으로 어떻게 쿼리됩니까? 어떤 필드가 가장 자주 함께 검색됩니까?
  • 일반적인 쿼리 경로 결정: SQL 애플리케이션에서 가장 빈번한 SELECT 문은 무엇입니까? 일반적으로 어떤 테이블이 조인됩니까?
  • 데이터 관계 이해: 엔터티는 어떻게 관련되어 있습니까? 일대일, 일대다 또는 다대다 관계입니까?

2. 비정규화 전략 선택

MongoDB의 강점은 관련 데이터를 임베딩하는 능력에 있습니다. 다음 전략을 고려하십시오.

  • 임베딩 (비정규화): 가장 일반적인 접근 방식입니다. 관계가 일대다이거나 데이터가 자주 함께 액세스되는 경우 상위 문서 내에 문서 또는 문서 배열을 임베딩합니다. 이렇게 하면 조인 필요성이 줄어듭니다.
    • 예시: 별도의 ordersorder_items 테이블을 사용하는 대신 order 문서 내에 order_items를 배열로 임베딩할 수 있습니다.
  • 참조: 임베딩으로 인해 문서가 지나치게 커지거나 데이터가 독립적으로 액세스되는 경우 사용합니다. 외래 키와 유사하게 관련 문서의 _id를 저장하고 애플리케이션 수준 조인을 수행하거나 MongoDB의 $lookup을 사용합니다.
    • 예시: users 컬렉션과 posts 컬렉션. 게시물은 작성자의 user_id를 저장할 수 있습니다. 그런 다음 $lookup을 사용하여 게시물을 가져올 때 작성자의 세부 정보를 검색할 수 있습니다.

3. MongoDB 컬렉션 및 문서 설계

액세스 패턴과 비정규화 전략을 기반으로 컬렉션을 설계합니다. 좋은 출발점은 SQL 테이블을 MongoDB 컬렉션에 매핑하는 것입니다. 그런 다음 어떤 관련 데이터를 임베딩하고 어떤 것을 참조할지 결정합니다.

SQL 스키마 예시:

-- Customers 테이블
CREATE TABLE Customers (
    CustomerID INT PRIMARY KEY,
    FirstName VARCHAR(50),
    LastName VARCHAR(50),
    Email VARCHAR(100)
);

-- Orders 테이블
CREATE TABLE Orders (
    OrderID INT PRIMARY KEY,
    CustomerID INT,
    OrderDate DATE,
    TotalAmount DECIMAL(10, 2),
    FOREIGN KEY (CustomerID) REFERENCES Customers(CustomerID)
);

-- OrderItems 테이블
CREATE TABLE OrderItems (
    OrderItemID INT PRIMARY KEY,
    OrderID INT,
    ProductID INT,
    Quantity INT,
    Price DECIMAL(10, 2),
    FOREIGN KEY (OrderID) REFERENCES Orders(OrderID)
);

MongoDB 문서 설계 옵션:

  • 옵션 A: 임베디드 주문이 있는 고객, 고객이 관리 가능한 수의 주문을 가지고 있고 주문이 고객과 함께 자주 조회되는 경우:

    {
      "_id": ObjectId("..."),
      "customer_id": 1,
      "first_name": "John",
      "last_name": "Doe",
      "email": "[email protected]",
      "orders": [
        {
          "order_id": 101,
          "order_date": ISODate("2023-10-26T00:00:00Z"),
          "total_amount": 50.00,
          "items": [
            { "product_id": 1, "quantity": 2, "price": 25.00 },
            { "product_id": 3, "quantity": 1, "price": 0.00 }
          ]
        }
      ]
    }
    
  • 옵션 B: 참조가 있는 별도 컬렉션, 주문이 많거나 독립적으로 자주 쿼리되는 경우.

    Customers 컬렉션:

    {
      "_id": ObjectId("..."),
      "customer_id": 1,
      "first_name": "John",
      "last_name": "Doe",
      "email": "[email protected]"
    }
    

    Orders 컬렉션:

    {
      "_id": ObjectId("..."),
      "order_id": 101,
      "customer_id": 1,
      "order_date": ISODate("2023-10-26T00:00:00Z"),
      "total_amount": 50.00,
      "items": [
        { "product_id": 1, "quantity": 2, "price": 25.00 },
        { "product_id": 3, "quantity": 1, "price": 0.00 }
      ]
    }
    

문서 크기 고려 사항: MongoDB에는 문서 크기 제한(16MB)이 있습니다. 이 제한을 초과할 수 있는 과도하게 큰 배열을 임베딩하지 않도록 하십시오. 배열이 무한정 커지면 별도의 컬렉션으로 분할하는 것을 고려하십시오.

2단계: 데이터 추출 및 변환

대상 스키마가 설계되면 SQL 데이터베이스에서 데이터를 추출하고 새 문서 형식으로 변환해야 합니다.

1. SQL에서 데이터 추출

표준 SQL 쿼리를 사용하여 필요한 데이터를 선택합니다. 이 데이터를 CSV 또는 JSON과 같은 형식으로 내보낼 수 있습니다.

  • SQL 클라이언트 사용: 대부분의 SQL 데이터베이스 도구(예: DBeaver, SQL Developer, pgAdmin)를 사용하면 쿼리 결과를 CSV 또는 JSON으로 내보낼 수 있습니다.
  • 스크립팅: 스크립트(Python, Node.js 등)를 작성하여 SQL 데이터베이스에 연결하고 쿼리를 실행하며 데이터를 가져옵니다.

2. 데이터 변환

이 단계에서 설계된 스키마를 구현합니다. 코드를 작성하거나 도구를 사용하여 다음을 수행해야 합니다.

  • 관련 레코드 그룹화: 예를 들어 특정 Order에 속하는 모든 OrderItems를 수집합니다.
  • 데이터 재구성: 관계형 행을 중첩된 JSON 문서로 변환합니다.
  • 데이터 유형 처리: 데이터 유형이 MongoDB와 호환되는지 확인합니다(예: 날짜, 숫자, 문자열).

Python 예시:

Customers, OrdersOrderItems를 CSV 파일로 내보냈다고 가정합니다.

import pandas as pd
import json
from bson import ObjectId

# CSV 파일에서 데이터 로드 (동일한 디렉토리에 있다고 가정)
customers_df = pd.read_csv('customers.csv')
orders_df = pd.read_csv('orders.csv')
order_items_df = pd.read_csv('order_items.csv')

# --- 데이터 변환 로직 ---

# 더 쉬운 조작을 위해 DataFrame을 딕셔너리로 변환
customers_list = customers_df.to_dict('records')
orders_list = orders_df.to_dict('records')
order_items_list = order_items_df.to_dict('records')

# 빠른 조회를 위해 주문 및 주문 항목 매핑 생성
orders_by_customer = {}
for order in orders_list:
    customer_id = order['CustomerID']
    if customer_id not in orders_by_customer:
        orders_by_customer[customer_id] = []
    orders_by_customer[customer_id].append(order)

order_items_by_order = {}
for item in order_items_list:
    order_id = item['OrderID']
    if order_id not in order_items_by_order:
        order_items_by_order[order_id] = []
    order_items_by_order[order_id].append(item)

# --- MongoDB 문서 구축 (옵션 A: 임베디드 주문이 있는 고객) ---
mongo_documents = []

for customer in customers_list:
    mongo_doc = {
        "_id": ObjectId(), # MongoDB가 자동으로 _id를 생성하지만 필요한 경우 매핑 가능
        "customer_id": customer['CustomerID'],
        "first_name": customer['FirstName'],
        "last_name": customer['LastName'],
        "email": customer['Email'],
        "orders": []
    }

    customer_id = customer['CustomerID']
    if customer_id in orders_by_customer:
        for order in orders_by_customer[customer_id]:
            order_doc = {
                "order_id": order['OrderID'],
                "order_date": order['OrderDate'], # 올바른 날짜 형식 확인
                "total_amount": order['TotalAmount'],
                "items": []
            }

            order_id = order['OrderID']
            if order_id in order_items_by_order:
                for item in order_items_by_order[order_id]:
                    order_doc['items'].append({
                        "product_id": item['ProductID'],
                        "quantity": item['Quantity'],
                        "price": item['Price']
                    })
            mongo_doc['orders'].append(order_doc)

    mongo_documents.append(mongo_doc)

# 이제 'mongo_documents'는 MongoDB에 삽입할 준비가 된 딕셔너리 목록입니다.
# print(json.dumps(mongo_documents[0], indent=2, default=str)) # 첫 번째 문서를 JSON으로 출력

# 옵션 B (별도 컬렉션)의 경우 각 컬렉션에 대한 목록을 만듭니다:
# customers_mongo = [{'customer_id': c['CustomerID'], ...} for c in customers_list]
# orders_mongo = [{'order_id': o['OrderID'], 'customer_id': o['CustomerID'], ...} for o in orders_list]

# 가져오기를 위해 JSON으로 저장 (선택 사항)
# with open('mongo_customer_data.json', 'w') as f:
#     json.dump(mongo_documents, f, indent=2, default=str)

3. 변환 도구

  • 사용자 정의 스크립트: Python with Pandas, Node.js with libraries like csv-parser and mysql/pg는 복잡한 변환에 강력합니다.
  • ETL 도구: Apache NiFi, Talend 또는 AWS Glue와 같은 도구는 SQL에서 MongoDB로의 마이그레이션을 포함한 복잡한 데이터 파이프라인을 오케스트레이션할 수 있습니다.
  • 데이터베이스 마이그레이션 플랫폼: 일부 상용 ETL 및 CDC 도구는 관계형 소스를 MongoDB로 동기화할 수 있습니다. 도구를 계획하기 전에 정확한 SQL 데이터베이스 및 MongoDB 대상에 대한 커넥터 지원을 확인하십시오.

3단계: MongoDB로 데이터 로드

데이터가 변환되면 MongoDB 인스턴스에 로드할 수 있습니다.

1. MongoDB에 연결

MongoDB Shell (mongosh) 또는 MongoDB 드라이버(프로그래밍 언어용)를 사용하여 데이터베이스에 연결합니다.

2. 변환된 데이터 가져오기

  • mongoimport 사용: 변환된 데이터를 JSON 파일로 내보낸 경우 mongoimport를 사용할 수 있습니다.

    # 데이터가 mongo_customer_data.json에 있고 'customers' 컬렉션으로 가져오려는 경우
    mongoimport --db your_database_name --collection customers --file mongo_customer_data.json --jsonArray
    
    • --jsonArray: JSON 파일에 문서 배열이 포함된 경우 이 플래그를 사용합니다.
  • MongoDB 드라이버 사용: 프로그래밍 언어(예: Python의 mongo_documents 목록)에서 데이터 구조를 생성한 경우 직접 삽입할 수 있습니다.

    pymongo를 사용한 Python 예시:

    from pymongo import MongoClient
    
    # 'mongo_documents' 목록이 이전 Python 스크립트에서 정의되었다고 가정
    client = MongoClient('mongodb://localhost:27017/')
    db = client['your_database_name']
    customers_collection = db['customers']
    
    # 변환된 문서 삽입
    if mongo_documents:
        insert_result = customers_collection.insert_many(mongo_documents)
        print(f"{len(insert_result.inserted_ids)}개의 문서가 삽입되었습니다.")
    else:
        print("삽입할 문서가 없습니다.")
    
    client.close()
    

3. 데이터 무결성 확인

로드 후 MongoDB에서 쿼리를 실행하여 데이터가 올바르게 가져와졌고 예상과 일치하는지 확인합니다.

// 예시: 'customers' 컬렉션의 문서 수 계산
use your_database_name;
print(db.customers.countDocuments());

// 예시: 특정 고객을 찾고 임베디드 주문 확인
db.customers.findOne({ "customer_id": 1 })

4단계: 애플리케이션 리팩토링

이 단계는 아마도 가장 시간이 많이 걸리는 단계일 것입니다. 애플리케이션 코드는 SQL 대신 MongoDB와 상호 작용하도록 업데이트되어야 합니다.

  • 데이터베이스 연결 업데이트: 연결 문자열과 라이브러리를 변경합니다.
  • 쿼리 다시 작성: SQL 쿼리를 선택한 드라이버의 API를 사용하는 MongoDB 쿼리 언어로 대체합니다.
  • 데이터 액세스 계층 조정: ORM 또는 데이터 액세스 계층을 MongoDB 문서와 함께 작동하도록 수정합니다.
  • MongoDB 기능 활용: 해당되는 경우 유연한 스키마, 집계 프레임워크 및 지리 공간 쿼리와 같은 기능을 활용하도록 애플리케이션을 조정합니다.

모범 사례 및 팁

  • 작게 시작: 가능하면 먼저 데이터의 하위 집합이나 덜 중요한 애플리케이션을 마이그레이션하여 경험을 쌓으십시오.
  • 스키마 설계 반복: 초기 MongoDB 스키마가 완벽하지 않을 수 있습니다. 성능 테스트 및 애플리케이션 피드백을 기반으로 반복하고 개선할 준비를 하십시오.
  • 현명하게 인덱싱: SQL과 마찬가지로 MongoDB에서도 인덱싱은 성능에 중요합니다. 쿼리 패턴을 식별하고 적절한 인덱스를 생성하십시오.
  • 성능 모니터링: MongoDB 배포에서 성능 병목 현상을 지속적으로 모니터링하고 필요에 따라 쿼리와 스키마를 최적화하십시오.
  • 증분 마이그레이션 고려: 대규모 데이터베이스의 경우 최종 전환 전에 SQL에서 MongoDB로 변경 사항을 거의 실시간으로 동기화하는 증분 마이그레이션 전략을 고려하십시오.

핵심 내용

가장 안전한 SQL-to-MongoDB 마이그레이션은 테이블 이름이 아닌 액세스 패턴에서 시작됩니다. 하나의 중요한 워크플로를 모델링하고, 작은 데이터 조각을 변환하고, MongoDB에 로드하고, 개수와 샘플 문서를 확인한 다음, 마이그레이션을 확장하기 전에 해당 형태에 맞게 애플리케이션 코드를 업데이트하십시오.