Синхронная запись в БД
День добрый форумчане! Устал бороться в одиночку с «асинхронностью» сии детища NodeJS. Прибегаю к помощи гуру Promises & Multyrequest.
Ситуация такая: Допустим, у нас в базе (юзаю Pg + Sequalize) есть табличка с данными такого вида: ID TYPE COUNTER 1 «one» 1 2 «two» 1 В модели есть методы decCounterByType(type); & incCounterByType(type); и вроде бы все ни чего пока не попробуешь использовать эти методы в цикле. Цикл же имитирует многочисленное обращение к базе, пример: for(let i = 0; i < 1000; i ++) { Model.incCounterByType(«one») .then(res => console.log(`${i}, ${type}, ${res.counter}`)) .catch(console.error); } Понимаем да, что вывод будет хаотичен и не каждое обращение получит правильный ответ, т. е. лог будет выглядеть примерно так: 0, «one», 1 3, «one», 2 1, «one», 4 2, «one», 3 Где то на просторах нашел решение, и да, оно работает, блокирует обращение: let last = Promise.resolve({}) for(let i = 0; i < 1000; i ++) { last = last .then(() => Model.incCounterByType(«one»)) .then(res => console.log(`${i}, ${type}, ${res.counter}`)) .catch(console.error); } в этом случае у нас вывод будет уже: 0, «one», 1 1, «one», 2 2, «one», 3 3, «one», 4 и вроде бы эврика, решение есть! Но, под правильным углом со знаниями работы алгоритма Promise, мы понимаем, что если один из Promise не выполнит «resolve», или выполнит «reject» or «throw» все остальные обращения ждет фиаско. Пока остановился на таком варианте но уверен, за такой костыль будут бить ногами: let last = Promise.resolve({}) for(let i = 0; i < 1000; i ++) { last = last .then(() => Model.incCounterByType(«one»)) .then(res => console.log(`${i}, ${type}, ${res.counter}`)) .catch(Promise.resolve)); } Цель, добиться корректного обращения к БД и синхронный вывод результатов. А как вы блокируете обращения к БД? Спасибо за участие в обсуждении! |
Что вы пытаетесь вообще сделать?
Почему не увеличить сразу все значения 1 запросом? |
Цитата:
|
Если каждый юзер посылает запрос при заходе - каждый юзер получает свой ответ. И на момент ответа - этот ответ верен. В чём тогда проблема?
Чисто чтоб "порядок был" и цифры красивенькие? |
Async/await в помощь
async function foo() { // вернет Promise<undefined> for (let i = 0; i < 1000; i++) { try { const res = await Model.incCounterByType(«one») console.log(`${i}, ${type}, ${res.counter}`) } catch(err) { // обработка ошибки } } } |
stweet, перечитал, прошу прощение.
Не обратил внимание, что у вас это просто имитация. C postgresql не работал, она не блокирует строку во время её обновления? |
Цитата:
record 1 = 1 record 2 = 10 record 3 = 9 record 4 = 11 и т.д. |
Цитата:
|
Цитата:
Пример такой, есть счетчик, равен "3" Если одновременно "два" юзера жмякнут на "поднять" оба получают "4". А в базе стоит "5" - !неудачный пример! Если два юзера одновременно зарегистрируются, счетчик идти у них будет не по порядку. - Скорее я об этом. |
let GLOBAL_ITER = 0; function iter() { return new Promise((resolve) => { setTimeout(() => { GLOBAL_ITER++; resolve(GLOBAL_ITER); }, 1e3); }); } async function test() { for (let i = 0; i < 5; i++) { try { const curGlobalIter = await iter(); console.log(curGlobalIter) } catch(err) { } } } test().then(() => console.log('done')); как вы можете понять, я увижу постепенно появившиеся с delay 1с 1 2 3 4 5 done |
Вот и я так думал, пока не попробовал это сделать используя базу данных. На примере обычного(локального) счетчика у меня тоже все чистенько.
|
Значит у вас проблема в логике на БД
async function registerUser(data) { // Promise<number> // ... // register_user - хранимка, возвращает integer id-а пользователя, вся логика работы с данными в ней описана const id = (await db.query('SELECT register_user as id FROM register_user(%)', [data])).id; // ... return id; } /* если в БД все правильно сделано и id пользователя это primary key, и например выдается какой-то последовательностью(например user_id_sequence), то вы никогда не получите два одинаковых id-а вызвав функцию registerUser */ |
Цитата:
Вы можете искусственно накапливать обращения, приходящие по очереди с интервалом меньше n, и, как только дольше чем n не было обращений, красиво отправлять в базу одним куском и красиво же отправлять обратно всем, но зачем? ...upd Цитата:
...upd Если уж так хочется красоты - можно сделать переменную someCount и при получении любого соответствующего ответа от базы проверять если та меньше возвращённого значения - устанавливать оное. И при ответе пользователю использовать уже её. Всё равно это не ничего не гарантирует, но будет немного "красивее". |
Цитата:
|
В общем вопрос такой:
как выглядит ваша функция Model.incCounterByType? Если это вызов к postgresql типа: UPDATE counters SET counter = counter + 1 WHERE type = % RETURNING counter; то все должно нормально работать |
Давайте на примере игры рассмотрим данную задачу. Допустим есть игра "однорукий бандит" (рулетка) и у нее есть общий баланс. Так вот, если 50 пользователей запустят эту игру (у каждого свой инстанц но общий баланс игры), выполняя какие то действия будут изменять состояние общего баланса игры. Меня пугает то, что в цикле разные ответы.
Т.е. пользователь "i" делает запрос на изменение общего баланса игры и получает "левый" ответ в тот момент когда должен был получить свой. Посмотрите внимательно на мною приведенный код в старте топика. |
Да, я рассматривал данное решение, но, если в момент хранения данных в памяти а не в базе рухнет сервер то он утащит за собой и память. А подняв будет масса вопросов у пользователей!
|
Как раз так и выглядит. Но в старте топика я вывел вам данные консоли, и они не по порядку, это меня и пугает. В базе все вроде бы пучком но ответы на запросы разные. Т.е. запрашиваемый может получить ответ предыдущего и на оборот.
|
stweet, каждый получает свой ответ.
Проблема исключительно в порядке. Но порядок в случае с пользователями не важен. Проблема решена. |
Между этим кодом никакой разницы нет. Он делает одно и то же.
let last = Promise.resolve({}) for(let i = 0; i < 1000; i ++) { last = last .then(() => Model.incCounterByType(«one»)) .then(res => console.log(`${i}, ${type}, ${res.counter}`)) .catch(console.error); } for(let i = 0; i < 1000; i ++) { try { const res = await Model.incCounterByType(«one»)) console.log(`${i}, ${type}, ${res.counter}`) } catch(err) { console.error(err); } } |
Не вполне убедительно и все же, огромное спасибо за участие.
|
Спасибо, будем надеяться так и есть.
|
И да, если вы вдруг захотите вызвать таки все обращения к бд одновременно(то что у вас изначально делалось) то код надо привести примерно к такому виду:
const res = await Promise.all(Array(1000).fill(Model.incCounterByType(«one»)).map(promise => promise.then(res => res.counter).catch(err => { console.error(err); return -1;}))); // res: number[] - массив чисел |
Цитата:
|
Часовой пояс GMT +3, время: 03:42. |