company_banner

R2DBC Arabba-RELEASE — новый взгляд на реактивное программирование для SQL


    Поздравляем Хабр с выходом R2DBC версии Arabba-RELEASE! Это самый первый стабильный релиз проекта.


    R2DBC (Reactive Relational Database Connectivity) — открытый проект, посвященный реактивному программированию для SQL. Разработчики R2DBC готовили первую версию спецификации целых два года! В этом хабрапосте мы поговорим, зачем нужен R2DBC, и что происходит в проекте прямо сейчас.


    В сообществе Java-разработчиков отношение к реактивщине традиционно крайне неоднозначное. Реактивное программирование даёт значительный прирост к масштабируемости приложения благодаря концепту конвейерной обработки данных. Но это также означает повышенный порог вхождения, а также код, который выглядит совершенно иначе, чем «традиционный» императивный код.


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


    Реактивные приложения, работающие с SQL базами данных, обычно используют JDBC, которое является стандартом для экосистемы JVM. В свою очередь, JDBC дает возможность использовать фреймворки, которые строятся на нем и предоставляют абстракции, позволяющие работать на более высоком уровне и не отвлекаться на технические аспекты общения с базой данных. JDBC — это блокирующий API. Если использовать JDBC в реактивном приложении, то приходится перекладывать блокирующие вызовы на ThreadPool. Как вариант, можно и не использовать JDBC, а напрямую работать с конкретными драйверами баз данных. Но, так или иначе, мы оказываемся перед дилеммой:


    • Либо мы используем привычные фреймворки, но упираемся в блокирующие драйвера и работу с ThreadPool
    • Либо мы используем неблокирующие драйвера конкретной базы, но теряем возможность использовать JDBC фреймворки

    Оба варианта ставят разработчиков в неудобное положение, т.к. ни один из них не решает проблему полностью. Поэтому для создания стандартизированного реактивного API для SQL и был создан R2DBC. Он состоит из спецификации и API. Оба этих компонента описывают, как сделать R2DBC-совместимый драйвер и что разработчики фреймворков могут ожидать от R2DBC в плане функциональности и поведения. R2DBC предоставляет фундамент для подключаемых драйверов.


    Зависимости


    R2DBC использует Java 8 и требует наличия внешней зависимости на Reactive Streams, потому что в Java 8 нет нативного API для реактивного программирования. Начиная с Java 9, Reactive Streams стала частью самой Java и появился Flow API, поэтому будущие версии R2DBC смогут мигрировать на Flow API сразу же, как только переключатся на Java 9, что позволит R2DBC стать спецификацией без зависимостей на внешние библиотеки.


    Структура R2DBC


    R2DBC определяет поведение и набор основных интерфейсов, которые используются для интеграции между R2DBC-драйвером и кодом, который к нему обращается. Вот они:


    • ConnectionFactory
    • Connection
    • Statement
    • Result
    • Row

    Помимо этих интерфейсов, R2DBC идет с комплектом разбитых на категории исключений и интерфейсов метаданных, предоставляющих подробные сведения о драйвере и базе данных.


    Главная точка входа в драйвер — ConnectionFactory. Она создает Connection, который позволяет общаться с базой данных.


    R2DBC использует обычный ServiceLoader из Java чтобы найти драйверы, лежащие на classpath-е. В приложении, ConnectionFactory можно получить из URL:


    ConnectionFactory connectionFactory = ConnectionFactories
                    .get("r2dbc:h2:mem:///my-db?DB_CLOSE_DELAY=-1");
    
    public interface ConnectionFactory {
        Publisher<? extends Connection> create();
        ConnectionFactoryMetadata getMetadata();
    }

    Запрос на получение Connection запускает неблокирующий процесс, соединяющийся с нижележащей базой данных. Сразу после подключения, это соединение используется для контроля транзакционного состояния, или просто чтобы запустить Statement:


    Flux<Result> results = Mono.from(connectionFactory.create()).flatMapMany(connection -> {
                return connection
                        .createStatement("CREATE TABLE person (id SERIAL PRIMARY KEY, first_name VARCHAR(255), last_name VARCHAR(255))")
                        .execute();
    });

    Давайте посмотрим на интерфейсы Connection и Statement:


    public interface Connection extends Closeable {
        Publisher<Void> beginTransaction();
        Publisher<Void> close();
        Publisher<Void> commitTransaction();
        Batch createBatch();
        Publisher<Void> createSavepoint(String name);
        Statement createStatement(String sql);
        boolean isAutoCommit();
        ConnectionMetadata getMetadata();
        IsolationLevel getTransactionIsolationLevel();
        Publisher<Void> releaseSavepoint(String name);
        Publisher<Void> rollbackTransaction();
        Publisher<Void> rollbackTransactionToSavepoint(String name);
        Publisher<Void> setAutoCommit(boolean state);
        Publisher<Void> setTransactionIsolationLevel(IsolationLevel level);
        Publisher<Boolean> validate(ValidationDepth depth);
    }
    
    public interface Statement {
        Statement add();
        Statement bind(int index, Object value);
        Statement bind(String name, Object value);
        Statement bindNull(int index , Class<?> type);
        Statement bindNull(String name, Class<?> type);
        Publisher<? extends Result> execute();
    }

    Запустив этот Statement, на выходе получаем некий Result. Он содержит информацию либо о количество измененных строк в таблице или сами строки:


    Flux<Result> results = …;
    Flux<Integer> updateCounts = results.flatMap(Result::getRowsUpdated);

    Строки можно обрабатывать потоково. Другими словами, строки появляются сразу же, как драйвер получил и расшифровал эту строку на уровне протокола. Чтобы обработать строки, нужно написать какую-то функцию преобразования, которая будет применяться к каждому расшифрованному Row. Эта функция может извлечь произвольное количество значений и вернуть либо скаляры, либо материализованный объект:


    Flux<Result> results = …;
    Flux<Integer> updateCounts = results.flatMap(result -> result.map((row, rowMetadata) -> row.get(0, Integer.class)));

    Посмотрим на интерфейсы Result и Row:


    public interface Result {
        Publisher<Integer> getRowsUpdated();
        <T> Publisher<T> map(BiFunction<Row, RowMetadata, ? extends T> mappingFunction);
    }
    
    public interface Row {
        Object get(int index);
        <T> T get(int index, Class<T> type);
        Object get(String name);
        <T> T get(String name, Class<T> type);
    }

    R2DBC построен на основе Reactive Streams, следовательно, для правильной обработки результатов работы R2DBC стоит использовать реактивную библиотеку. Голый Publisher практически непригоден для этого. Все примеры кода в этой статье используют Project Reactor.


    Облать применения спецификации


    R2DBC определяет, как именно должны конвертироваться типы между базой данных и JVM, как конкретные реализации R2DBC должны вести себя. В нем есть гайдлайны по обеспечению совместимости, позволяющие конкретной реализации пройти TCK. R2DBC во многом вдохновлялся JDBC. Именно поэтому, использование R2DBC может показаться вам вполне обычным и привычным. В спецификации затронуты следующие темы:


    • Driver SPI and TCK (Technology Compatibility Kit)
    • Integration with BLOB and CLOB types
    • Plain and Parameterized Statements („Prepared Statements“)
    • Batch operations
    • Categorized Exceptions (R2dbcRollbackException, R2dbcBadGrammarException)
    • ServiceLoader-based Driver Discovery
    • Connection URL scheme

    Спецификация так же позволяет реализовывать расширения которые могут быть опционально реализованы драйверами в которых есть возможность поддержать эти расширения.


    Экосистема


    R2DBC начался весной 2018 года как спецификация на драйвер Postgres. Сразу же после изначального ревью, рабочая группа R2DBC осознала, какой вклад R2DBC может сделать в этой области вообще, и поэтому он вырос в целую стандартную спецификацию. Несколько проектов поддержало эти идеи созданием драйверов и библиотек, предназначенных для использования вместе с R2DBC:


    Драйверы


    • Google Cloud Spanner
    • H2
    • Microsoft SQL Server
    • MySQL
    • Postgres
    • SAP HANA

    Библиотеки


    • R2DBC Pool (Connection Pool)
    • R2DBC Proxy (Observability Wrapper, похож на P6Spy и DataSource Proxy)

    Чтобы сделать свой драйвер R2DBC, в большинстве случаев нужно совершенно по-новому реализовать сетевой протокол, поскольку большинство JDBC-драверов используют внутри SocketInputStream и SocketOutputStream. Все такие драйвера — это очень молодые проекты, и использовать их нужно с большой осмотрительностью. Oracle недавно, на конференции Code One, рассказали о планах на драйвер OJDBC 20, сразу после новостей о прекращении работ над ADBA. Оракловский драйвер OJDBC20 будет поставляться с несколькими реактивными расширениями, вдохновленными работой над ADBA и обратной связью от рабочей группы R2DBC, поэтому этот драйвер можно будет использовать в реактивных приложениях.


    Несколько поставщиков баз данных также заинтересованы в создании R2DBC-драйверов.


    То же самое справедливо и для фреймворков. Проекты вроде R2DBC Client, kotysa и Spring Data R2DBC — все они позволяют использовать R2DBC в приложениях. Другие библиотеки, например, jOOQ, Micronaut и Hibernate Rx — уже в курсе о существовании R2DBC и тоже хотят когда-нибудь проинтегрироваться с ним.


    Что почитать, где забрать?


    Начать можно вот с чего:



    R2DBC представляет из себя release train Arabba-RELEASE, состоящий из модулей:



    Артефакты этого релиза:



    При использовании Maven, нужно добавить pom.xml следуюище строки:


    <dependencyManagement>
      <dependencies>
        <dependency>
          <groupId>io.r2dbc</groupId>
          <artifactId>r2dbc-bom</artifactId>
          <version>Arabba-RELEASE</version>
          <type>pom</type>
          <scope>import</scope>
        </dependency>
      </dependencies>
    </dependencyManagement>
    
    <dependencies>
      <dependency>
        <groupId>io.r2dbc</groupId>
        <artifactId>r2dbc-postgresql</artifactId>
      </dependency>
    
      <dependency>
        <groupId>io.r2dbc</groupId>
        <artifactId>r2dbc-pool</artifactId>
      </dependency>
    </dependencies>

    Что дальше?


    R2DBC Arabba-RELEASE — это первый стабильный релиз открытого стандарта. Развитие спецификации на этом не останавливается: хранимые процедуры, расширения транзакций, спецификация событий базы данных (таких как Postgres Listen/Notify) — это всего несколько из тем, запланированных на следующую версию, R2DBC 0.9.


    Присоединяйтесь к сообществу — это позволит не только следить за разработкой, но и поучаствовать в ней!

    • +28
    • 4,7k
    • 9
    JUG Ru Group
    255,62
    Конференции для программистов и сочувствующих. 18+
    Поделиться публикацией

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

      +2

      Традиционный первый комментарий: скрипач не нужен, родной, он только топливо жрёт. Скоро подвезут файберы и корутины, и всё само заработает, а мы будем ничего не делать и получать свои 300к/сек. Верно, bsideup ?

        +1

        Конечно, конечно… нет :D
        Файберы и корутины упрощают для юзера, а реактивщина — делает чтобы быстро было!
        Одно другому не мешает ;)


        ИМХО файберы виртуальные потоки только улучшат adoption реактивных технологий — потому что можно будет не бояться "что-то там заблокировать", а всякие flatMap concatMap превратятся в обычный map.

        0
        к сожалению ещё сырой продукт и не особо быстрый :( как PoC отлично все, но для продакшена ещё рановато.
          +2

          А не поделитесь ссылками чтобы подтвердить своё мнение? На открытые баг репорты, бенчмарки.


          А то иначе выглядит как вброс ;)

            +3
            Вот сравнение производительности: github.com/r2dbc/r2dbc-postgresql/issues/138
            так же можно посмотреть: www.techempower.com/benchmarks/#section=data-r18&hw=ph&test=db

            Из проблем с чем я столкнулся: github.com/r2dbc/r2dbc-postgresql/issues/207 и не обленился сделать ишью.
            Так же периодически зависали конекты и приходилось убивать джава процесс.
            Если сделать матрешку из конекшена, пула и прокси то у меня на мультитредовых тестах вылетала 34000 постгрес ошибка, хотя никакими курсорами я не пользовался. Стоило убрать либо прокси либо пул и все становилось хорошо.
            Периодически получал ошибку, что конекшен неожиданны был закрыт.

            До текущего релиза шел в комплекте r2dbc-client, а сейчас то-ли на него забили то-ли еще что, но в bom его уже нет. Что также неприятно.
              +1

              За репорт — респект!
              Про "мультитредовые тесты" — надеюсь тоже репортил.


              По ссылке на TechEmpower (который я кстати не очень люблю, учитывая что он был создан чтобы показать какой их фреймворк быстрый и ни раз был замечен делающим некорректные бенчмарки для других) r2dbc не нашёл.
              Опять же, стоит мерять производительность реальных реактивных сервисов (JDBC + Thread Pool vs R2DBC), где каждый вызов JDBC будет идти через context switch т.к. блокировать текущий реактивный поток нельзя.


              В "песочнице" R2DBC vs JDBC показывает себя неплохо (особенно с MSSQL, кстати), а так же есть куда оптимизировать. В таких вещах обычно первым идёт "сделать чтобы работало" а потом уже "чтобы быстро".

                +1
                Про «мультитредовые тесты» — надеюсь тоже репортил.

                не — я понял, что надо убирать r2dbc иначе в проде будет беда и в авральном режиме переписывал все на vertx.io/docs/vertx-pg-client/java

                В таких вещах обычно первым идёт «сделать чтобы работало» а потом уже «чтобы быстро».

                Согласен, поэтому и написал, что в качестве PoC все смотрится красиво :)
                  0
                  учитывая что он был создан чтобы показать какой их фреймворк быстрый

                  А какой из фреймворков их?

            0
            Спасибо, будем посмотреть!

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

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