Две альтернативы JDBC

    Примеры для двух неблокирующих библиотек доступа к SQL базам данных — Vert.X SQL и R2DBC. Примеры будут основаны на PostgreSQL и реактивных обертках Java.



    Начнем с главного: JDBC — это отличный стандарт. Служил и служит верой и правдой.


    Но новые веяния просят новых решений. И даже есть смысл иногда потеснить JDBC.


    Я предлагаю взглянуть на две альтернативных реализации доступа к SQL базам из JVM приложений и их (реализаций) применимости.


    В статье будет теоретическая часть, практические примеры и в конце немного сравнения. Кому интересна практическая часть — просто перематывайте текст, к конкретной реализации теория отношение имеет весьма посредственное.


    Пояснения к материалу


    Базу данных я взял отсюда. Её я загрузил на отдельный сервер, чтобы сервер БД (PostgreSQL 12) не мешал смотреть на работу клиента. Производительность приложения зависит от версии СУБД, от схемы данных, от количества данных в ответе, от типов данных и, наверное, много чего еще.


    Также не стоит забывать, что производительность самого JDK меняется — для потоков OpenJDK 11, который я использовал, не обязательно такая же для Oracle JDK 8, например.


    Для эмуляции медленных запросов можно использовать вызов pg_sleep(seconds) из PostgreSQL.


    Не стоит экспериментировать с разными библиотеками в одном модуле — могут быть конфликтующие зависимости (netty). Есть различия даже в patch-версиях, которые сломают работу. Это потенциально может стать сильной головной болью, если netty используется в других частях проекта, например, при обработке HTTP-запросов.


    В примерах выполняется по одному запросу, без транзакций. Рассмотрение более сложных случаев выходит за рамки статьи. Стоит отметить, что как и в JDBC, транзакции привязываются к соединению. То есть пока соединение полностью не использовано и не возвращено в пул, его использовать для других запросов нельзя, как и в JDBC. Использовать менеджеры транзакций без серьезного изучения не стоит — как правило, они используют ThreadLocal для поддержки транзакций, что для данных библиотек недопустимо.


    Полностью код, использованный в качестве примера тут, можно найти у меня на GitHub.


    Если разрабатывается конечное приложение, то скорость и производительность имеет смысл мерять в полной связке со всеми частями. Например, если есть веб-фреймворк, то стоит мерять производительность в связке с ним, а не "чистый" доступ к данным. Может оказаться, что какой-то фреймворк лучше работает с какой-то одной библиотекой, даже если "вчистую" выигрывает другая.


    Зачем нужны неблокирующие вызовы. Теория.


    Неблокирующие вызовы — это про распределение ресурсов, а не про скорость. Скорость иногда приходит, но в целом это больше про масштабирование. Соответственно, лучше всего проявляет это себя при масштабировании.


    Вот говорят, что потоки очень "тяжеловесные", что переключение контекста — это накладные расходы. Что-то в этом есть. Проявляется это на тысячах, в лучшем случае, потоках. С десятками тысяч проблемы заметны, с сотнями — их не избежать. Так что первое, о чем стоит подумать при выборе технологии доступа — а нужно ли масштабироваться до таких значений? Иногда это нужно, иногда о таких объемах речи не идет и смысл в использовании более сложного API теряется. Под простым API я подразумеваю не JDBC, а готовые решения, которые работают поверх него — JPA, JOOQ, Hibernate ORM и подобные.


    Проблемы с количеством потоков могут возникать не из-за высокой частоты запросов, но и из-за "бутылочных горлышек" — один путь исполнения может задействовать доступ к базе и блокировать поток из пула, который в обычных условиях освободился бы раньше. Например, каждый десятый запрос лезет в базу и виснет. Раньше у нас приложения были глупые и всегда лезли в базу и уж если там заблокировало — пропало все (упрощаю картинку). Сейчас у нас куча интеграций и мы лезем из кода во все стороны, во всякие разные сервисы и базы, а управлять отдельными пулами потоков нам все еще неудобно. Именно такие случаи "бутылочных горлышек" могут доставить гораздо больше проблем, чем много конкурентых запросов — например, фиксированный пул потоков может исчерпаться не по причине недоступности базы, а когда поток "утек" и завис на другой операции.


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


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


    Еще хотелось бы отметить, что "неблокирующий" относится именно к "потоку", а не к чему-то абстрактному. Используя "неблокирующие" операции мы все равно чего-то будем ждать. Очень просто — у нас ограничено количество соединений к базе данных. Блокирующий или неблокирующий у нас ввод-вывод, а "одновременно" будет выполняться только то, что позволит база данных и пул соединений. Ждать будем все равно, хотя потоки не будем блокировать — что позволит их использовать для работы над чем-то, не требующим доступа к базе.


    Второе, о чем стоит подумать — это общий стиль работы приложения. Если приложение основано на неблокирующих операциях, то использование блокирующих вызовов помимо того, что просто некрасиво, может привести к дополнительным ошибкам — как в "Примере 2". Впрочем, об этом твердят на каждом втором митапе о неблокирующих библиотеках и останавливаться на этом не стоит.


    Что плохого в неблокирующих библитеках. Теория.


    Первое: Плохо, что высокоуровневых библиотек/фреймворков практически нет (только Spring Data R2DBC нашел). Для JDBC есть удобные дополнительные абстракции — JPA, JOOQ, Hibernate ORM, которые здорово облегчают жизнь разработчика. Там и кэш запросов, и метрики, и проверки типов ну и всякое, написанное за много лет. Пулы соединений, менеджеры транзакций — все это либо отсутствует, либо безальтернативно.


    Второе: Это не стандартно и пока плохо поддерживается. Под этим нужно понимать как минимум две вещи:


    • производители баз данных отдают приоритет JDBC драйверам, так что не пишут или запаздывают с обновлениями, исправлениями и новыми возможностями баз в альтернативные решения.
    • не всякую базу данных можно использовать в принципе. Например, Oracle никак не поддерживается.

    Vert.X SQL (PG) Client.


    Набор библиотек от Eclipse, страница GitHub и документация для Postgres.
    Особенности:


    • поддерживаются PostgreSQL, MySQL, MSSQL, DB2
    • основана на callback, имеет поддержку RxJava2. Не уверен насчет корутин Kotlin, но написать руками несложно.
    • Работает на vert.x фреймворке, так что должна естественно вписываться в Quarkus.
    • Быстрая.

    Ссылка к последнему пункту


    Не поддерживаются встраиваемые базы данных вроде H2, что затрудняет использование в автоматических тестах. Есть недавняя отличная статья о том, что тестировать надо на той базе, которая в продакшне, но это не абсолютный рецепт успеха и не единственный возможный вариант.


    Добавим зависимости для клиента, реактивному (rxjava2) его варианту и сетевой транспорт для моей машины:


        <dependency>
          <groupId>io.vertx</groupId>
          <artifactId>vertx-pg-client</artifactId>
          <version>3.9.0</version>
        </dependency>
        <dependency>
          <groupId>io.vertx</groupId>
          <artifactId>vertx-rx-java2</artifactId>
          <version>3.9.0</version>
        </dependency>
        <dependency>
          <groupId>io.netty</groupId>
          <artifactId>netty-transport-native-epoll</artifactId>
          <version>4.1.15.Final</version>
          <classifier>linux-x86_64</classifier>
        </dependency>

    Будем использовать пул и реактивный вариант API:


    import io.vertx.pgclient.PgConnectOptions;
    import io.vertx.reactivex.pgclient.PgPool;
    import io.vertx.reactivex.sqlclient.Pool;
    import io.vertx.reactivex.sqlclient.Tuple;
    import io.vertx.sqlclient.PoolOptions;

    Настройка соединения. Важно указать, что надо кэшировать prepared statement на уровне пула — это хорошо поднимает производительность.


            PgConnectOptions connectOptions = new PgConnectOptions()
                    .setPort(5432)
                    .setHost(host)
                    .setDatabase("postgres")
                    .setCachePreparedStatements(true)
                    .setPreparedStatementCacheMaxSize(1000)
                    .setSsl(false)
                    .setUser(user)
                    .setPassword(password);

    И пула с размером pool (например, 50).


           PoolOptions poolOptions = new PoolOptions()
                    .setMaxSize(pool);
            Pool client = PgPool.pool(connectOptions, poolOptions);

    Будем называть операции с асинхронным ответом попросили. Процесс вытаскивания данных выглядит примерно так:


    1. попросили дать соединение из пула
    2. получили соединение, попросили приготовить запрос
    3. получили подготовленный запрос, попросили выполнить этот запрос с параметрами в Tuple
    4. получили RowSet, который является Iterable
    5. прошлись результатам и вытащили значение по имени колонки.
    6. вернули соединение в пул, "закрыв" его.

    При необходимости использовать транзакции их можно начать на пуле соединений.
    Параметры запроса указываются как $1 $2 $3 и так далее. Методы, начинающиеся с rx — обертки для RxJava2, которые предоставляет vert.x. Без них можно сыграть в callback hell. Если писать на RxJava2 обертке, выйдет вот так:


            Flowable<String> titles = client.rxGetConnection()
                    .flatMapPublisher(connection ->
                            connection.rxPrepare("SELECT title FROM nicer_but_slower_film_list WHERE FID = $1")
                                    .flatMap(statement -> statement.query().rxExecute(Tuple.of(Math.abs(random.nextInt(990)))))
                                    .flattenAsFlowable(Functions.identity())
                                    .map(row -> row.getString("title"))
                                    .doOnError(Throwable::printStackTrace)
                                    .subscribeOn(Schedulers.computation())
                                    .doFinally(connection::close));

    После всего пул можно закрыть


    client.close();

    R2DBC


    → Страница проекта


    Особенности:


    • поддерживаются — MariaDB, MySQL, PostgreSQL, MSSQL, H2
    • поддержка в Spring Data R2DBC, что упрощает разработку под Spring Boot
    • основано на Reactive Streams, поощряет использовние Project Reactor

    Для этого возьмем зависимости клиента и пула соединений:


        <dependency>
          <groupId>io.r2dbc</groupId>
          <artifactId>r2dbc-postgresql</artifactId>
          <version>0.8.2.RELEASE</version>
        </dependency>
        <dependency>
          <groupId>io.r2dbc</groupId>
          <artifactId>r2dbc-pool</artifactId>
          <version>0.8.2.RELEASE</version>
        </dependency>

    Вместе с пулом неявно придет и реактивный Project Reactor.


    Нам понадобятся следующие классы:


    import io.r2dbc.pool.ConnectionPool;
    import io.r2dbc.pool.ConnectionPoolConfiguration;
    import io.r2dbc.spi.ConnectionFactories;
    import io.r2dbc.spi.ConnectionFactory;
    import io.r2dbc.spi.ConnectionFactoryOptions;
    
    import reactor.core.publisher.Flux;
    import reactor.core.publisher.Mono;

    Строим фабрику соединений и пул. Prepared Statement кэшируются всегда, насколько я понял из исходников.


            ConnectionFactory connectionFactory = ConnectionFactories.get(ConnectionFactoryOptions.builder()
                    .option(DRIVER, "postgresql")
                    .option(HOST, host)
                    .option(PORT, 5432)  // optional, defaults to 5432
                    .option(USER, user)
                    .option(PASSWORD, password)
                    .option(DATABASE, "postgres")  // optional
                    .option(SSL, false)  // optional
                    .build());
            ConnectionPoolConfiguration configuration = ConnectionPoolConfiguration.builder(connectionFactory)
                    .maxIdleTime(Duration.ofMillis(1000))
                    .maxSize(poolSize)
                    .build();
    
            ConnectionPool pool = new ConnectionPool(configuration);

    Будем называть операции с асинхронным ответом попросили. Процесс вытаскивания данных выглядит примерно так:


    1. попросили дать соединение из пула
    2. получили соединение, подготовили запрос
    3. задали параметры запроса
    4. попросили выполнить этот запрос
    5. получили поток Result
    6. предоставили mapper-функцию для преобразования результата в объект и попросили дать объекты из результата запроса.
    7. вернули соединение в пул, попросив "закрыть" его.

    Важно соблюдать последний пункт — для возвращения соединения в пул или для закрытия недостаточно вызвать close, надо еще и подписаться на это событие. Параметры запроса указываются как $1 $2 $3 и так далее. В терминах Project Reactor это можно сделать так:


            Flux<String> titles = Flux.usingWhen(pool.create(), connection ->
                            Flux.from(
                                    connection.createStatement("SELECT title FROM nicer_but_slower_film_list WHERE FID = $1")
                                            .bind("$1", Math.abs(random.nextInt(990)))
                                            .execute()
                            ).flatMap(result -> result.map(RdbcTest::getTitle))
                    , Connection::close);        
    
        private static String getTitle(Row row, RowMetadata meta) {
            return row.get("title", String.class);
        }
    

    Ну и не забыть закрыть пул в конце.


           pool.close();

    Имитация нагрузки


    Я считаю, что "вчистую" гонять доступ к базе смысла мало, но все же на начальных этапах знакомства с технологией можно таким образом проверить какие-то специфические гипотезы и отловить какие-то баги. Буду запускать реактивные R2DBC, VertX и JDBC. Executions (скажем, 50 000) запросов, запуская одновременно только concurrency (скажем, 1000) запросов. Хвала backpressure.


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


    VertX
            Flowable.range(1, executions)
                    .doOnNext(i -> { if (i % chunk == 0) LOGGER.info("Process {}", i);})
                    .flatMap(i -> client.preparedQuery(
                            "SELECT title FROM nicer_but_slower_film_list WHERE FID = $1")
                                    .rxExecute(Tuple.of(Math.abs(random.nextInt(990))))
                                    .doOnError(Throwable::printStackTrace)
                                    .flattenAsFlowable(Functions.identity())
                                    .map(row -> row.getString("title").length())
                                    .doOnError(Throwable::printStackTrace)
                                    .subscribeOn(Schedulers.computation()),
                            false, concurrency)
                    .doOnComplete(() -> LOGGER.info("Done with VertX"))
                    .blockingSubscribe(unused -> {                }, Throwable::printStackTrace);

    R2DBC
            Flux.range(1, executions)
                    .doOnNext(i -> {  if (i % chunk == 0) LOGGER.info("Processing {}", i);})
                    .flatMap(i -> Flux.usingWhen(pool.create(),
                            connection -> Flux.from(
                                    connection.createStatement("SELECT title FROM nicer_but_slower_film_list WHERE FID = $1")
                                            .bind("$1", Math.abs(random.nextInt(990)))
                                            .execute()
                            )
                                    .flatMap(result -> Flux.from(result.map(RdbcTest::getTitle))),
                            Connection::close)
                                    .subscribeOn(Schedulers.parallel())
                                    .doOnError(Throwable::printStackTrace)
                            , concurrency)
                    .doOnError(Throwable::printStackTrace)
                    .doOnComplete(() -> LOGGER.info("Done with R2DBC"))
                    .blockLast();

    JDBC + Hikari Pool + Reactor
           HikariConfig config = new HikariConfig();
            config.setJdbcUrl("jdbc:postgresql://" + host + "/postgres");
            config.setMaximumPoolSize(poolSize);
            config.setIsolateInternalQueries(false);
    
            HikariDataSource ds = new HikariDataSource(config);
    
            Flux.range(1, executions)
                    .flatMap(Mono::just)
                    .flatMap(index -> Mono.fromCallable(ds::getConnection)
                                    .doOnNext(i -> { if (index % chunk == 0) LOGGER.info("Process {}", index);})
                                    .map(this::request).subscribeOn(Schedulers.elastic())
                            , concurrency
                    )
                    .subscribeOn(Schedulers.elastic())
                    .doOnError(Throwable::printStackTrace)
                    .doOnComplete( ()->LOGGER.info("Done with JDBC"))
                    .blockLast();
    
        private Integer request(Connection connection) {
            try {
                var s = connection.prepareStatement(
                        "SELECT title FROM nicer_but_slower_film_list WHERE FID = ?"
                );
                s.setInt(1, Math.abs(random.nextInt(990)));
                var results = s.executeQuery();
                int counter = 0;
                while (results.next()) {
                    counter += results.getString("title").length();
                }
                results.close();
                s.close();
                connection.close();
                return counter;
            } catch (RuntimeException e) {
                e.printStackTrace();
                throw e;
            } catch (Exception e) {
                e.printStackTrace();
                throw new IllegalStateException(e);
            }
        }

    Хочу выполнить и посмотреть на использованные ресурсы c помощью команды time:


    /usr/bin/time --verbose java ...

    и посмотреть результаты в Java Mission Control — для этого запущу Java Flight Recorder.


    java -XX:StartFlightRecording=disk=true,dumponexit=true,filename=/tmp/r2dbc.jfr,settings=profile,path-to-gc-roots=false,delay=1s,name=R2DBC ...

    Результаты time показывают время в userspace и в system, переключения контекста и еще несколько параметров. На моих запусках VertX вырывается далеко вперед, R2DBC и JDBC примерно одинаковы по времени выполнения, чаще переключает контекст. Это все сильно зависит от количества конкурентных "запросов" и размера пула (это самый главный фактор оказался у меня). Получалось, что временами R2DBC проводит в system больше времени, иногда в userspace. В рамках эксперимента я не стал копать глубже, почему так и почему vertx гораздо быстрее на этих прогонах.


    time R2DBC
            Command being timed: "java -jar r2dbc-1.0-SNAPSHOT-jar-with-dependencies.jar -iterations 50000 -concurrent 1000 -pool 50 -user anonymous -password 12345678 -host pg12.local"
            User time (seconds): 34.28
            System time (seconds): 5.55
            Percent of CPU this job got: 10%
            Maximum resident set size (kbytes): 307004
            Minor (reclaiming a frame) page faults: 76835
            Voluntary context switches: 121789
            Involuntary context switches: 9670

    time JDBC
            Command being timed: "java -jar jdbc-1.0-SNAPSHOT-jar-with-dependencies.jar -iterations 50000 -concurrent 1000 -pool 50 -user anonymous -password 12345678 -host pg12.local"
            User time (seconds): 38.72
            System time (seconds): 5.80
            Percent of CPU this job got: 76%
            Maximum resident set size (kbytes): 459688
            Minor (reclaiming a frame) page faults: 125453
            Voluntary context switches: 187752
            Involuntary context switches: 14221

    time VertX
            Command being timed: "java -jar vertx-1.0-SNAPSHOT-jar-with-dependencies.jar -iterations 50000 -concurrent 1000 -pool 50-user anonymous -password 12345678 -host pg12.local"
            User time (seconds): 19.06
            System time (seconds): 2.02
            Percent of CPU this job got: 20%
            Maximum resident set size (kbytes): 178516
            Minor (reclaiming a frame) page faults: 43054
            Voluntary context switches: 109914
            Involuntary context switches: 4691

    Что показывает Java Mission Control? JDBC требует больше памяти и чаще делает GC. Зато есть впечатляющие картинки для презентаций в разделе Threads.


    Прекрасные неблокирующие


    R2DBC:


    R2DBC


    VertX:


    VertX


    И "жуткий" JDBC:


    JDBC


    Все красное, все блокировано. Ужас вроде бы. Ну на это можно спросить "и что?". Сами по себе красные черточки ничего не значат и сами по себе проблемой не являются. Проблема, когда они мешают другим действиям приложения. Вот это очень вероятно, но это нужно доказывать в каждом случае отдельно.


    Надеюсь, что статья была полезна!

    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

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

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

      +1

      Давайте начнем с того что в java нет особых проблем с тредами. И на этом можно и закончить.

        +1

        Давайте начнем с того, что однопоточный NodeJS может лопатить тысячи запросов в секунду не отжирая гигабайты памяти. а в джава подход — зачем нам думать о памяти и производительности, нафигачим тысячи потоков и разрастемся на десятки ЕС2 инстансов.

          +2

          Вы вот сейчас рассказываете феерический бред. Во первых nodejs течет причем довольно сильно. И да сколько там у вас nodejs жрет? Давайте начнем с этого. В java если вы нормально написали приложение у вас вытекания памяти не будет. У меня было приложение которое работало nonstop несколько лет и не текло. В nodejs это вообще реально?

            +2

            Мне нравится ваш подход — возьмем кривое nodejs приложение и будем сравнивать с хорошо написанным джавовским :))) Речь то не об утекании памяти в кривых приложениях, а о том что вы не понимаете зачем нужен async non-blocking подход. Вы считаете что thread-per-request это лучшая практика и что создание тысячи тредов это нормальная ситуация. Готов с вами поспорить на ящик octomore 6.3 что вы пишете свое стандартное потоковое приложение с хождением в редис и постгрес, а я свое и потом мы запускаем все в докере с жесткими ограничениями типа 512 на CPU и 512Mb памяти на контейнер. И долбим к примеру 1000 конкурентных юзеров на это все — а дальше смотрим latency и error rate.

              –1

              А можно дурацкий вопрос? А сколько у нас будет ядер этой машине? Если больше двух то боюсь для вашего nodejs приложения это плохие новости. Вы надеюсь помните что не блокирующийся nodejs не умеет больше одного потока? А java умеет. И даже современный бюджетный процессор умеет запускать более двух потоков кода. И java это умеет ооочень давно.

                0

                Вы походу читаете через строку.


                ограничениями типа 512 на CPU и 512Mb памяти на контейнер.

                и писать я буду на джаве — какой нить springboot(чтобы пожирнее было :))) ), lettuce и vertx-pg-client. Но могу и на Ноде если вам так сильно хочется.

                  0

                  Там про количество ядер ни слова. Вы отлично должны понимать что в java проблем с многопоточностью нет, причем очень давно, так-как это основная ниша где она применяется. И в случае nodejs вам придется просто запускать пачку nodejs приложений и иметь блокировки на уровне СУБД. Получим в лучшем случае шило на мыло.

                    +1

                    512 CPU units это пол ядра :)
                    И без обид но вы вообще не в курсе что такое async non-blocking. Почитайте про epoll, что такое Netty и тд. Мне не надо стопяцот потоков чтобы читать/писать с сокетов. Я обойдусь всего парочкой — тк в IO bound задач нет проблем с ЦПУ тк основное время это записать-подождать-прочитать какую либо ИО задачу. Все ваши потоки будут просто стоять и ждать и кушать память. При 1000 конкуретных юзерах и thread-per-request у вас по дефолту отожрется 1Гб памяти только для хттп потоков.

                      0

                      Я вообще-то в курсе и более того пишу под это. И настраивал все это еще в apache. По этому знаю ограничения. В случае если у вас внезапно больше одного ядра, то async non-blocking может не утилизировать весь процессор и сеть. И если не использовать простую модель one request one process, то гибридная модель будет легко натягивать async non-blocking Хороший пример гибридной модели nginx.

                        0

                        Каша какая то — что такое гибридная модель? Это когда на каждое ядро запускаем event loop? а не на одном все делаем? Так это все равно как был async non-blocking так и остался. И чтобы закончить эту демагогию — готовы ли вы доказать преимущества тысячи потоков перед парочкой.
                        Я беру springboot reactive, lettuce, vertx-pg-client. Вы springboot, Jedis и PG JDBC — и смотрим на 512 юнитах цпу, 512Мб памяти контейнера(не хипа). Выигрывает кто больше обработает запросов за 5 минут на 1000 конкурентных юзерах.

                          0
                          Каша какая то — что такое гибридная модель? Это когда на каждое ядро запускаем event loop? а не на одном все делаем? Так это все равно как был async non-blocking так и остался.

                          Никакой каши. Проблема у вас в случае event loop будет возникать в том что в случае утилизации множества ядер потребуется как-то балансировать нагрузку. И в случае классического java модели и сервлета это проблемы особо не представляет, а вот в случае non-blocking есть определенные проблемы.


                          И чтобы закончить эту демагогию — готовы ли вы доказать преимущества тысячи потоков перед парочкой.

                          На нормальной машине где более одного потока исполнения, про что я сразу сказал. А не на куцем полпроцессора. Где обычно java и используется.


                          А если же будет 512 юнита цпу и 512Мб памяти то лучше взять вообще openresty с lapis фреймворк на lua. Жрать всего будет меньше В том числе и памяти. И да там тоже есть неблокирующие вызовы postgresql. А ну да и nginx я могу воспользоваться его моделью распределяющей нагрузку.


                          Дополнительно все сильно зависит от используемой загрузки. К примеру если вы начнете тянуть под миллион записей, у вас async non-blocking тоже может сливать, потому что банально оно однопоточный.


                          non-blocking не панацея, как и многопоточная модель. Если у вас много мелких запросов и много клиентов, он может быть лучше. А может и не быть. В случае многопоточной модели, производительность могут убивать медленные клиенты. Но в случае non-blocking они же будут жрать память.

                            –1

                            Не будет у меня возникать тк джавовские имплементации обычно делают два евент лупа на ядро. А в ноде запускают несколько процессов.


                            Проблема у вас в случае event loop будет возникать в том что в случае утилизации множества ядер потребуется как-то балансировать нагрузку.

                            А вы платите за эту нормальную машину из своего кармана? Представляете бывают проекты где инстансов микро-сервисов пару тысяч и вот таким горе программистам на каждый инстанс надо подавать по 16 ядер и 64Гб оперативки иначе в их понимании джава не взлетает.


                            На нормальной машине где более одного потока исполнения, про что я сразу сказал. А не на куцем полпроцессора. Где обычно java и используется.

                            А в Кафку как будем ходить? Или данные читать с какого нить AWS S3? Джава или Нода тем и хороши что много разработчиков и много различных библиотек доступны.


                            А если же будет 512 юнита цпу и 512Мб памяти то лучше взять вообще openresty с lapis фреймворк на lua. Жрать всего будет меньше

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


                            Дополнительно все сильно зависит от используемой загрузки.

                            Если тянуть по миллиону и одновременно куча клиентов то первее умрет Постгрес. и никакие потоки и ивент лупы тут вообще не спасут.


                            К примеру если вы начнете тянуть под миллион записей, у вас async non-blocking тоже может сливать, потому что банально оно однопоточный.

                            А кроме вас никто и не утверждал что есть серебряная пуля.
                            Помините фразу вашу — Давайте начнем с того что в java нет особых проблем с тредами. И на этом можно и закончить.


                            non-blocking не панацея, как и многопоточная модель.

                            Те вы будет ок про наш спор если мы оставляем 512Мб памяти и читерим до 2048 ЦПУ юнитов?


                            Но в случае non-blocking они же будут жрать память.
                              0
                              А вы платите за эту нормальную машину из своего кармана? Представляете бывают проекты где инстансов микро-сервисов пару тысяч и вот таким горе программистам на каждый инстанс надо подавать по 16 ядер и 64Гб оперативки иначе в их понимании джава не взлетает.

                              Если у меня есть ограничения про процессору и памяти, я бы сначала подумал зачем мне там вообще java.


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

                              Как обычно не читай сразу отвечай. Еще раз я сейчас пишу как раз под non-blocking со всеми вытекающими оттуда проблемами.


                              Те вы будет ок про наш спор если мы оставляем 512Мб памяти и читерим до 2048 ЦПУ юнитов?

                              Вполне норм. Имплементацию можете и сами набросать в том же spring она переключается банально заменой библиотек. Заодно и померять. Дополнительно стоит померять при 50% медленных клиентов. В том же jmeter есть. Вполне интересно в чем будет разница.

                                0

                                А у вас все проекты вида тратьте денег мульоны, нам пофиг на расходы? Или понять что хорошо когда бэкенд на одном языке написан и людей можно перекидывать с проекта на проект?


                                Если у меня есть ограничения про процессору и памяти, я бы сначала подумал зачем мне там вообще java.

                                Смотрите свое самое первое сообщение и думайте.


                                Как обычно не читай сразу отвечай. Еще раз я сейчас пишу как раз под non-blocking со всеми вытекающими оттуда проблемами.

                                Не, никто за вас свою часть не будет делать и тем более за интерес. А то накинуть на вентилятор это вы можете и писать кучу комментариев время есть. А как сделать так пусть другие?


                                Вполне интересно в чем будет разница.
                                  0
                                  А у вас все проекты вида тратьте денег мульоны, нам пофиг на расходы? Или понять что хорошо когда бэкенд на одном языке написан и людей можно перекидывать с проекта на проект?

                                  Мне просто без разницы на чем писать. На чем надо на том и напишу. Если не писал до этого нуу окей, просто будет более долгий старт.


                                  Не, никто за вас свою часть не будет делать и тем более за интерес.

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

                                    –1

                                    Ага — удачи в бизнесе с таким подходом. А давайте это забахаем на Расте, а это на Скале, вот этот кусок ради интереса сделаем на Хаскеле.


                                    Мне просто без разницы на чем писать. На чем надо на том и напишу. Если не писал до этого нуу окей, просто будет более долгий старт.

                                    Делаем два ендпоинта


                                    • в один случайным образом постим число 1..100 — оно записывается в Постгрес где ид это число, а второе поле текущий таймстемп, а так же эти же данные кладем в редис с ттл в 1 сек
                                    • вычитываем записи по случайному числу 1..100 — если есть то вовращаем данные с редиса, если там нет то с постгреса.
                                      Для сравнения нужно, чтобы реализации делали одно и то же. Если хотите чтобы я написал ну окей, я могу набросать, но вопрос упирается что надо?
                                      0
                                      Ага — удачи в бизнесе с таким подходом. А давайте это забахаем на Расте, а это на Скале, вот этот кусок ради интереса сделаем на Хаскеле.

                                      Прямо сейчас у меня основные языки это lua и js. Ну js еще ладно. Но первый так получилось :)


                                      Делаем два ендпоинта

                                      Уже сделали
                                      https://medium.com/@filia.aleks/r2dbc-vs-jdbc-19ac3c99fafa


                                      Но там надо еще две статьи что я выше привел добавить для получения целостной картины.

                                    0

                                    Хотя вообще говоря я сделал проще. Я погуглил


                                    Нашел три статьи
                                    https://medium.com/@filia.aleks/r2dbc-vs-jdbc-19ac3c99fafa
                                    https://technology.amis.nl/2020/03/27/performance-of-relational-database-drivers-r2dbc-vs-jdbc/
                                    https://technology.amis.nl/2020/04/10/spring-blocking-vs-non-blocking-r2dbc-vs-jdbc-and-webflux-vs-web-mvc/
                                    https://github.com/spring-projects/spring-data-r2dbc/issues/203


                                    Если кратко, скомпилировать. То на машине с малым количеством CPU и памяти будет лучше reactive. На большей машине с большим количеством CPU будет лучше JDBC.

                                      0

                                      Это касается R2DBC про который я не раз говорил в комментариях к схожим статьям, что он медленный и авторы этого не скрывают. И в продакшене его еще рано использовать.
                                      Так же в этой статье была приведена ссылка на производительность verxt client'a — https://www.techempower.com/benchmarks/#section=data-r18&hw=ph&test=db


                                      так же мы с вами говорим вообще за подход когда вся система реативная или когда поток потоком погоняет.

                                        0

                                        Тут вопрос больше в том как будет происходить масштабирование системы. Если масштабирование идет плодим кучу мелких машин и как-то между этим всем балансируем закладывая это в архитектуру, то nonblocking можно. Если же масштабирование "классическое", просто берем машину побольше, тот там в случае java работает лучше стандартные сервлеты, просто за счет того что там так сложилось и уже все грабли по этой теме собраны.


                                        Но опять же если уж реактивщину использовать и хочется использовать jvm, я бы scala или clojure. Хотя конечно для второго народ программировать будет сложновато :)

                                          0

                                          Вы на ходу сами себе придумываете что-то, то классическое маштабирование которое ничем не отличается от плодим кучу мелких машин
                                          Наш спор ограничивается одним инстансом с указанными параметрами.

                                            0
                                            Наш спор ограничивается одним инстансом с указанными параметрами.

                                            На указанном инстансе с 512 CPU и 512 RAM будет быстрее non-blocking в общем случае. Но nginx я бы перед ним все же поставил.

                                              0
                                              ну хоть что то :)))) рад что объективность восторжествовала. и он будет быстрее и на 8 ядрах и 64Гб памяти
                                                0

                                                8 ядер мало. Надо смотреть от 16 ядер. Хотя и на 8 не факт.

                                                  0

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

            0

            Ну почему же “закончить“? Наличие хорошего способа сделать что-то не означает, что все остальное — плохо.

              –1

              На java пишут все же больше стандартные большие и скучные "опердени" для кровавого интерпрайза. Там чем стандартнее тем лучше. Плюс классическое масштабирование применяется чаще.

            0
            А что насчет Quarkus react-pg-client?
            0
            Про поддержку в БД. А зачем ждать у моря погоды? В смысле Oracle не поддерживает фичу, значит enterprise массово не будет её использовать, а потому ждать незачем и если есть нужда — можно сделать довольно простой эмулятор, обеспечивающий аналогичный функционал без завязки на конкретную СУБД.

            По сути предлагаемые решения есть именно те самые велосипеды, которые в данном случае стоит сделать самим. Потому что — всё очень жёстко заточено под конкретное видение автора велосипеда. Значит придётся изголяться и тратить много лишнего времени на получение нужного функционала через узкое отверстие, оставленное автором как обычно — сзади. А альтернатив нет (кроме собственного велосипеда). Ну и Oracle не поддерживает.

            Хотя может у приведённых примеров решений есть какие-то значимые плюсы? Но я в статье их не увидел.
              0

              Данные велосипеды делаются под эгидой Eclipse и Spring, что внушает некоторое доверие.

                0

                Вообще не сильно понятно зачем. Для запуска в контейнере где мало ресурсов и один поток исполнения?

                0

                ОНО ЖЕ НЕБЛОКИРУЮЩЕЕСЯ!111. Хотя постойте это java это там не так уж и важно.

                  0
                  Я вас удивлю, но на Java тоже можно в неблокирующиеся игрушки играть.
                    0

                    Я знаю что умеет. И более того поддержка тянется аж с tomcat 6 версии nio под него есть.

                0
                Было бы очень интересно узнать о возможности (и опыте если такой есть) подружить асинхронные драйверы с такими озвученными инструментами, как JOOQ и Hibernate.
                  +1

                  все просто — возможности нет

                    0
                    Это-то больше всего и препятствует переходу.
                      +1

                      Если у вас Хибернейт то вам уже производительность по определению не приоритет :)
                      Но когда R2DBC будет готово для продакшена то к нему есть Спринг Дата

                        0
                        У нас jOOQ и Vert.x. Поэтому вопрос весьма актуален — сейчас приходится городить свои пулы.
                          0

                          Можно попробовать просто генерить SQL с jOOQ и скармливать его Vert.x

                            0
                            Можно, но это такая полумера все же. Хотелось бы полноценной работы с record'ами и custom типами.
                            В общем-то мой изначальный вопрос как раз о таком опыте. Пусть даже это будет просто генерация SQL при помощи jOOQ с последующей отправкой в какой-нибудь Vert.x Reactive PostgreSQL client.
                            Хочется узнать, как другие решают эту задачу (если вообще решают), увидеть примеры…
                              0

                              Я делал один запрос в котором много возможных вариантов через jOOQ и сгенерить SQL, но большинство запросов просто руками писаные в Repository объекте сидят. У меня специфика, что много микросервисов и схемы простые — не больше 10 таблиц в схеме.

                  –1

                  Совсем недавно на Хабре статья была: https://habr.com/ru/post/500446/
                  Думаю неплохо было бы упомянуть и написать про цели своей: например раскрыть подробнее ...

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

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