몇 년간 MongoDB를 사용하면서 만나게 되는 골치 아픈 친구 중 하나가 바로 OperationTimeout 에러입니다. 갑자기 애플리케이션이 멈춘 듯 동작하다가 “operation exceeded time limit” 메시지를 뱉어내는 순간, 개발자들의 혈압은 순식간에 올라가죠.

하지만 걱정할 필요 없습니다. 이 에러의 정체를 파악하고 체계적으로 접근하면 충분히 해결할 수 있는 문제입니다. 오늘은 MongoDB의 OperationTimeout 에러가 발생하는 근본 원인부터 실무에서 바로 적용할 수 있는 해결책까지 자세히 알아보겠습니다.

 

1. OperationTimeout 에러란 무엇인가?

MongoDB에서 OperationTimeout은 말 그대로 작업이 지정된 시간 내에 완료되지 못했을 때 발생하는 에러입니다. 개발자들이 흔히 혼동하는 부분이 바로 Connection Timeout과 Operation Timeout의 차이인데, Connection Timeout은 데이터베이스에 연결하기까지 기다리는 최대 시간이고, Operation Timeout은 이미 연결된 상태에서 특정 작업(CRUD)이 수행되기까지 기다리는 최대 시간을 의미합니다.

실제로 에러 메시지는 다음과 같은 형태로 나타납니다:

MongoExecutionTimeoutException: Operation exceeded time limit
Timed out after 30000 ms while waiting for a server
Request timed out. Retries due to rate limiting: True

이런 에러들이 발생하는 이유를 이해하려면 먼저 MongoDB의 타임아웃 메커니즘을 알아야 합니다.

 

 

2. MongoDB의 다양한 타임아웃 유형 이해하기

MongoDB 드라이버는 여러 가지 네트워크 타임아웃 옵션을 제공하며, 기본값들이 사용 사례에 맞지 않을 수 있어 이해가 중요합니다. 주로 세 가지 종류가 있습니다: Server Selection Timeout, Connection Timeout, Socket Timeout입니다.

2.1 Server Selection Timeout (serverSelectionTimeoutMS)

기본값: 30초

적절한 서버를 찾는 데 허용되는 시간입니다. 복제 세트에서 Primary 서버를 찾거나 샤드된 환경에서 적절한 샤드를 선택하는 시간이 포함됩니다.

2.2 Connection Timeout (connectTimeoutMS)

기본값: 10초

새로운 연결을 설정하는 데 걸리는 시간입니다. 네트워크 지연이나 서버 부하로 인해 연결이 늦어질 때 발생합니다.

2.3 Socket Timeout (socketTimeoutMS)

기본값: 0 (무제한)

연결이 설정된 후 데이터 송수신에 허용되는 시간입니다. 많은 개발자들이 장시간 실행되는 서버 작업을 방지하기 위해 낮은 socketTimeoutMS 값을 설정하지만, 대부분의 경우 maxTimeMS가 더 나은 선택입니다.

2.4 Operation Timeout (maxTimeMS)

MongoDB 2.6부터 지원

개별 작업(쿼리, 집계 등)의 최대 실행 시간을 설정합니다. 네트워크 지연이나 커서의 유휴 시간은 이 시간에 포함되지 않으며, 순수한 처리 시간만 계산됩니다.

 

 

3. OperationTimeout 에러의 주요 원인들

3.1 네트워크 관련 문제

방화벽 설정 문제 MongoDB의 기본 포트인 27017에 대한 접근이 차단되면 클라이언트가 데이터베이스에 연결할 수 없어 타임아웃이 발생합니다. 방화벽 블록은 잠긴 문과 같아서 클라이언트의 진입을 거부하고 타임아웃을 누적시킵니다.

DNS 해석 문제 잘못 구성된 DNS 리졸버나 오래된 IP 주소로 인해 연결 지연이 발생할 수 있습니다.

3.2 서버 부하 및 리소스 부족

CPU/메모리 부족 서버 리소스가 부족하면 쿼리 처리 속도가 현저히 느려집니다.

동시 연결 수 초과 연결 풀 크기를 너무 크게 설정하면 ECONNRESET 에러가 발생할 수 있으며, 이는 Node.js 프로세스의 파일 디스크립터 제한에 걸렸을 가능성이 높습니다.

