Транзакции базы данных Firebase в реальном времени иногда занимают слишком много времени.

У меня есть приложение nodejs, которое работает на одном сервере. Мне нужно запустить приложение на нескольких серверах для балансировки нагрузки.

Это приложение одновременно обрабатывает только один запрос от конкретного пользователя. Если от пользователя получены одновременные запросы, запросы ставятся в очередь и выполняются один за другим. Это делается для обеспечения согласованности некоторых данных в базе данных, которые могут быть повреждены, если одновременные запросы обрабатываются приложением.

Поскольку я собираюсь запускать приложение на нескольких серверах, мне нужен способ запретить серверам обрабатывать одновременные запросы от одного пользователя. для этого я использовал базу данных Firebase в реальном времени для создания распределенной блокировки. Ниже приведена более простая версия моего кода.

function lockUser(user) {
    return firebaseAdmin.database().ref('users/' + user + '/lock').transaction((currentData) => {
        if (currentData === null || currentData.lockTime === 0) {
            return {'lockTime': Date.now()};
        }
    }, null, false).then(async (result) => {
        if (result.committed) {
            return Promise.resolve();
        }
        log.info('failed to lock ' + user + '. retrying.');
        await sleepFor(500);
        return lockUser(user, user, res);
    }).catch(async (reason) => {
        log.info('lock failed. ' + user + '. reason: ' + reason + '. retrying');
        await sleepFor(500);
        return lockUser(user, user, res);
    });
}

function unlockUser(user) {
    log.info('unlocking firebase lock. ' + user);
    firebaseAdmin.database().ref('users/' + user + '/lock').set({'lockTime': 0}, (error) => {
        if (error) {
            log.warn('failed to unlock ' + user + '. error: ' + util.inspect(error));
        } else {
            log.info('unlocked ' + user);
        }
    });
}

С приведенным выше кодом блокировка обычно занимает около 100 миллисекунд и в основном непротиворечива. Но редко иногда я наблюдаю значительную задержку завершения транзакции. В этом случае блокировка может занять около 30 секунд.

В чем может быть причина такой задержки? Есть ли причина, по которой я не должен использовать базу данных Firebase в реальном времени таким образом?


person Lahiru Chandima    schedule 20.02.2019    source источник


Ответы (1)


Транзакции RTDB по сути являются операциями сравнения и установки. Если состояние базы данных изменяется во время выполнения транзакции, SDK повторяет транзакцию с новым состоянием базы данных. В вашем случае это может произойти, когда несколько процессов борются за блокировку:

  1. P1 пытается получить блокировку, видит currentData.lockTime = 0 и решает обновить ее.
  2. P2 пытается получить блокировку, видит currentData.lockTime = 0 и решает обновить ее.
  3. P1 фиксирует свою транзакцию.
  4. P2 видит, что данные изменились с момента последнего чтения, поэтому повторяет транзакцию.

Теперь есть патологический случай, когда как раз перед тем, как P2 повторяет транзакцию, P1 отказывается от своей блокировки. Таким образом, P2 снова увидит этот currentData.lockTime = 0 и снова попытается захватить блокировку. Но и эта попытка может потерпеть неудачу, если другой процесс P3 захватит блокировку из-под P2. Итак, цикл повторных попыток продолжается.

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

Я не уверен, что это то, что происходит в вашем случае. Но это определенно одно из объяснений. Возможно, включите ведение журнала отладки для SDK и попытайтесь получить больше информации о том, что происходит во время этих длительных задержек. Я бы также рекомендовал попробовать реализовать ваши регулярные операции обновления данных с помощью транзакций и полностью исключить распределенную блокировку.

person Hiranya Jayathilaka    schedule 20.02.2019
comment
Спасибо за ответ, но конфликт блокировок, похоже, не является причиной в этом случае. Мои журналы показывают, что не было других попыток блокировки, когда одна блокировка занимала ненормально много времени. Кроме того, я тестировал блокировку, отправляя несколько клиентских запросов одновременно, и серверу удавалось последовательно выполнять их довольно быстро (с характером моего клиентского приложения одновременные запросы встречаются редко, поэтому я не тестировал большое количество одновременных запросов). Запросы). - person Lahiru Chandima; 21.02.2019
comment
Другая возможность — аварийное переключение сервера. Задержка, которую вы видите, может быть временем, которое требуется SDK для обнаружения сбоя сервера и переключения на другой сервер в пуле (хотя ~ 30 секунд звучит слишком много для этого). Журналы отладки SDK должны быть в состоянии подтвердить. - person Hiranya Jayathilaka; 21.02.2019
comment
Спасибо. Я включу журналы и попытаюсь выяснить, что происходит не так. - person Lahiru Chandima; 21.02.2019