Пара слов про R2DBC и PostgreSQL

    В последнее время я опять вижу, что усилился хайп вокруг реактивного программирования в общем, и реактивной работе с Базами данных — в частности. У меня есть пара фраз, которые я бы хотел сказать по этому поводу.

    image

    Напомню, что было в предыдущих серииях. В конце 2017 вышел Spring 5, в котором появилась поддержка реактивного программирования. В частности, речь идет про WebFlux. Все стали резко пробовать эту технологию (типичная кривая хайпа), а потом в индустрии появилось понимание, что реактивщина нужна не всем. Главные усвоенные уроки:

    • Реактивное программирование более сложно в отладке
    • Разрабатывать реактивные системы — труднее, чем классические, блокирующиеся; если нечаенно заблокируешь thread — тебе конец. Именно поэтому и появился проект (один из авторов — bsideup )
    • Реактивное программирование, а в частности — эластичность, нужно в основном на больших нагрузках а-ля Netflix. Если у вас 100 RPS, наверное, вам следует писать код в блокирующем стиле — так дешевле и проще

    Кроме того, все поняли ещё одну штуку. Для того чтобы реактивное программирование действительно раскрылось, нужно чтобы вся система была реактивной, включая вашу база данных. Проблема в том, что у БД может вовсе и не быть реактивного, или хотя бы асинхронного API. Ну, или такое API может быть, но оно может быть не поддержено в вашем драйвере.

    Собственно, почти все вокруг меня сейчас разрабатывают системы на PostgreSQL, когда нужна классическая реляционная База данных. Это, как говорится, выбор по умолчанию. С PostgreSQL мы работаем с помощью старого (ему больше 10 лет) JDBC драйвера — pgjdbc. Этот драйвер всем хорош. Там практически нет багов. Этот драйвер сильно улучшен по производительности (спасибо за это в том числе и vladimirsitnikov ). Но в pgjdbc есть один фатальный недостаток — нет поддержки асинхронного API.

    В последние годы началась большая гонка по разработке альтернативных драйверов для PostgreSQL, в которых предполагалась разработка асинхронного API. Даже Oracle попытался это сделать, но уже закрыл разработку в пользу проекта Loom и его Fibers. Но потом Oracle одумалась и начала делать это опять.

    Прямо сейчас есть 3 решения:

    1. https://github.com/jasync-sql/jasync-sql — асинхронный драйвер для PostgreSQL, написанный на Kotlin, где транспортом выступает Netty
    2. Решение, которое используют Vert.xhttps://github.com/mauricio/postgresql-async. Примечательно, что проект заархивирован, и предыдущий драйвер jasync-sql переписывает как раз таки именно его (правда, оригинал написан на Scala, а новое решение — на Kotlin).
      Update. Меня поправили в комментариях, клиент от Vert.x жив и чувствует себя хорошо в бенчмарках.
    3. Драйвер от команды Spring — https://github.com/r2dbc/r2dbc-postgresql. Этот драйвер сразу предоставляет реактивное API (а не асинхронное), а для транспорта, как и в jasync-sql, используется Netty.

    Концептуально, эти драйверы построены на похожих принципах. Все они используют исключительно существующие возможности PostgreSQL:


    К сожалению, драйверы не могут поменять Wire protocol Postgres (как бы этого не хотел Олег Докука — https://www.youtube.com/watch?v=n9tL2I_Big8). То есть, Postgres всё ещё требует отдельный Connection для SELECT запроса. Кроме того, Postgres всё также создает новый процесс для каждого Connection — https://www.postgresql.org/docs/current/tutorial-arch.html.

    Однако есть и хорошие новости. Модифицирующие запросы можно посылать пачкой, не дожидаясь результата (с помощью pipeline). Для такого случая не требуется Connection на каждый запрос.

    То есть, подытожим. Реактивные (или асинхронные) PG драйверы улучшают ситуацию в приложениях (мы теперь используем Netty based Non-blocking IO, не тратя по thread на запрос), но конкретно на стороне PostgreSQL — дела не так хороши (будем надеяться на улучшение сетевого протокола и архитектуры БД).

    На какой из драйверов стоит посмотреть более пристально? Если вы, как и я, используете Spring в работе — то, вероятно, r2dbc-postgresql — хороший выбор. Важно, что в последних версиях своего фреймворка, ребята из Pivotal довольно хорошо синтегрировали драйвер с привычными вещами, например, с Spring Data R2DBC (это пока ещё RC). В результате, нам не придется работать с низкоуровневым API драйвера и Пулом соединений, что понижает вероятность допустить ошибку в таком сложном проекте.

    Минимальный пример (который я успешно подсмотрел в документации) использования Spring Data R2DBC выглядит довольно привычно:

    1. Настраиваем credentials и connection pool:

    spring.r2dbc.username=postgres
    spring.r2dbc.password=P4$$W0RddD
    spring.r2dbc.url=r2dbc:postgresql://localhost:5432/r2dbc
    spring.r2dbc.pool.enabled=true
    spring.r2dbc.pool.initial-size=10
    spring.r2dbc.pool.max-idle-time=1m
    spring.r2dbc.pool.max-size=30
    spring.data.r2dbc.repositories.enabled=true
    

    2. Создаем сущность:

    public class Customer {
        @Id
        private Long id;
    
        private String firstName;
    
        private String lastName;
    
        public Customer(String firstName, String lastName) {
            this.firstName = firstName;
            this.lastName = lastName;
        }
    }
    

    3. Создаем привычный нам Репозиторий:

    public interface CustomerRepository extends ReactiveCrudRepository<Customer, Long> {
    }
    

    4. Используем репозиторий:

    @RestController
    @RequestMapping("/")
    public class CustomerController {
    
        private CustomerRepository customerRepository;
    
        @PostMapping("/customer")
        Mono<Void> create(@RequestBody Publisher<Customer> customerFlux) {
            return customerRepository.saveAll(customerFlux).then();
        }
    
        @GetMapping("/customer")
        public Flux<Customer> list() {
            return customerRepository.findAll();
        }
    
        @GetMapping("/customer/{id}")
        public Mono<Customer> findById(@PathVariable Long id) {
            return customerRepository.findById(id);
        }
    }
    

    Полный пример можно найти в этом репозитории — https://github.com/Hixon10/spring-boot-r2dbc-postgresql-example.

    Напоследок, давайте поговорим о важном: нужно ли использовать r2dbc сейчас в ваших приложениях? На мой взгляд — пока рановато. Я бы подождал, когда набор технологий (драйвер, Spring DATA) зарелизятся, и собирут первые шишки. Я думаю, что в 2021 году уже можно будет смотреть на этот драйвер более пристально.

    Текущее состояние дел — целиком реактивная система (Spring WebFlux + R2DBC database driver) выглядит очень круто. Буквально сегодня olegchir опубликовал дайджест, в котором есть ссылка на статью с бенчмарками — https://technology.amis.nl/2020/04/10/spring-blocking-vs-non-blocking-r2dbc-vs-jdbc-and-webflux-vs-web-mvc/. В кратце — реактивщина побеждает.
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 8

      +2

      Стоит указать, что реактивщина побеждает в случае Spring. Предыдущая статья того же автора: https://technology.amis.nl/2020/03/27/performance-of-relational-database-drivers-r2dbc-vs-jdbc/ не менее интересна — там в гонке добавлен Quarkus (JDBC & R2DBC) и он в варианте JDBC бъет спринг с реактивщиной. Интересно посмотреть и на компромисы — процессор против памяти против пропускной способности.

      +3
      Vertx клиент никто не архивировал и он активно развивается: github.com/eclipse-vertx/vertx-sql-client
      R2DBC для продакшена еще не готов, то там то здесь вылазят баги — лично сталкивался и постил им тикеты. Да и проскальзывало, что разработчики сосредоточены на доведением его до ума, а не попытках выжать максимум производительности.
      Так же Вертх очень быстр: www.techempower.com/benchmarks/#section=data-r15&hw=ph&test=db
      Единственное пришлось сделать свою прослойку чтобы удобно работать с ним через Flux/Mono.
        +1
        Ох, что-то я пропустил этот клиент от Vertx. Спасибо!
        +2

        я тут накидал проект для теста *без веба и запросов, чтобы исключить влияние блокирующего и неблокирующего "веба" и вынес базу на внешний сервер, чтобы он не мешал бенчмарку. VertX vs R2DBC vs JDBC Postgres, все с пулом соединений. Результаты:
        1) Очень сильно зависит от пула соединений. Это важно, потому что иногда можно позволить себе большой пул, а иногда нельзя. И пул все равно всегда используется. Пул для JDBC можно выбрать, а для реактивных — только по одному поддерживаемому.
        2) Сильно зависит от количества конкурентных акторов.
        3) Сильно зависит от того, медленные запросы в базу или быстрые.


        В двух словах для старого linux jdk 11 ноута: затраты на переключение потоков невелики, пока их сотни. На тысячах начинает проявляться, если пул к базе относительно большой, но все равно довольно слабо влияет на общее время. Да, во JFR/JMC потоки на jdbc красненькие все, а в reactive все зелененькое, но толку от красок? Памяти жрет больше с потоками. Быстрее всего vertx-pg, медленнее всех r2dbc. Но на отдельных тестах все могут давать одинаковые результаты или вырываться вперед/отставать.
        Ну и надо смотреть на конкретные места — маршаллинг данных, сколько прилетает в ответе данных, нужны ли транзакции, сколько запросов в транзакции. Тюнинг пула дает больше всего — там и настройки кэширования prepared statement могут быть, и прочая и прочая....

          0
          Спасибо за отличный комментарий, который, как обычно на хабре бывает, полезнее статьи :)

          Не хотите написать свою статью, с этими бенчмарками? Может, кто-то еще что-то интересное расскажет, а то в этой статье как-то совсем пусто получилось :(
            +4

            демотивирован — за прошлую статью получил плюсов в статью и минусов в карму. Хорошо хоть на минимуме для голосования остался.
            А подобная статья с базами — тема холиварная, не упомянешь какую-то деталь вроде "а вот -XXUncompressShit" или кто-то запутается в подписках реактора — у всех настроение испорчено. Слишком обширная тема, я пока остыну и, может, соберусь…
            А вот GitHub, если интересно.

          0

          У меня результаты получились совсем другими. JDBC выигрывает по перфомансу всегда.
          https://link.medium.com/trTOA3bjU6
          Здесь отличное обсуждение https://github.com/spring-projects/spring-data-r2dbc/issues/203

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

          Самое читаемое