database
MongoDB에서는 문서(Document) 단위로 락이 걸린다고 알려져 있지만, 실제로는 컬렉션·데이터베이스 수준 등 다양한 단계(Granularity)에서 락이 발생할 수 있습니다. 이번 글에서는 문서·컬렉션·인덱스 등 어디에 어떻게 락이 걸리는지 살펴보고, MongoDB 내부에서 사용하는 Intent Lock, Shared Lock, Exclusive Lock 개념을 설명합니다. 또한 락 경합(Contestion)이 생기는 상황과 이를 모니터링하기 위한 명령어(db.currentOp(), profiler 등)에 대해서도 구체적으로 알아보겠습니다.
MongoDB에서 Lock의 범위는 크게 3가지 입니다. 문서 단위의 락(Document-Level Lock), 컬렉션 단위의 락(Collection-Level Lock), 데이터베이스 단위의 락(Database-Level Lock)이 사용됩니다. MongoDB의 WiredTiger 엔진은 업데이트가 발생할 때 해당 문서(document)를 중심으로 락을 획득합니다. 덕분에 다른 문서에 대한 읽기/쓰기는 동시에 처리될 수 있어 높은 동시성을 확보할 수 있습니다. 컬렉션에 대한 구조적 변경(예: 컬렉션 생성, 삭제, 인덱스 생성/제거) 시에는 컬렉션 레벨 락이 걸립니다. 이 락은 짧은 시간 동안만 유지되도록 설계되었지만, 빈번하게 발생하면 전체 컬렉션을 대상으로 병행 처리를 방해할 수 있습니다. 데이터베이스 설정 변경, 스키마 메타데이터 변경, 일부 관리 작업 등은 DB 전체 범위에 영향을 미칠 수 있으므로, 이 과정에서 더 확장된 락이 발생하기도 합니다. 일반적인 읽기/쓰기는 DB 레벨 락을 거의 사용하지 않으나, 대규모 작업(예: DB 복제·이동) 시 발생하는 예외 케이스를 이해해둘 필요가 있습니다.
MongoDB에서 락은 단순히 “읽기 혹은 쓰기”를 위한 한 가지 형태로만 존재하는 것이 아니라, 훨씬 더 세분화되고 계층적인 구조를 가지고 있습니다. 이는 데이터베이스(DB), 컬렉션(Collection), 문서(Document) 같은 서로 다른 수준에서 동시에 발생할 수 있는 여러 연산을 효율적으로 처리하기 위한 전략이라 할 수 있습니다. 특히 MongoDB는 Intent Lock, Shared Lock, Exclusive Lock의 세 가지 락 모드를 조합해 동시성 충돌을 줄이고, 동시에 가능한 한 높은 성능을 유지하려 노력합니다.
먼저 Intent Lock은 상위 수준 객체(예: 데이터베이스)에 대해 “곧 여기서 읽기나 쓰기를 할 예정이니, 협조를 부탁한다”라는 의사를 표명하는 역할을 담당합니다. 구체적으로 IS(Intent Shared)와 IX(Intent Exclusive)가 존재해, 문서나 컬렉션에서 실제 읽기 혹은 쓰기를 시도하기 전에 상위 레벨에서 미리 해당 타입의 락을 예약해 둔다고 할 수 있습니다. 이렇게 하면 하위 수준(문서 단위)에서 락을 획득하거나 경합이 발생할 때 충돌을 줄이고, 전체 시스템 성능을 한층 높일 수 있습니다.
Shared Lock(S 락)은 다수의 읽기 연산이 동시에 진행되는 것을 허용하는, 일종의 “협력적” 락입니다. 여러 트랜잭션이 동일한 데이터에 대해 S 락을 획득해도 상호 충돌 없이 접근이 가능하기 때문에, 읽기 중심의 환경에서는 병렬 처리를 높이는 데 유용합니다. 단, 쓰기(Exclusive) 락과는 양립할 수 없으므로, 누군가가 S 락을 유지하고 있는 동안 다른 트랜잭션이 해당 객체에 쓰기를 시도하려면, 우선 S 락이 해제되어야만 가능합니다.
반면 Exclusive Lock(X 락)은 이름에서 느껴지듯이 배타성을 최우선으로 합니다. 한 문서 혹은 컬렉션에 X 락이 걸린 상태라면, 해당 객체에 어떠한 형태의 접근(읽기나 쓰기)도 허용되지 않습니다. 쓰기 작업이 정확히 하나의 프로세스에서만 이루어져야 하고, 도중에 다른 트랜잭션이 끼어들면 데이터 정합성 문제가 발생할 수 있기 때문입니다. 따라서 X 락이 해제되기 전까지 다른 트랜잭션들은 대기해야 하며, 이로 인해 잠금 경합(Lock Contention)이 늘어나지 않도록 잘 설계해야 합니다.
이러한 Intent Lock, Shared Lock, Exclusive Lock을 조합하면, MongoDB는 상위 객체 수준에서는 비교적 가벼운 Intent Lock을 통해 “곧 일어날 작업”을 미리 선언하고, 실제 쓰기나 읽기는 하위 수준(문서 등)에서 정확히 필요한 시점에 Shared나 Exclusive 락을 획득하여 작업을 수행합니다. 이는 여러 연결(트랜잭션)에서 발생하는 다양한 연산이 서로 간섭하지 않도록 보호하면서도, 락 범위를 최소한으로 제한해 동시성을 극대화하는 핵심 메커니즘입니다. 이러한 락 모델을 이해하면, 대규모 트래픽이 몰리는 시스템에서도 MongoDB가 어떻게 데이터를 안전히 보존하면서 빠른 응답을 제공하는지 더욱 명확히 파악할 수 있을 것입니다.
MongoDB에서 Intent Lock, Shared Lock, Exclusive Lock 등은 스토리지 엔진(WiredTiger 등)이 내부적으로 자동 관리하는 개념입니다. 사용자가 직접 “LOCK TABLE” 같은 명령어를 통해 수동으로 락을 걸거나 해제하지 않고, MongoDB가 알아서 동시성 충돌을 제어합니다. 따라서 일반적인 애플리케이션 코딩이나 운영 관점에서는 “락을 어떻게 획득하느냐”보다는 “잠금 경합을 어떻게 줄이느냐”, “MongoDB가 제공하는 락 메커니즘을 이해하고, 쿼리나 스키마를 어떻게 설계하느냐”에 초점을 맞추시면 됩니다. 예를 들어, 인덱스를 잘 구성하거나, 단일 문서에 필요한 데이터를 모두 묶어(denormalization) 한 번에 처리하면, 실제 락 획득 시간이 줄어들어 동시성이 높아집니다. 하지만, 거꾸로 대규모 스캔 쿼리가 자주 발생하거나, 여러 문서를 동시에 갱신해야 할 때는 락이 과도하게 걸려 병목이 생길 수 있으니 주의가 필요합니다. 결론적으로, MongoDB 내부의 락 모드는 사용자가 직접 제어하는 영역이 아니라, 스토리지 엔진이 자동으로 관리하는 영역이며, 개발자나 운영자는 동시성 이슈가 발생하기 전에 미리 튜닝할 수 있도록 쿼리와 스키마 설계를 최적화하는 데 집중하면 됩니다.
MongoDB 환경에서 여러 클라이언트가 동시에 특정 자원(문서나 컬렉션 등)에 접근할 때, 요구되는 락 모드가 서로 충돌하면 Lock 경합(Contestion)이 발생합니다. 이는 일시적으로 쓰기 작업이 장시간 컬렉션 레벨 락을 점유한다거나, 대량 Batch 업데이트가 진행될 때 다른 쿼리들이 대기 상태에 놓이는 상황 등을 예로 들 수 있습니다. 이러한 락 경합이 잦아지면 쿼리 처리 지연이 눈에 띄게 늘어나고, 심한 경우 데드락(Deadlock)이 발생할 수도 있습니다.
이때 유용한 명령어가 바로 db.currentOp()입니다. 운영 중에 현재 실행되고 있는 쿼리와 그 쿼리가 획득·대기 중인 락 정보를 확인할 수 있죠.
db.currentOp({ active: true, secs_running: { $gt: 5 } })
예컨대 위와 같은 조건을 적용해, 5초 이상 실행 중인 작업이 있는지 추적하고, 필요하다면
이와 함께 Profiler를 활성화하면, 각 쿼리에 대해 실행 계획 외에도 락 대기 시간(lockStats) 등의 세부 지표를 기록할 수 있습니다. 다만 프로파일링 레벨을 높이면 그만큼 시스템 리소스 사용이 늘어나므로, 실제 운영 환경에서는 일시적으로만 활성화하거나 특정 임계값 이상의 쿼리에 대해서만 기록하도록 설정하는 것이 좋습니다. 이처럼 db.currentOp()와 Profiler 같은 모니터링 툴을 적절히 활용하면, 어느 부분에서 락 경합이 심한지 조기에 파악하고, 쿼리나 스키마를 최적화하여 시스템 병목을 완화할 수 있습니다.
MongoDB는 문서 단위의 미세 락(Document-Level Lock)부터 컬렉션·DB 수준의 제한적 락까지 다양한 계층을 갖추고 있습니다. 이를 내부적으로 Intent/Shared/Exclusive 락 모드를 조합해 운용함으로써 높은 동시성과 데이터 정합성을 동시에 추구합니다. 하지만 아무리 세분화된 락 체계를 갖추고 있어도, 운영 환경에서 대규모 업데이트나 빈번한 인덱스 변경이 발생하면 락 경합이 심해질 수 있습니다. 이때는 db.currentOp(), Profiler 등을 사용해 병목 지점을 파악하고, 쿼리 구조를 최적화하거나 스키마 설계를 개선하여 락 충돌을 줄이는 접근이 필요합니다.
다음 글에서는 MongoDB의 Multi-Document Transactions와 ACID 보장 범위에 대해 알아봅니다. 단일 문서 락을 넘어, 여러 문서에 걸쳐 원자적으로 쓰기 연산을 처리하려면 어떤 점을 주의해야 하는지, 실제 운영 시 성능 면에서 어떤 트레이드오프가 있는지 확인해 봅시다.