Как быстро вы можете увеличить значение в базе данных Firebase Realtime? - PullRequest
4 голосов
/ 01 мая 2020

firebaser here

Когда я недавно написал в Твиттере о новом операторе increment() в базе данных Firebase Realtime, один из товарищей по команде спросил, как быстро increment() ,

Мне было интересно то же самое: как быстро вы можете увеличить значение с помощью increment(1)? И как это сравнить с использованием транзакции для увеличения значения ?

1 Ответ

6 голосов
/ 01 мая 2020

TL; DR

Я проверил эти случаи:

  1. Увеличение значения с помощью вызова transaction:

    ref.transaction(function(value) {
      return (value || 0) + 1;
    });
    
  2. Увеличение значения с помощью нового оператора increment:

    ref.set(admin.database.ServerValue.increment(1));
    

Тот факт, что приращение происходит быстрее, не будет сюрпризом, но ... насколько ?

Результаты:

  • С помощью транзакций мне удалось увеличить значение примерно в 60-70 раз в секунду.
  • С помощью оператора increment я смог увеличить значение примерно в 200-300 раз в секунду.

Как я выполнил тест и получил эти числа

Я запустил тест на своем MacBook Pro модели 2016 года и обернул вышеизложенное простым Node.js скриптом, который использует Node SDK на стороне клиента . Скрипт обертывания для операций был действительно базовым c:

timer = setInterval(function() {
    ... the increment or transaction from above ...
}, 100);

setTimeout(function() {
  clearInterval(timer);
  process.exit(1);
}, 60000)

Итак: увеличьте значение 10 раз в секунду и прекратите делать это через 1 минуту. Затем я породил экземпляры этого процесса с помощью этого сценария:

for instance in {1..10}
do
  node increment.js &
done

Так что это будет запускать 10 параллельных процессов с оператором increment, каждый из которых увеличивает значение в 10 раз в секунду, в общей сложности 100 приращений в второй. Затем я менял количество экземпляров до тех пор, пока «приращения в секунду» не достигли пика.

Затем я написал небольшой сценарий для jsbin , чтобы прослушать значение и определить количество приращений. в секунду с помощью простого фильтра низких частот, скользящего среднего. У меня были некоторые проблемы, поэтому я не уверен, что расчеты полностью верны. Учитывая мои результаты тестов, они были достаточно близко, но если кому-то захочется написать лучшего наблюдателя: будь моим гостем. :)

Что следует помнить о тестах:

  1. Я продолжал увеличивать количество процессов, пока «приращение в секунду» не показалось максимальным, но я заметил что это совпало с поклонниками моего ноутбука, идущими на полной скорости. Так что, скорее всего, я не нашел истинную максимальную пропускную способность работы на стороне сервера, но это была комбинация моей тестовой среды и сервера. Таким образом, вполне возможно (и на самом деле вероятно), что вы можете получить разные результаты при попытке воспроизвести этот тест, хотя, конечно, пропускная способность increment всегда должна быть значительно выше, чем transaction. Независимо от того, какие результаты вы получите: пожалуйста, поделитесь ими. :)

  2. Я использовал клиентский Node.js SDK, так как было проще всего работать. Использование разных SDK может дать немного разные результаты, хотя я ожидаю, что основные SDK (iOS, Android и Web) будут очень близки к тому, что я получил.

  3. Два разных товарищи по команде сразу же спросили, буду ли я запускать это на одном узле, или я буду увеличивать несколько значений параллельно. Параллельное увеличение нескольких значений может показать, есть ли узкое место пропускной способности в масштабе всей системы или если оно является указанным для узла c (что я ожидаю).

  4. Как уже говорилось: мой тест У меня нет ничего особенного, но мой код наблюдателя jsbin особенно подозрительный. Престижность, если кому-то хочется кодировать лучшего наблюдателя на тех же данных.


Как работает оператор транзакций и приращений под капотом

Чтобы понять производительность Разница между transaction и increment действительно помогает узнать, как эти операции работают под капотом. Для базы данных Firebase Realtime «под капотом» означает, что команды и ответы, которые отправляются между клиентами и сервером через соединение через веб-сокет.

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

В идеальном сценарии первоначальное предположение верно, и значение немедленно записывается на диск на сервере ( и после этого разослал всем слушателям). В блок-схеме, которая выглядела бы так:

            Client            Server

               +                   +
 transaction() |                   |
               |                   |
        null   |                   |
     +---<-----+                   |
     |         |                   |
     +--->-----+                   |
         1     |     (null, 1)     |
               +--------->---------+
               |                   |
               +---------<---------+
               |     (ack, 3)      |
               |                   |
               v                   v

