MongoDB Lock Mechanisms and Implementation Details
MongoDB’s locking system uses a hierarchical resource model with four lock modes, RAII‑style acquisition classes, ticket‑based throttling, and a bucketed lock manager that employs bit‑mask conflict checks and priority flags to grant exclusive locks promptly and prevent starvation.
MongoDB is a leading document‑oriented database, and its slow‑query log is often used to diagnose performance problems. The article starts by introducing the slow log concept and shows how to enable the profiler to capture slow operations.
Example commands to view and set the profiler:
# 查看Databaseprofiler配置
db.getProfilingStatus()
# 设置Databaseprofiler用于采集慢请求
db.setProfilingLevel(
,
)MongoDB classifies lock resources into hierarchical types. Before version 4.4 the enum looks like:
enum ResourceType {
RESOURCE_INVALID = 0,
RESOURCE_PBWM,
RESOURCE_RSTL,
RESOURCE_GLOBAL,
RESOURCE_DATABASE,
RESOURCE_COLLECTION,
RESOURCE_METADATA,
RESOURCE_MUTEX,
ResourceTypesCount
};From version 4.4 onward the classification is simplified:
enum ResourceType {
RESOURCE_INVALID = 0,
RESOURCE_GLOBAL,
RESOURCE_DATABASE,
RESOURCE_COLLECTION,
RESOURCE_METADATA,
RESOURCE_MUTEX,
ResourceTypesCount
};MongoDB defines four lock modes (intent shared, intent exclusive, shared, exclusive):
enum LockMode {
MODE_NONE = 0,
MODE_IS = 1, // intent shared (r)
MODE_IX = 2, // intent exclusive (w)
MODE_S = 3, // shared (R)
MODE_X = 4, // exclusive (W)
LockModesCount
};The lock matrix determines compatibility between requested and already granted modes:
/*
* MongoDB lock matrix
* | Requested Mode | MODE_NONE | MODE_IS | MODE_IX | MODE_S | MODE_X |
* |----------------|-----------|---------|---------|--------|--------|
* | MODE_IS | + | + | + | + | |
* | MODE_IX | + | + | + | | |
* | MODE_S | + | + | | + | |
* | MODE_X | + | | | | |
*/Lock acquisition is performed through RAII‑style helper classes. AutoGetCollection first obtains a global or replica‑set lock, then a database lock, and finally the collection lock:
// catalog_raii.h
class AutoGetCollection {
// ...
boost::optional
_autoDb;
std::vector
_collLocks;
};
// catalog_raii.cpp
AutoGetCollection::AutoGetCollection(...) {
_autoDb.emplace(opCtx, dbName, isSharedLockMode(modeColl) ? MODE_IS : MODE_IX, deadline, secondaryDbNames);
_collLocks.emplace_back(opCtx, nsOrUUID, modeColl, deadline);
}The corresponding database lock ( AutoGetDb ) creates a GlobalLock before acquiring the DB lock:
// d_concurrency.h
class DBLock {
public:
DBLock(OperationContext* opCtx, StringData db, LockMode mode,
Date_t deadline = Date_t::max(), bool skipGlobalAndRSTLLocks = false);
private:
const ResourceId _id;
OperationContext* _opCtx;
boost::optional
_globalLock;
};
// concurrency.cpp
Lock::DBLock::DBLock(...)
: _id(RESOURCE_DATABASE, db), _opCtx(opCtx) {
if (!skipGlobalAndRSTLLocks) {
_globalLock.emplace(opCtx, isSharedLockMode(_mode) ? MODE_IS : MODE_IX, deadline, InterruptBehavior::kThrow);
}
_opCtx->lockState()->lock(_opCtx, _id, _mode, deadline);
}The global lock itself may acquire only the global lock or both the global and replication‑state‑transition lock:
// d_concurrency.h
class GlobalLock {
public:
GlobalLock(OperationContext* opCtx, LockMode lockMode,
Date_t deadline = Date_t::max(),
InterruptBehavior behavior = InterruptBehavior::kThrow,
bool skipRSTLLock = false);
private:
ResourceLock _pbwm;
ResourceLock _fcvLock;
// ...
};
// lock_state.cpp
void GlobalLock::_takeGlobalLockOnly(LockMode lockMode, Date_t deadline) {
_opCtx->lockState()->lockGlobal(_opCtx, lockMode, deadline);
}
void GlobalLock::_takeGlobalAndRSTLLocks(LockMode lockMode, Date_t deadline) {
_opCtx->lockState()->lock(_opCtx, resourceIdReplicationStateTransitionLock, MODE_IX, deadline);
_opCtx->lockState()->lockGlobal(_opCtx, lockMode, deadline);
}Before a global lock is taken, MongoDB acquires a ticket to limit concurrency. Ticket holders are created at startup (default 128 tickets for reads and writes) and can be either semaphore‑based or FIFO‑based:
// ticketHolders.h
class TicketHolders {
public:
void setGlobalThrottling(std::unique_ptr
reading,
std::unique_ptr
writing);
TicketHolder* getTicketHolder(LockMode mode);
private:
std::unique_ptr
_openReadTransaction;
std::unique_ptr
_openWriteTransaction;
};
// ticketHolders.cpp
TicketHolder* TicketHolders::getTicketHolder(LockMode mode) {
switch (mode) {
case MODE_S:
case MODE_IS: return _openReadTransaction.get();
case MODE_IX: return _openWriteTransaction.get();
default: return nullptr;
}
}Lock acquisition is delegated to LockerImpl , which first obtains a ticket and then calls _lockBegin and, if necessary, _lockComplete to wait for the lock:
// lock_state.cpp
void LockerImpl::lockGlobal(OperationContext* opCtx, LockMode mode, Date_t deadline) {
if (_modeForTicket == MODE_NONE) {
_acquireTicket(opCtx, mode, deadline);
_modeForTicket = mode;
}
LockResult result = _lockBegin(opCtx, resourceIdGlobal, mode);
if (result == LOCK_WAITING) {
_lockComplete(opCtx, resourceIdGlobal, mode, deadline);
}
}
LockResult LockerImpl::_lockBegin(OperationContext* opCtx, ResourceId resId, LockMode mode) {
// Record statistics, set priority for GLOBAL S/X, then call lock manager
return getGlobalLockManager()->lock(resId, request, mode);
}
void LockerImpl::_lockComplete(OperationContext* opCtx, ResourceId resId, LockMode mode, Date_t deadline) {
while (true) {
LockResult result = _notify.wait(opCtx, waitTime);
if (result == LOCK_OK) break;
// handle timeout, interruption, etc.
}
}The lock manager stores locks in a hash of 128 buckets to reduce contention. Each bucket maps a ResourceId to a LockHead , which maintains a granted list and a conflict (waiting) list. Bit‑mask counters ( grantedModes , conflictModes ) allow O(1) conflict checks:
// lock_manager.h
struct LockHead {
LockRequestList grantedList;
uint32_t grantedModes;
LockRequestList conflictList;
uint32_t conflictModes;
// ...
LockResult newRequest(LockRequest* request) {
if (conflicts(request->mode, grantedModes) ||
(!compatibleFirstCount && conflicts(request->mode, conflictModes))) {
// enqueueAtFront decides front or back of conflict list
if (request->enqueueAtFront) conflictList.push_front(request);
else conflictList.push_back(request);
incConflictModeCount(request->mode);
return LOCK_WAITING;
}
// grant immediately
grantedList.push_back(request);
incGrantedModeCount(request->mode);
if (request->compatibleFirst) compatibleFirstCount++;
return LOCK_OK;
}
};
// lock_manager.cpp
LockResult LockManager::lock(ResourceId resId, LockRequest* request, LockMode mode) {
LockBucket* bucket = _getBucket(resId);
std::lock_guard
lk(bucket->mutex);
LockHead* lock = bucket->findOrInsert(resId);
return lock->newRequest(request);
}
LockBucket* LockManager::_getBucket(ResourceId resId) const {
return &_lockBuckets[resId % _numLockBuckets];
}To avoid starvation of exclusive locks, MongoDB assigns higher priority to global S and X locks by setting enqueueAtFront = true and compatibleFirst = true . This forces exclusive requests to the front of the conflict queue and allows them to be granted as soon as the current granted modes are released, preventing endless waiting caused by a flood of shared requests.
In summary, the article walks through MongoDB’s lock hierarchy, the lock matrix, the RAII acquisition path, the ticket‑based throttling mechanism, the lock manager’s bucketed data structures, and the priority flags that protect exclusive operations from starvation.
Tencent Cloud Developer
Official Tencent Cloud community account that brings together developers, shares practical tech insights, and fosters an influential tech exchange community.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.