阿里云主机折上折
  • 微信号
Current Site:Index > Asynchronous and non-blocking operations

Asynchronous and non-blocking operations

Author:Chuan Chen 阅读数:51500人阅读 分类: MongoDB

Basic Concepts of Asynchronous and Non-blocking Operations

Asynchronous and non-blocking operations are core design patterns in modern database systems, particularly evident in high-performance NoSQL databases like MongoDB. Asynchronous operations allow programs to continue executing other tasks while waiting for I/O operations to complete, while non-blocking operations ensure threads are not suspended for extended periods. These two mechanisms work together to significantly improve system throughput and responsiveness.

In MongoDB's driver implementation, typical asynchronous operations are achieved through callback functions, Promises, or async/await syntax. For example, when executing a time-consuming query, the program does not wait idly for the result but instead registers a callback function that is automatically triggered once the query completes:

// Asynchronous query using callback style
db.collection('users').find({ age: { $gt: 30 } }, (err, docs) => {
  if (err) throw err;
  console.log(docs);
});
console.log('Query initiated, continuing with other operations');

Asynchronous I/O Model in MongoDB

MongoDB's official Node.js driver is entirely built on an event loop and non-blocking I/O. When a client initiates a query request, the driver encapsulates the request in BSON format and sends it via TCP. At this point, the operating system kernel handles the network transmission, while the Node.js event loop can continue processing other events. This design enables the single-threaded JavaScript runtime to handle thousands of concurrent connections simultaneously.

At the underlying implementation level, the MongoDB driver uses the operating system's non-blocking sockets. For example, in a Linux environment, the driver monitors socket state changes via the epoll mechanism:

const { MongoClient } = require('mongodb');
const client = new MongoClient('mongodb://localhost:27017');

async function run() {
  await client.connect();
  const collection = client.db('test').collection('users');
  const cursor = collection.find().batchSize(100); // Non-blocking batch retrieval
  
  // Asynchronously iterate through results
  for await (const doc of cursor) {
    console.log(doc);
  }
}
run().catch(console.error);

Batch Operations and Bulk Write Optimizations

MongoDB has special optimizations for batch operations. Inserting 100 documents in a batch is over 10 times faster than inserting them individually 100 times. This is due to its batch operation protocol and asynchronous batch processing mechanism:

const bulkOps = [
  { insertOne: { document: { name: 'Alice', age: 25 } } },
  { updateOne: { filter: { name: 'Bob' }, update: { $set: { age: 30 } } } },
  { deleteOne: { filter: { name: 'Charlie' } } }
];

// Ordered bulk operations (executed sequentially)
db.collection('users').bulkWrite(bulkOps, { ordered: true })
  .then(res => console.log(res.modifiedCount));

// Unordered bulk operations (executed in parallel)
db.collection('users').bulkWrite(bulkOps, { ordered: false })
  .then(res => console.log(res.insertedCount));

Real-time Processing with Change Streams

MongoDB's Change Streams feature provides an asynchronous event notification mechanism based on the publish-subscribe pattern, allowing applications to listen for data changes in real time without polling:

const pipeline = [
  { $match: { operationType: { $in: ['insert', 'update'] } } },
  { $project: { 'fullDocument.name': 1 } }
];

const changeStream = db.collection('inventory').watch(pipeline);
changeStream.on('change', next => {
  console.log('Change event:', next);
  // Business logic can be triggered here, such as updating caches or sending notifications
});

// Close the listener after 60 seconds
setTimeout(() => changeStream.close(), 60000);

Connection Pooling and Concurrency Control

The MongoDB driver maintains a connection pool to manage all TCP connections, with a default size typically ranging from 5 to 100 connections (configurable). When an application initiates a request, the driver acquires an available connection from the pool and returns it after use rather than closing it:

const client = new MongoClient(uri, {
  poolSize: 50, // Connection pool size
  connectTimeoutMS: 30000,
  socketTimeoutMS: 60000,
  waitQueueTimeoutMS: 5000 // Timeout for acquiring a connection
});

// Stress test scenario
async function stressTest() {
  const promises = [];
  for (let i = 0; i < 1000; i++) {
    promises.push(
      client.db().collection('stress').insertOne({ value: i })
    );
  }
  await Promise.all(promises); // The driver automatically manages the connection pool
}

Error Handling and Retry Mechanisms

Error handling in asynchronous operations requires special attention to timing issues. The MongoDB driver implements automatic retry logic, especially for transient failures like network interruptions:

const client = new MongoClient(uri, {
  retryWrites: true,
  retryReads: true,
  maxRetryTime: 30000
});

async function reliableUpdate() {
  try {
    await client.db().collection('orders').updateOne(
      { _id: orderId },
      { $inc: { count: 1 } },
      { maxTimeMS: 5000 } // Operation timeout setting
    );
  } catch (err) {
    if (err instanceof MongoNetworkError) {
      console.error('Network failure, client should retry');
    } else if (err instanceof MongoServerError) {
      console.error('Server error:', err.codeName);
    }
  }
}

Asynchronous Control in Transactions

MongoDB 4.0+ supports multi-document transactions, and its asynchronous API design requires special attention to execution order and error propagation:

const session = client.startSession();
try {
  await session.withTransaction(async () => {
    const accounts = client.db('bank').collection('accounts');
    await accounts.updateOne(
      { _id: 'A' }, { $inc: { balance: -100 } }, { session }
    );
    await accounts.updateOne(
      { _id: 'B' }, { $inc: { balance: 100 } }, { session }
    );
  }, {
    readConcern: { level: 'snapshot' },
    writeConcern: { w: 'majority' }
  });
} finally {
  await session.endSession();
}

Performance Monitoring and Tuning

Through the asynchronous event interfaces exposed by the MongoDB driver, operation performance can be monitored and optimized:

const client = new MongoClient(uri, {
  monitorCommands: true
});

client.on('commandStarted', event => {
  console.log(`Command ${event.commandName} started`);
});
client.on('commandSucceeded', event => {
  console.log(`Command ${event.commandName} took ${event.duration}ms`);
});
client.on('commandFailed', event => {
  console.error(`Command ${event.commandName} failed`, event.failure);
});

// Slow query log example
client.on('commandSucceeded', event => {
  if (event.duration > 100) {
    console.warn(`Slow query warning: ${JSON.stringify(event.command)}`);
  }
});

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.