Много ошибок WriteConflict с транзакциями MongoDB - PullRequest
0 голосов
/ 05 ноября 2018

В настоящее время я играю с транзакциями в последнем доступном образе докера MongoDB 4.1.4 (используя Node 8.12.0 и Mongoose 5.3.8 в качестве клиента). Я сделал простой набор реплик с 3 экземплярами Монго, все работает отлично и все, пока я не сделаю много WriteConflict ошибок в течение короткого времени.

Мой код выглядит так:

// name, value are strings
// date is current time

const session = await createAnalyticsTransaction(); // returns 'session'

// _id is pregenerated
var stat = await Logger.findById(_id).session(session);

if (stat) {
    // do nothing if it already exists
    return true;
} 

await Logger.update({
    _id
}, {
    $setOnInsert: {
        _id,
        name,
        created: date.toDate(),
        modified: date.toDate()
    }
}, { 
    session, 
    upsert: true 
});

/*
    var period = 'month';
    var time = '2018-11'; 
    await Analytics.update({
        _id
    }, { 
        $setOnInsert: {
            _id,
            name,
            period,
            time,
            created: date.toDate()
        },
        $inc: inc
    }, {
        upsert: true,
        session: session 
    });
*/

await session.commitTransaction();
await session.endSession();

Пока все работает, пока я не раскомментирую переход в коллекцию Google Analytics с помощью $inc и $setOnInsert и не выполню около 1000 одновременных операций. Идея состоит в том, что коллекцию Analytics следует создавать, если она еще не создана. И тогда я начинаю получать много MongoError: WriteConflict со свойством ошибки errorLabels, имеющим TransientTransactionError.

enter image description here

Полагаю, это из-за $inc или upsert: true? Кто-нибудь испытывал это? Какое решение лучше в этом случае?

