node-tarantool-driver
node-tarantool-driver copied to clipboard
Множественная вставка кортежей
Всем привет. Заметил, что не могу вставить большое кличество кортежей на nodejs
node v10.12.0 [email protected] MacOS 10.14
for(let i = 1; i <= 1000; i++) {
connection.insert("your_space", [i, i*2, "fft" ])
.then(
() => { log("+OK"); },
err => { error("+ERR"); }
);
}
Очень хорошо воспроизводится на 1000, в итоге стабильно вставляется только 554 кортежа. В самом драйвере нашел несколько интересных моментов.
- Когда мы вызываем connection.insert("your_space", tuple); первым делом драйвер сделает select https://github.com/tarantool/node-tarantool-driver/blob/master/lib/commands.js#L21, чтобы получить id спейса. Если мы делаем 1000 запросов insert в space "your_space", драйвер сделает 1000 одинаковых select'ов на получение его id, хотя хватило бы и одного.
Да, там есть сохранение в spaceId this.namespace для последующего использования
_this.namespace[name] = {
id: spaceId,
name: name,
indexes: {}
};
Но у меня 1000 select'ов выполнялись прежде чем приходил ответ на первый.
- Сказанное в пункте 1 не должно влиять на вставку данных, т.к это не баг, а скорее небольшое упущение. Ниже приведу пример лога драйвера вставки 1000 кортежей в space "your_space".
Запросы на получение id спейса по имени.
tarantool-driver:commands +OK - _replaceInsert cmd: 2 reqId: 0 spaceId: your_space tuple: [1,2,"fr"] +0ms
tarantool-driver:commands +OK - _getSpaceId name: your_space
tarantool-driver:main +OK - sendCommand. command: [1,1,{}] state: connect +1ms
tarantool-driver:main socket queue -> 1(1) +0ms
...1000 штук....
tarantool-driver:commands +OK - _replaceInsert cmd: 2 reqId: 1998 spaceId: your_space tuple: [1000,2000,"fr"] +0ms
tarantool-driver:commands +OK - _getSpaceId name: your_space +0ms
tarantool-driver:main +OK - sendCommand. command: [1,1999,{}] state: connect +0ms
tarantool-driver:main socket queue -> 1(1999) +1ms
после этого прилетает ответы на запросы. 65536 байт максимум, который мы можем получить за один раз.
tarantool-driver:handler +OK - dataHandler +31ms
tarantool-driver:handler +OK - dataHandler.CONNECTED: data.length 65536 +0ms
Каждый ответ на select имеет длину 147 байт, 142 - payload, и длина data.readUInt32BE(1); 4 байта с оффсетом 1. https://github.com/tarantool/node-tarantool-driver/blob/master/lib/event-handler.js#L63
Итого за один раз мы получили ответ на 445 select'ов и 121(116+5) байт 446-го ответа. Т.к у нас есть начало следующего сообщения дравер сохраняет его и переводит dataState в состояние AWAITING https://github.com/tarantool/node-tarantool-driver/blob/master/lib/event-handler.js#L89
Следующим шагом драйвер резолвит 445 промисов созданных для получения id спейса вот тут https://github.com/tarantool/node-tarantool-driver/blob/master/lib/commands.js#L418
И отправляется 445 запросов на создание кортежей
tarantool-driver:commands +OK - _replaceInsert metadata: [535,0] +10ms
tarantool-driver:commands +OK - _replaceInsert cmd: 2 reqId: 0 spaceId: 535 tuple: [1,2,"fr"] +0ms
tarantool-driver:main +OK - sendCommand. command: [2,0,{}] state: connect +1ms
tarantool-driver:main socket queue -> 2(0) +1ms
...445 штук....
tarantool-driver:commands +OK - _replaceInsert metadata: [535,0] +0ms
tarantool-driver:commands +OK - _replaceInsert cmd: 2 reqId: 888 spaceId: 535 tuple: [445,890,"fr"] +0ms
tarantool-driver:main +OK - sendCommand. command: [2,888,{}] state: connect +0ms
tarantool-driver:main socket queue -> 2(888) +0ms
Как видно из лога spaceId = 535 полученные из базы.
Далее по логу нам приходит еще 65536 байт, ответ на наши select'ы и 116 было итого 65652
tarantool-driver:handler +OK - dataHandler +1ms
tarantool-driver:handler +OK - dataHandler.AWAITING data.length: 65536 awaitingResponseLength 142 bufferSlide.bufferLength: 116 +0ms
tarantool-driver:handler +OK - dataHandler.AWAITING data.length: 65652 +0ms
Еще на 446 сообщений
И т.к. у нас снова awaitingResponseLength > 0 https://github.com/tarantool/node-tarantool-driver/blob/master/lib/event-handler.js#L129 драйвер оставляет dataState в состоянии AWAITING https://github.com/tarantool/node-tarantool-driver/blob/master/lib/event-handler.js#L130 , но переводит еще и state в состояние AWAITING https://github.com/tarantool/node-tarantool-driver/blob/master/lib/event-handler.js#L131
Далее по логике вещей драйвер должен зарезолвить следующие 446 промисов, что должно будет вызвать запись следующих 446 кортежей.
но вот код функции sendCommand https://github.com/tarantool/node-tarantool-driver/blob/master/lib/connection.js#L135
как видно здесь данные отправляются на сервер, только если state выставлен в CONNECTED, а state был только что переведен в AWAITING И выполняется default блок https://github.com/tarantool/node-tarantool-driver/blob/master/lib/connection.js#L151 который просто складывает сообщения в offlineQueue.
tarantool-driver:main +OK - sendCommand. command: [2,890,{}] state: awaiting +0ms
tarantool-driver:main push top offlineQueue +0ms
...446 штук....
tarantool-driver:main +OK - sendCommand. command: [2,1780,{}] state: awaiting +1ms
tarantool-driver:main push top offlineQueue +0ms
Далее мы получаем по сети от сервера 35381 байт и 90 байт у нас есть от предыдущего сообщения в этом чанке содержатся 110 ответов на наши селекты, остальные это ответы на 445 insert'ов, которые мы смогли отправить пока state был равен CONNECTED
tarantool-driver:handler +OK - dataHandler +0ms
tarantool-driver:handler +OK - dataHandler.AWAITING data.length: 35381 awaitingResponseLength 142 bufferSlide.bufferLength: 90 +0ms
tarantool-driver:handler +OK - dataHandler.AWAITING data.length: 35471 +0ms
после переработки этого чанка state снова выставляется в CONNECTED, но offlineQueue при этом не будет отправлена на сервер https://github.com/tarantool/node-tarantool-driver/blob/master/lib/event-handler.js#L124
Оставшиеся сообщения будут отправлены в обычном режиме.
Резюме:
- По первому пункту я в дравере добавил проверку в эту функцию, что запрос на получение id для этого спейса уже отправлен и возвращаю уже существующий промис. Когда запрос будет выполнен, все промисы буду успешно зарезолвлены. Это ускорит множественную вставку, т.к будет только один запрос. Тесты прошли.
- Очевидный баг с тем, что offlineQueue не отправляется на сервер при переходе из состояния AWAITING или AWAITING_LENGTH в состояние CONNECTED