[英]MySQL XDev API - Node.js Connector - client.getSession() never resolve nor throw after few hours
我在 nodejs 中使用 MySQL XDevAPI 連接器連接到同一服務器上的 MySQL 數據庫。 它成功運行了幾個小時,直到某個時候await pool.getSession()
永遠不會解決也不會拋出任何錯誤。
我創建我的客戶池:
{
pooling : {
enabled: true,
maxSize: 25,
maxIdleTime : 1000,
queueTimeout: 2000
}
}
正如我所說,幾個小時后,服務器將順利運行,突然間,連接池將停止為我提供新會話。 它發生之前的時間從 10/12 小時到 48 小時不等(使用它的網站幾乎沒有收到流量)。
我試圖通過添加一個帶有超時的Promise.race
來規避這個問題,如下所示:
await Promise.race([
pool.getSession(),
new Promise((resolve, reject) => {
setTimeout(() => reject(
new DBConnectionError(
'XDEV_GET_SESSION_NEVER_RETURNS',
'Here we go again...'
)
), 10000)
})
]);
有時,達到超時允許池為后續的pool.getSession
工作幾分鍾,直到錯誤再次出現。 有時需要多次調用(3-4),有時,我需要重新啟動我的應用程序。 這是一種非常奇怪和隨機的行為。
我通過調用await con.close()
在任何事務/請求后釋放每個連接,並且我的比賽超時是 queueTimeout 的五倍。
我究竟做錯了什么 ?
通過進一步調查,我發現這個問題更多地與遺留代碼在模擬嵌套事務中做愚蠢的事情有關。
它沒有重用當前連接,而是在某個地方創建了一個新連接,並且如果滿足某個條件(在這種精確情況下,用戶會話因不活動而死亡),它永遠不會將連接返回到池中,因為代碼沒有沒有遵循應用程序設計指南。
這種特殊情況導致錯誤以看似“隨機”的頻率發生,因為用戶會話的生命周期在用戶和應用程序使用之間可能存在很大差異。
這是一個非常特定於項目的相關問題,依賴於私有代碼中的錯誤,但如果有人遇到與 mysql 連接器類似的問題,我將其作為參考:仔細調查以找到未發布的連接。
我相信maxIdleTimeout
參數可以通過自動返回到池的 idl 連接來避免這種情況,但它似乎不是這樣工作的。
即使使用我的固定代碼,該錯誤再次出現。
所以,我進行了四種類型的測試:
@mysql/xdevapi
模塊mysql2
模塊@mysql/xdevapi
模塊mysql2
模塊[*] 通過原始測試,我的意思是在不涉及任何私有代碼的情況下測試包。 只有涉及的模塊。
我們嘗試執行 500 個請求,一次一個,看看會發生什么:
mysql2
const mysql = require('mysql2/promise');
const MAX_CON = Number.parseInt(process.argv[2]) || 120;
const NB_REQ = Number.parseInt(process.argv[3]) || 500;
(async() => {
try{
const client = new mysql.createPool(
{
host : '127.0.0.1',
port : 3306,
user : 'test',
password : 'test',
database : 'test',
connectionLimit : MAX_CON
}
);
const before = Date.now();
for(let i = 0; i < NB_REQ; i++){
const con = await client.getConnection();
await con.query('SELECT 1 from user');
await con.release();
}
const time = (Date.now() - before) / 1000; //in seconds
const opBySecond = NB_REQ / time;
console.log('The end : ', { opBySecond, time, NB_REQ, MAX_CON });
await client.end();
process.exit(0);
}catch(e){
console.log(e);
}
})();
@mysql/xdevapi
const mysql = require('@mysql/xdevapi');
const MAX_CON = Number.parseInt(process.argv[2]) || 120;
const NB_REQ = Number.parseInt(process.argv[3]) || 500;
(async() => {
try{
const client = await mysql.getClient(
{
host : '127.0.0.1',
port : 33060,
user : 'test',
password : 'test',
schema : 'test'
},
{
pooling : {
maxSize : MAX_CON
}
}
);
const before = Date.now();
for(let i = 0; i < NB_REQ; i++){
const con = await client.getSession();
const prep = await con.sql('SELECT 1 from user');
await prep.execute();
await con.close();
}
const time = (Date.now() - before) / 1000;
const opBySecond = NB_REQ / time;
console.log('The end : ', { opBySecond, time, NB_REQ, MAX_CON });
await client.close();
process.exit(0);
}catch(e){
console.log(e);
}
})();
兩個腳本都運行500
請求,其中包含60
連接池和各自的命令:
node squential-mysql2.js 60 500
node squential-xdevapi.js 60 500
結果:
司機 | 操作/秒 | 時間(秒) |
---|---|---|
mysql2 | 11 111.11 | 0.045 |
xdevapi | 3 703.70 | 0.135 |
@mysql/xdevapi
在順序執行時比mysql2
慢 3 到 4 倍。
mysql2
const mysql = require('mysql2/promise');
const MAX_CON = Number.parseInt(process.argv[2]) || 60;
const NB_REQ = Number.parseInt(process.argv[3]) || 500;
(async() => {
try{
const client = new mysql.createPool(
{
host : '127.0.0.1',
port : 3306,
user : 'test',
password : 'test',
database : 'test',
connectionLimit : MAX_CON
}
);
const before = Date.now();
const promises = [];
for(let i = 0; i < NB_REQ; i++){
promises.push(new Promise(async resolve => {
const con = await client.getConnection();
await con.query('SELECT 1 from user');
await con.release();
resolve();
}))
}
await Promise.allSettled(promises);
const time = (Date.now() - before) / 1000; //in seconds
const opBySecond = NB_REQ / time;
console.log('The end : ', { opBySecond, time, NB_REQ, MAX_CON });
await client.end();
process.exit(0);
}catch(e){
console.log(e);
}
})();
@mysql/xdevapi
const mysql = require('@mysql/xdevapi');
const MAX_CON = Number.parseInt(process.argv[2]) || 60;
const NB_REQ = Number.parseInt(process.argv[3]) || 500;
(async() => {
try{
const client = await mysql.getClient(
{
host : '127.0.0.1',
port : 33060,
user : 'test',
password : 'test',
schema : 'test'
},
{
pooling : {
maxSize : MAX_CON
}
}
);
const before = Date.now();
const promises = [];
for(let i = 0; i < NB_REQ; i++){
promises.push(new Promise(async resolve => {
const con = await client.getSession();
const prep = await con.sql('SELECT 1 from user');
await prep.execute();
await con.close();
resolve()
}));
}
await Promise.allSettled(promises);
const time = (Date.now() - before) / 1000;
const opBySecond = NB_REQ / time;
console.log('The end : ', { opBySecond, time, NB_REQ, MAX_CON });
await client.close();
process.exit(0);
}catch(e){
console.log(e);
}
})();
情況更糟。
兩個腳本都運行500
請求,其中包含60
連接池和各自的命令:
node parallel-mysql2.js 60 500
node parallel-xdevapi.js 60 500
結果:
司機 | 操作/秒 | 時間(秒) |
---|---|---|
mysql2 | 8 771.92 | 0.057 |
xdevapi | 76.66 | 6.522 |
@mysql/xdevapi
在並行執行時比mysql2
慢 110 倍。 但它並不止於此。
如果你嘗試node parallel-xdevapi.js 120 500
你可能會得到一個錯誤:
/test/node/node_modules/@mysql/xdevapi/lib/DevAPI/Connection.js:920
return state.error || new Error(errors.MESSAGES.ER_DEVAPI_CONNECTION_CLOSED);
^
Error: This session was closed. Use "mysqlx.getSession()" or "mysqlx.getClient()" to create a new one.
at Object.getError (/test/node/node_modules/@mysql/xdevapi/lib/DevAPI/Connection.js:920:35)
at Socket.<anonymous> (/test/node/node_modules/@mysql/xdevapi/lib/DevAPI/Connection.js:677:40)
at Object.onceWrapper (node:events:642:26)
at Socket.emit (node:events:527:28)
at TCP.<anonymous> (node:net:715:12)
Node.js v18.0.0
而node parallel-mysql2.js 120 500
輸出:
司機 | 操作/秒 | 時間(秒) |
---|---|---|
mysql2 | 7 142.86 | 0.07 |
對於拋出的錯誤,我的猜測是mysql/xdevapi
不處理一次連接的mysql-server
max 客戶端。 在我的機器上,它是默認值 128,因為我有多個使用數據庫的進程,使用maxSize=120
池運行腳本會使進程崩潰。
這就是為什么我在 dev 中沒有收到任何錯誤,但在 prod 中出現了一些危險的崩潰:兩個進程的maxSize=25
池和並行啟動的 4 個 nodejs 進程,它達到了最大 200 個 mysql 客戶端連接。 任何連接負載都會使一個或多個池崩潰,因此該進程將無法從中恢復。 從這一點開始,所有對client.getSession()
的后續調用都將無限期掛起。
我重構了整個應用程序數據庫連接處理以確保它是一致的,然后我創建了一個可以處理連接器插件/插件的數據庫組件。
我創建了兩個插件:一個使用@mysql/xedvapi
驅動程序,另一個使用mysql2
。 當我使用autocannon
運行一些基准測試時,兩者都顯示出與上述相似的結果。
我還創建了一個連接包裝器,它可以幫助我了解是否有任何連接未關閉。 沒有任何連接處於打開狀態,並且在每次查詢后總是返回到池中。
總之,我想說,此時@mysql/xdevapi
包似乎有問題,在版本8.0.29
和 mysql 8.0.29
,在 nodejs v18.0.0
,ubuntu 20.04
上。
要么是因為當 mysql 服務器無法接受更多連接時它不處理邊緣情況,要么是因為它的性能很慢,特別是當查詢不是順序的(這是像 nodejs 應用程序這樣的 web 服務器中的大多數用例)。
我現在通過從@mysql/xdevapi
切換到mysql2
解決了我的問題。
對於那些對這個討論感興趣或可能對這個討論感興趣的人,請點擊這個鏈接到mysql 錯誤報告。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.