node-tarantool-driver icon indicating copy to clipboard operation
node-tarantool-driver copied to clipboard

Множественная вставка кортежей

Open affair opened this issue 6 years ago • 0 comments

Всем привет. Заметил, что не могу вставить большое кличество кортежей на 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 кортежа. В самом драйвере нашел несколько интересных моментов.

  1. Когда мы вызываем 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. Сказанное в пункте 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

Оставшиеся сообщения будут отправлены в обычном режиме.

Резюме:

  1. По первому пункту я в дравере добавил проверку в эту функцию, что запрос на получение id для этого спейса уже отправлен и возвращаю уже существующий промис. Когда запрос будет выполнен, все промисы буду успешно зарезолвлены. Это ускорит множественную вставку, т.к будет только один запрос. Тесты прошли.
  2. Очевидный баг с тем, что offlineQueue не отправляется на сервер при переходе из состояния AWAITING или AWAITING_LENGTH в состояние CONNECTED

affair avatar Oct 14 '18 10:10 affair