Но если узел уже имеет значение на сервере, он отклоняет запись, возвращает фактическое значение, и клиент пытается снова:

            Client            Server

               +                   +
 transaction() |                   |
               |                   |
        null   |                   |
     +---<-----+                   |
     |         |                   |
     +--->-----+                   |
         1     |                   |
               |     (null, 1)     |
               +--------->---------+
               |                   |
               +---------<---------+
               |     (nack, 2)     |
               |                   |
         2     |                   |
     +---<-----+                   |
     |         |                   |
     +--->-----+                   |
         3     |      (2, 3)       |
               +--------->---------+
               |                   |
               +---------<---------+
               |      (ack, 3)     |
               |                   |
               |                   |
               v                   v

Это не так уж плохо, одна дополнительная поездка туда и обратно. Даже если бы Firebase использовал блокировку pessimisti c, ему потребовался бы этот обход, поэтому мы ничего не потеряли.

Проблема начинается, если несколько клиентов изменяют одно и то же значение одновременно. Это приводит к так называемому конфликту на узле, который выглядит следующим образом:

            Client            Server                Client
               +                   +                   +
 transaction() |                   |                   |
               |                   |                   | transaction()
        null   |                   |                   |
     +---<-----+                   |                   |  null
     |         |                   |                   +--->----+
     +--->-----+                   |                   |        |
         1     |                   |                   +---<----+ 
               |     (null, 1)     |                   |   1
               +--------->---------+    (null, 1)      |
               |                   |---------<---------+
               +---------<---------+                   |
               |     (nack, 2)     |--------->---------+
               |                   |     (nack, 2)     |
         2     |                   |                   |
     +---<-----+                   |                   |   2
     |         |                   |                   |--->----+
     +--->-----+                   |                   |        |
         3     |      (2, 3)       |                   |---<----+ 
               +--------->---------+                   |   3
               |                   |                   |
               +---------<---------+                   |
               |      (ack, 3)     |       (2, 3)      |
               |                   |---------<---------+
               |                   |                   |
               |                   |--------->---------+
               |                   |    (nack, 3)      |
               |                   |                   |   3
               |                   |                   |--->----+
               |                   |                   |        |
               |                   |                   |---<----+ 
               |                   |                   |   4
               |                   |       (3, 4)      |
               |                   |---------<---------+
               |                   |                   |
               |                   |--------->---------+
               |                   |     (ack, 4)      |
               |                   |                   |
               v                   v                   v

TODO: обновите приведенную выше диаграмму, чтобы операции на сервере не перекрывались.

Второй клиент должен был сделать еще одну попытку для своей работы, потому что значение на стороне сервера было изменено между его первой и второй попыткой. Чем больше клиентов мы пишем в это местоположение, тем больше вероятность повторных попыток. И клиент Firebase выполняет эти попытки автоматически, но после нескольких попыток он прекратит работу и выдаст исключение Error: maxretry для приложения.

По этой причине я мог увеличить счетчик только до 60-70. раз в секунду: при большем количестве операций записи на узле происходило слишком много конфликтов.

Операция increment по своей природе является атомом c. Вы говорите базе данных: каким бы ни было текущее значение, сделайте его x выше. Это означает, что клиент никогда не должен знать текущее значение узла, и поэтому он также не может угадать неправильно. Он просто говорит серверу, что делать.

Наша блок-схема с несколькими клиентами выглядит следующим образом при использовании increment:

            Client            Server                Client

               +                   +                   +
  increment(1) |                   |                   |
               |                   |                   | increment(1)
               |  (increment, 1)   |                   |
               +--------->---------+   (increment, 1)  |
               |                   |---------<---------+
               +---------<---------+                   |
               |      (ack, 2)     |--------->---------+
               |                   |     (ack, 3)      |
               |                   |                   |
               v                   v                   v

Длина только этих двух последних блок-схем уже в значительной степени объясняет, почему increment в этом сценарии намного быстрее: для этого сделана операция increment, поэтому проводной протокол гораздо ближе отражает то, что мы пытаемся выполнить sh. И эта простота приводит к разнице в производительности в 3–4 раза только в одном моем простом тесте и, вероятно, еще больше в сценарии производства ios.

Конечно, транзакции по-прежнему полезны, так как их гораздо больше c операции, а не просто увеличение / уменьшение.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...