{ MongoError: WriteConflict
   at /Users/akuzmenok/Zend/MeteorLingua/lingua-analytics/node_modules/mongodb-core/lib/connection/pool.js:581:63
   at authenticateStragglers (/Users/akuzmenok/Zend/MeteorLingua/lingua-analytics/node_modules/mongodb-core/lib/connection/pool.js:504:16)
   at Connection.messageHandler (/Users/akuzmenok/Zend/MeteorLingua/lingua-analytics/node_modules/mongodb-core/lib/connection/pool.js:540:5)
   at emitMessageHandler (/Users/akuzmenok/Zend/MeteorLingua/lingua-analytics/node_modules/mongodb-core/lib/connection/connection.js:310:10)
   at Socket.<anonymous> (/Users/akuzmenok/Zend/MeteorLingua/lingua-analytics/node_modules/mongodb-core/lib/connection/connection.js:453:17)
   at emitOne (events.js:116:13)
   at Socket.emit (events.js:211:7)
   at addChunk (_stream_readable.js:263:12)
   at readableAddChunk (_stream_readable.js:250:11)
   at Socket.Readable.push (_stream_readable.js:208:10)
   at TCP.onread (net.js:597:20)
=> awaited here:
   at Function.Promise.await (/Users/akuzmenok/.meteor/packages/promise/.0.11.1.1ugu6ow.mjjhg++os+web.browser+web.browser.legacy+web.cordova/npm/node_modules/meteor-promise/promise_server.js:56:12)
   at Promise.asyncApply (imports/lib/analytics.js:97:9)
   at /Users/akuzmenok/.meteor/packages/promise/.0.11.1.1ugu6ow.mjjhg++os+web.browser+web.browser.legacy+web.cordova/npm/node_modules/meteor-promise/fiber_pool.js:43:40
=> awaited here:
   at Function.Promise.await (/Users/akuzmenok/.meteor/packages/promise/.0.11.1.1ugu6ow.mjjhg++os+web.browser+web.browser.legacy+web.cordova/npm/node_modules/meteor-promise/promise_server.js:56:12)
   at Promise.asyncApply (imports/lib/analytics.js:139:5)
   at /Users/akuzmenok/.meteor/packages/promise/.0.11.1.1ugu6ow.mjjhg++os+web.browser+web.browser.legacy+web.cordova/npm/node_modules/meteor-promise/fiber_pool.js:43:40
=> awaited here:
   at Function.Promise.await (/Users/akuzmenok/.meteor/packages/promise/.0.11.1.1ugu6ow.mjjhg++os+web.browser+web.browser.legacy+web.cordova/npm/node_modules/meteor-promise/promise_server.js:56:12)
   at Promise.asyncApply (imports/lib/analytics.js:158:5)
   at /Users/akuzmenok/.meteor/packages/promise/.0.11.1.1ugu6ow.mjjhg++os+web.browser+web.browser.legacy+web.cordova/npm/node_modules/meteor-promise/fiber_pool.js:43:40
=> awaited here:
   at Function.Promise.await (/Users/akuzmenok/.meteor/packages/promise/.0.11.1.1ugu6ow.mjjhg++os+web.browser+web.browser.legacy+web.cordova/npm/node_modules/meteor-promise/promise_server.js:56:12)
   at Promise.asyncApply (imports/lib/analytics.js:49:23)
   at /Users/akuzmenok/.meteor/packages/promise/.0.11.1.1ugu6ow.mjjhg++os+web.browser+web.browser.legacy+web.cordova/npm/node_modules/meteor-promise/fiber_pool.js:43:40
=> awaited here:
   at Function.Promise.await (/Users/akuzmenok/.meteor/packages/promise/.0.11.1.1ugu6ow.mjjhg++os+web.browser+web.browser.legacy+web.cordova/npm/node_modules/meteor-promise/promise_server.js:56:12)
   at Promise.asyncApply (imports/lib/analytics.js:14:23)
   at /Users/akuzmenok/.meteor/packages/promise/.0.11.1.1ugu6ow.mjjhg++os+web.browser+web.browser.legacy+web.cordova/npm/node_modules/meteor-promise/fiber_pool.js:43:40
 errorLabels: [ 'TransientTransactionError' ],
 operationTime: Timestamp { _bsontype: 'Timestamp', low_: 12, high_: 1541424838 },
 ok: 0,
 errmsg: 'WriteConflict',
 code: 112,
 codeName: 'WriteConflict',
 '$clusterTime':
  { clusterTime: Timestamp { _bsontype: 'Timestamp', low_: 12, high_: 1541424838 },
    signature: { hash: [Object], keyId: 0 } },
 name: 'MongoError',
 [Symbol(mongoErrorContextSymbol)]: {} }

Еще одно замечание, я начинаю транзакцию, подобную этой:

const session = await MongoAnalytics.startSession({ causalConsistency: true });
session.startTransaction({ readConcern: { level: 'snapshot' }, writeConcern: { w: 'majority' } });

1 Ответ

0 голосов
/ 05 ноября 2018

Вы читаете данные из базы данных, а затем обновляете их. Это выглядит так:

DATA (state 0) <---
UPDATED DATA (state 1) --->

При выполнении двух асинхронных вызовов:

DATA (state 0) <---
DATA (state 0) <---
UPDATED DATA (state 1) --->
UPDATED DATA (state 1') ---> ERROR

Возвращает ошибку, так как состояние данных изменилось. Вот как транзакции должны работать.



Чтобы избежать конфликта доступа, вы можете реализовать собственную систему очередей. Или перехватите ошибку и перезапустите транзакцию с setTimeout с максимальным количеством попыток.

Система очередей :

DATA (state 0) <---
UPDATED DATA (state 1) --->
DATA (state 1) <---
UPDATED DATA (state 2) --->

Перезапустить систему

DATA (state 0) <---
DATA (state 0) <---
UPDATED DATA (state 1) --->
UPDATED DATA (state 1') ---> ERROR
DATA (state 1) <---
UPDATED DATA (state 2) --->
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...