3.3 쿼리 최적화 부족

인덱스 부재 적절한 인덱스가 없으면 Full Collection Scan이 발생하여 작업 시간이 급격히 증가합니다.

복잡한 집계 파이프라인 최적화되지 않은 집계 쿼리는 메모리와 CPU를 과도하게 사용합니다.

3.4 클라우드 환경 특성

Azure Cosmos DB의 RU 제한 Azure Cosmos DB에서는 Request Units(RU) 제한으로 인한 rate limiting이 발생할 수 있으며, 이는 “Request timed out. Retries due to rate limiting: True” 에러를 유발합니다.

 

 

4. 개발 언어별 해결 방법

4.1 Node.js 환경에서의 해결책

연결 문자열 설정

const { MongoClient } = require('mongodb');

const uri = 'mongodb://localhost:27017/mydb?socketTimeoutMS=90000&connectTimeoutMS=10000&serverSelectionTimeoutMS=5000';

const options = {
  maxPoolSize: 10,
  socketTimeoutMS: 90000,
  connectTimeoutMS: 10000,
  serverSelectionTimeoutMS: 5000
};

MongoClient.connect(uri, options, (err, client) => {
  if (err) {
    console.error('연결 실패:', err);
    return;
  }
  console.log('MongoDB 연결 성공');
});

개별 쿼리에 타임아웃 설정

// maxTimeMS를 사용한 쿼리 타임아웃 설정
collection.find({ name: "홍길동" })
  .maxTimeMS(30000)  // 30초 타임아웃
  .toArray((err, docs) => {
    if (err) {
      console.error('쿼리 타임아웃:', err);
      return;
    }
    console.log(docs);
  });

4.2 Java 환경에서의 해결책

import com.mongodb.MongoClient;
import com.mongodb.MongoClientOptions;
import com.mongodb.ServerAddress;

MongoClientOptions options = MongoClientOptions.builder()
    .connectTimeout(10000)        // 10초 연결 타임아웃
    .socketTimeout(90000)         // 90초 소켓 타임아웃
    .serverSelectionTimeout(5000) // 5초 서버 선택 타임아웃
    .maxConnectionPoolSize(20)    // 최대 연결 풀 크기
    .build();

MongoClient mongoClient = new MongoClient(
    new ServerAddress("localhost", 27017), 
    options
);

개별 작업에 타임아웃 적용

// CountOptions를 사용한 카운트 작업 타임아웃
CountOptions countOptions = new CountOptions()
    .maxTime(30, TimeUnit.SECONDS);

long count = collection.countDocuments(filter, countOptions);

4.3 Python (PyMongo) 환경에서의 해결책

from pymongo import MongoClient
import pymongo

# 연결 옵션 설정
client = MongoClient(
    'mongodb://localhost:27017/',
    connectTimeoutMS=10000,
    socketTimeoutMS=90000,
    serverSelectionTimeoutMS=5000,
    maxPoolSize=20
)

# timeoutMS 옵션 사용
client = MongoClient(
    'mongodb://localhost:27017/',
    timeoutMS=30000  # 30초 전체 작업 타임아웃
)

컨텍스트 매니저를 사용한 타임아웃 설정

# timeout() 메서드를 사용한 개별 작업 타임아웃
with pymongo.timeout(10):  # 10초 타임아웃
    result = collection.find_one({'name': '홍길동'})

 

 

5. 최적화 및 모니터링 방법

5.1 쿼리 성능 최적화

인덱스 생성 전략

// 복합 인덱스 생성 예시
db.users.createIndex({ "email": 1, "status": 1 });

// 쿼리 실행 계획 확인
db.users.find({ email: "user@example.com" }).explain("executionStats");

집계 파이프라인 최적화

// 비효율적인 파이프라인
db.orders.aggregate([
  { $match: { status: "completed" }},
  { $sort: { date: -1 }},
  { $limit: 100 }
]);

// 최적화된 파이프라인 (인덱스 활용)
db.orders.aggregate([
  { $match: { status: "completed" }},
  { $sort: { date: -1 }},
  { $limit: 100 }
]).allowDiskUse(true);

