Весь квест вращается вокруг простого, но сложно ответного вопроса: «Есть ли смысл использовать R2DBC?»

Погуглив, можно найти несколько бенчмарков, которые точно утверждают, что R2DBC великолепен с высокой степенью параллелизма. Однако эти тесты основаны на сценарии, который очень далек от реального приложения с интенсивным использованием баз данных. Обычно они проверяют простое «get» на таблице базы данных: это слишком упрощенно.

  • Поэтому я написал свои собственные варианты использования, используя SPRING-BOOT в блокирующей (JDBC) и реактивной (R2DBC) манере.
  • Развернул все с помощью docker-compose в контейнерной среде.
  • Проверено несколько раз, изменяя некоторые важные параметры

И я пришел к выводу, что утверждение «R2DBC быстрее с высокой степенью параллелизма» в основном неверно.

ВВЕДЕНИЕ

Мое развертывание тестовой среды содержит следующие контейнеры:

Я использовал GRAFANA и INFLUXDB, чтобы составить график полученных результатов с очень красивой конфигурацией, поддерживающей графики, подобные приведенному ниже:

Развернутая служба базы данных POSTGRES содержит достаточно простую схему, которая автоматически создается при запуске контейнера.

NGINX был помещен туда только для балансировки нагрузки между несколькими контейнерами одного вида — если таковые имеются. На самом деле тестируются только контейнеры JDBC и R2DBC. (и был настроен для полной работы в условиях высокого давления)

Нагрузочные тесты были выполнены с использованием временного контейнера K6, отличающегося от других запусков путем указания разных параметров ENV.

КЛАССИЧЕСКИЙ ТЕСТ

Давайте начнем с тестирования простого HTTP-запроса GET непосредственно на книжном столе, как это делается в различных сценариях, найденных в Интернете. НО…. Я также параметризовал количество элементов базы данных, извлекаемых из одного GET, и вот тут ничья!

Начнем с одного контейнера со следующими конфигурациями:

spring-jdbc:
  build:
    context: ../spring-jdbc
  depends_on:
    - db-service
  restart: always
  environment:
    - POOL_SIZE=20
  ports:
    - "8080:8080"
  deploy:
    replicas: 1
    resources:
      limits:
        cpus: 2
        memory: 2G
      reservations:
        memory: 2G

spring-r2dbc:
  build:
    context: ../spring-r2dbc
  depends_on:
    - db-service
  restart: always
  environment:
    - POOL_SIZE=20
  ports:
    - "8081:8080"
  deploy:
    replicas: 1
    resources:
      limits:
        cpus: 2
        memory: 2G
      reservations:
        memory: 2G

Давайте проведем несколько тестов, изменив эти параметры: количество одновременных пользователей, обращающихся к конечным точкам, и количество элементов, извлеченных из базы данных.

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

Database Items retrieved: 
         
       6000 / 3000 / 1500
Troughput:
  Case 1: 500 users
  - R2DBC: 4083 / 5373 / 10689
  - JDBC:  3321 / 5447 / 22765
  Case 2: 250 users

  - R2DBC: 4385 / 7364 / 15152
  - JDBC: 3520 / 6944 / 20255

  Case 3: 100 users

  - R2DBC: 4057 / 7636 / 15341
  - JDBC: 3247 / 8835 / 22079

  Case 4: 10 users

  - R2DBC: 4541 / 8823 / 16111
  - JDBC: 6841 / 12031 / 26349

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

Используйте реактивный стек только тогда, когда имеет смысл обрабатывать ПОТОК данных, а не ТРЮЧОК :)

R2DBC кажется значительно лучше только при извлечении 3000 элементов более чем 100 одновременных пользователей. Это очень ограниченный сценарий. Существует ли множество приложений, которым требуется более 3000 строк базы данных одновременно с более чем 100 одновременными пользователями? Учитывая также, что разбиение на страницы запроса является опцией, ответ очевиден. Мне кажется тривиальным, что полностью реактивный стек не является хорошим выбором для подавляющего большинства веб-приложений, интенсивно использующих базы данных.

Оптимизация реактивного стека с использованием дополнительных очередей для минимизации использования ЦП и переключений контекста ЦП недостаточно хороша в сценариях, когда из БД извлекается мало строк. Всегда помните, что реактивный проект добавляет много накладных расходов на обработку, и, возможно, поэтому R2DBC в некоторых случаях кажется НАМНОГО медленнее.

ЗАКЛЮЧЕНИЕ

Теперь пойдем дальше. Что, если мы на самом деле реализуем два разных приложения, используя блокирующий стек Spring-JDBC и реактивный стек Spring-R2DBC?

Мы увидим это вместе с полным кодом среды тестирования и приложений во второй части этой статьи! Следите за обновлениями.

Обновление: вот вторая часть.



Как всегда, большое спасибо за чтение.