Datasource Level
In distributed scenarios, it's crucial to avoid deadlocks caused by datasource contention
VonaJS uses Async Local Storage to provide different datasource level for different contexts, thereby creating different database connection pools and preventing datasource contention
Scenario Description
1. Worker A
Execute the following code in Worker A:
class ControllerStudent {
async test() {
const result = await this.ctx.db.transaction.begin(async () => {
const data = { a: 1, b: 2 };
return await this.scope.queue.add.pushAsync(data);
});
console.log(result);
}
}- First, create a database transaction
- Push a job within the database transaction and await for the job to return the result
2. Worker B
Execute the following code in Worker B:
class QueueAdd {
async execute(data, _options) {
+ await this.scope.model.student.update({ id: 1, name: 'student name' });
console.log(process.pid, data.a + data.b);
return data.a + data.b;
}
}- Modifying student data in the job
3. Potential Deadlock Analysis
- When Worker A starts a database transaction, the number of available connections in the database connection pool is
0. The transaction will only be completed after the job returns the result, thus releasing the database connection - When Worker B modifies student data, it needs to obtain a connection object from the database connection pool. Since the connection count is
0, it will wait indefinitely Worker AandWorker Bwait for each other, leading to a deadlock
Solution
In fact, the above potential deadlock situation will not occur in VonaJS. Because VonaJS provides a datasource level mechanism
VonaJS uses Async Local Storage to provide different datasource level for different contexts, thereby creating different database connection pools and avoiding datasource contention
TIP
In VonaJS, in a distributed scenario, setting the connection maximum of the database connection pool to 1 will not cause deadlocks due to datasource contention
class ControllerStudent {
async test() {
await this.scope.model.student.select();
const dbLevel = this.bean.database.current.level;
await this.bean.database.switchDb(async () => {
await this.scope.model.student.select();
const dbLevel1 = this.bean.database.current.level;
console.log(dbLevel, dbLevel1);
}, { level: dbLevel + 1 });
}
}this.bean.database.current.level: Gets the current datasource levelswitchDb: Creates a new context by passing in the newlevelstudent.select: Executes in different contexts, thus using different datasource levels and correspondingly different database connection pools
Shorthand Method
class ControllerStudent {
async test() {
+ await this.bean.database.switchDbIsolate(async () => {
+ });
}
}switchDbIsolate: Automatically passes in the parametercurrent.level + 1, thus implementing datasource leveling
Push Job
When using the push/pushAsync method to push a job, the system automatically passes in current.level + 1, thus implementing datasource leveling and avoiding deadlocks
You can also explicitly pass in the parameter:
class ControllerStudent {
async test() {
const result = await this.ctx.db.transaction.begin(async () => {
const data = { a: 1, b: 2 };
return await this.scope.queue.add.pushAsync(data, {
+ dbInfo: {
+ level: this.bean.database.current.level + 1,
+ },
});
});
console.log(result);
}
}