5.2 연결 풀 최적화

적절한 풀 크기 설정

const options = {
  maxPoolSize: 10,  // 동시 연결 수
  minPoolSize: 2,   // 최소 연결 유지
  maxIdleTimeMS: 30000,  // 유휴 연결 제거 시간
  waitQueueTimeoutMS: 5000  // 연결 대기 시간
};

5.3 모니터링 및 진단

MongoDB 프로파일링 활성화

// 느린 쿼리 프로파일링 (100ms 이상)
db.setProfilingLevel(1, { slowms: 100 });

// 프로파일링 결과 확인
db.system.profile.find().sort({ ts: -1 }).limit(10);

 

 

6. 클라우드 환경별 특별 고려사항

6.1 MongoDB Atlas 설정

Atlas에서는 네트워크 접근 설정이 중요합니다:

// Atlas 연결 문자열 예시
const uri = "mongodb+srv://username:password@cluster0.abc123.mongodb.net/mydb?retryWrites=true&w=majority&socketTimeoutMS=90000";

6.2 Azure Cosmos DB 최적화

RU(Request Unit) 부족으로 인한 타임아웃을 방지하려면 RU를 증가시키거나 Server Side Retry(SSR) 기능을 활성화할 수 있습니다:

// Cosmos DB 연결 옵션
const options = {
  maxTimeMS: 30000,
  retryWrites: true,
  retryReads: true
};

 

 

7. 실무 적용을 위한 단계별 체크리스트

7.1 즉시 적용 가능한 해결책

  1. 타임아웃 값 조정
    • socketTimeoutMS: 60000-120000 (60-120초)
    • connectTimeoutMS: 10000 (10초)
    • serverSelectionTimeoutMS: 5000 (5초)
  2. 연결 풀 최적화
    • maxPoolSize: 10-50 (애플리케이션 규모에 따라)
    • minPoolSize: 2-5
  3. 쿼리별 타임아웃 설정
    • 일반 쿼리: 30초
    • 리포트성 쿼리: 60-300초

7.2 중장기 최적화 방안

  1. 인덱스 전략 수립
    • 자주 사용되는 쿼리 패턴 분석
    • 복합 인덱스 최적화
    • 인덱스 힌트 활용
  2. 하드웨어 리소스 모니터링
    • CPU, 메모리 사용률 추적
    • 디스크 I/O 병목 지점 파악
    • 네트워크 지연 시간 측정

 

 

8. 자주 발생하는 실수와 해결 방법

8.1 흔한 실수들

socketTimeoutMS를 너무 낮게 설정 많은 개발자가 장시간 실행 작업을 방지하려고 socketTimeoutMS를 낮게 설정하지만, maxTimeMS가 더 적절한 선택입니다.

연결 풀 크기 과대 설정 필요 이상으로 큰 연결 풀은 오히려 성능을 저하시킬 수 있습니다.

8.2 올바른 접근법

// 추천 설정
const goodOptions = {
  socketTimeoutMS: 60000,    // 충분한 시간 확보
  connectTimeoutMS: 10000,   // 적절한 연결 시간
  maxPoolSize: 20,           // 적정 풀 크기
  serverSelectionTimeoutMS: 5000  // 빠른 서버 선택
};

// 개별 쿼리에는 maxTimeMS 사용
collection.find(query).maxTimeMS(30000);

 

 

MongoDB의 OperationTimeout 에러는 복잡해 보이지만, 체계적으로 접근하면 충분히 해결할 수 있는 문제입니다. 가장 중요한 것은 에러의 근본 원인을 파악하는 것입니다. 네트워크 문제인지, 쿼리 최적화 문제인지, 아니면 리소스 부족 문제인지 정확히 진단한 후 적절한 해결책을 적용해야 합니다.

실무에서는 모니터링 도구를 활용해 지속적으로 성능을 추적하고, 사용자 경험에 영향을 주기 전에 미리 대응하는 것이 중요합니다. 위에서 제시한 해결책들을 단계적으로 적용하면서 여러분의 MongoDB 환경을 더욱 안정적으로 만들어 보시기 바랍니다.

 

댓글 남기기