Пул операторов JDBC с DB2 не имеет существенной разницы во времени

Я использую драйвер JDBC db2, он же JT400 для подключения на сервер db2 в Application System/400, компьютерной системе среднего уровня.

Моя цель — insert into три таблицы вне мейнфрейма IBM, которые будут облачными экземплярами (например, Amazon WS).

Чтобы производительность была лучше

1) Я использую уже установленные соединения для подключения к db2. (используя org.apache.commons.dbcp.BasicDataSource или com.ibm.as400.access.AS400JDBCManagedConnectionPoolDataSource, оба работают нормально.)

public class AS400JDBCManagedConnectionPoolDataSource extends AS400JDBCManagedDataSource implements ConnectionPoolDataSource, Referenceable, Serializable {
}

public class AS400JDBCManagedDataSource extends ToolboxWrapper implements DataSource, Referenceable, Serializable, Cloneable {
}

2) Я хочу кэшировать операторы insert into для всех трех таблиц, чтобы мне не приходилось каждый раз отправлять запрос и каждый раз компилировать, что дорого. Вместо этого я бы просто передал только параметры. (Очевидно, я делаю это, используя JDBC prepared statements)

На основе официального документа IBM Оптимизация доступа к DB2 для i5/OS из Java и WebSphere, стр. 17-20 — Включение расширенной динамической поддержки, оператор можно кэшировать с помощью AS400JDBCManagedConnectionPoolDataSource.

НО, проблема в том, что insert into запросов компилируются каждый раз, что каждый раз занимает 200ms * 3 queries = 600ms.

Пример, который я использую,

public class CustomerOrderEventHandler extends MultiEventHandler {

    private static Logger logger = LogManager.getLogger(CustomerOrderEventHandler.class);

    //private BasicDataSource establishedConnections = new BasicDataSource();

    //private DB2SimpleDataSource nativeEstablishedConnections = new DB2SimpleDataSource();

    private AS400JDBCManagedConnectionPoolDataSource dynamicEstablishedConnections =
            new AS400JDBCManagedConnectionPoolDataSource();

    private State3 orderState3;
    private State2 orderState2;
    private State1 orderState1;

    public CustomerOrderEventHandler() throws SQLException {
        dynamicEstablishedConnections.setServerName(State.server);
        dynamicEstablishedConnections.setDatabaseName(State.DATABASE);
        dynamicEstablishedConnections.setUser(State.user);
        dynamicEstablishedConnections.setPassword(State.password);
        dynamicEstablishedConnections.setSavePasswordWhenSerialized(true);
        dynamicEstablishedConnections.setPrompt(false);
        dynamicEstablishedConnections.setMinPoolSize(3);
        dynamicEstablishedConnections.setInitialPoolSize(5);
        dynamicEstablishedConnections.setMaxPoolSize(50);
        dynamicEstablishedConnections.setExtendedDynamic(true);
        Connection connection = dynamicEstablishedConnections.getConnection();
        connection.close();
    }

    public void onEvent(CustomerOrder orderEvent){
        long start =  System.currentTimeMillis();
        Connection dbConnection = null;
        try {
            dbConnection = dynamicEstablishedConnections.getConnection();
            long connectionSetupTime = System.currentTimeMillis() - start;

            state3 = new State3(dbConnection);
            state2 = new State2(dbConnection);
            state1 = new State1(dbConnection);

            long initialisation = System.currentTimeMillis() - start - connectionSetupTime;

            int[] state3Result = state3.apply(orderEvent);
            int[] state2Result = state2.apply(orderEvent);
            long state1Result = state1.apply(orderEvent);

            dbConnection.commit();

            logger.info("eventId="+ getEventId(orderEvent) +
                    ",connectionSetupTime=" + connectionSetupTime +
                    ",queryPreCompilation=" + initialisation +
                    ",insertionOnlyTimeTaken=" +
                    (System.currentTimeMillis() - (start + connectionSetupTime + initialisation)) +
                    ",insertionTotalTimeTaken=" + (System.currentTimeMillis() - start));
        } catch (SQLException e) {
            logger.error("Error updating the order states.", e);
            if(dbConnection != null) {
                try {
                    dbConnection.rollback();
                } catch (SQLException e1) {
                    logger.error("Error rolling back the state.", e1);
                }
            }
            throw new CustomerOrderEventHandlerRuntimeException("Error updating the customer order states.", e);
        }
    }

    private Long getEventId(CustomerOrder order) {
        return Long.valueOf(order.getMessageHeader().getCorrelationId());
    }
}

И состояния с командами вставки выглядят так, как показано ниже,

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class State2 extends State {

    private static Logger logger = LogManager.getLogger(DetailState.class);

    Connection connection;
    PreparedStatement preparedStatement;

    String detailsCompiledQuery = "INSERT INTO " + DATABASE + "." + getStateName() +
            "(" + DetailState.EVENT_ID + ", " +
            State2.ORDER_NUMBER + ", " +
            State2.SKU_ID + ", " +
            State2.SKU_ORDERED_QTY + ") VALUES(?, ?, ?, ?)";

    public State2(Connection connection) throws SQLException {
        this.connection = connection;
        this.preparedStatement = this.connection.prepareStatement(detailsCompiledQuery); // this is taking ~200ms each time
        this.preparedStatement.setPoolable(true); //might not be required, not sure
    }

    public int[] apply(CustomerOrder event) throws StateException {

        event.getMessageBody().getDetails().forEach(detail -> {
            try {
                preparedStatement.setLong(1, getEventId(event));
                preparedStatement.setString(2, getOrderNo(event));
                preparedStatement.setInt(3, detail.getSkuId());
                preparedStatement.setInt(4, detail.getQty());
                preparedStatement.addBatch();
            } catch (SQLException e) {
                logger.error(e);
                throw new StateException("Error setting up data", e);
            }
        });

        long startedTime = System.currentTimeMillis();
        int[] inserted = new int[0];
        try {
            inserted = preparedStatement.executeBatch();
        } catch (SQLException e) {
            throw new StateException("Error updating allocations data", e);
        }
        logger.info("eventId="+ getEventId(event) +
                ",state=details,insertionTimeTaken=" + (System.currentTimeMillis() - startedTime));
        return inserted;
    }

    @Override
    protected String getStateName() {
        return properties.getProperty("state.order.details.name");
    }
}

Таким образом, поток каждый раз, когда принимается событие (например, CustomerOrder), получает установленное соединение, а затем запрашивает состояния инициализировать свои statements.

Показатели времени выглядят так, как показано ниже.

для первого события требуется 580ms для создания preparedStatements для 3 таблиц.

{"timeMillis":1489982655836,"thread":"ScalaTest-run-running-CustomerOrderEventHandlerSpecs","level":"INFO","loggerName":"com.xyz.customerorder.events.handler.CustomerOrderEventHandler",
"message":"eventId=1489982654314,connectionSetupTime=1,queryPreCompilation=580,insertionOnlyTimeTaken=938,insertionTotalTimeTaken=1519","endOfBatch":false,"loggerFqcn":"org.apache.logging.log4j.spi.AbstractLogger","threadId":1,"threadPriority":5}

для второго события требуется 470ms для подготовки отчетов для 3 таблиц, что меньше, чем для первого события, но всего < 100ms, я предполагаю, что это значительно меньше, так как оно даже не должно доходить до компиляции.

{"timeMillis":1489982667243,"thread":"ScalaTest-run-running-PurchaseOrderEventHandlerSpecs","level":"INFO","loggerName":"com.xyz.customerorder.events.handler.CustomerOrderEventHandler",
"message":"eventId=1489982665456,connectionSetupTime=0,queryPreCompilation=417,insertionOnlyTimeTaken=1363,insertionTotalTimeTaken=1780","endOfBatch":false,"loggerFqcn":"org.apache.logging.log4j.spi.AbstractLogger","threadId":1,"threadPriority":5}

Я думаю, что поскольку я закрываю preparedStatement для этого конкретного соединения, оно даже не существует для нового соединения. Если это так, то какой смысл вообще иметь кэширование операторов в многопоточной среде.

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

моментальный снимок

Вопросы

Основной

Q1) Драйвер DB2 JDBC вообще кэширует операторы между несколькими соединениями? Потому что я не вижу большой разницы при подготовке заявления. (см. пример, первый принимает ~600ms, второй принимает ~500ms)

использованная литература

ODP = открытый путь данных

пакеты SQL

Пакеты SQL — это постоянные объекты, используемые для хранения информации, связанной с подготовленными операторами SQL. Их может использовать драйвер IBM iSeries Access для IBM Toolbox for Java JDBC. Они также используются приложениями, использующими интерфейс API QSQPRCED (SQL Process Extended Dynamic).

В случае JDBC наличие пакета SQL проверяется, когда клиентское приложение выполняет первую подготовку оператора SQL. Если пакет не существует, он создается в это время (даже если он еще не содержит операторов SQL).

Конфигурация пула соединений Tomcat jdbc — DB2 на iSeries(AS400)

Драйвер IBM Data Server для кэширования операторов JDBC и SQLJ


person prayagupd    schedule 20.03.2017    source источник
comment
очень подробный пост, а какой у вас вопрос?   -  person Andy Guibert    schedule 20.03.2017
comment
Привет @AndyGuibert. Мой главный вопрос: кэшируются ли вообще подготовленные операторы JDBC? потому что я вижу разницу только в ~100ms при подготовке отчетов. Каждый раз требуется не менее ~150ms * 3 = 450ms для компиляции трех запросов.   -  person prayagupd    schedule 20.03.2017
comment
Ваш вопрос слишком широк: вы задаете как минимум три разных вопроса. Пожалуйста, сосредоточьтесь на одном вопросе. Кроме того: в JDBC ConnectionPoolDataSource не является пулом соединений (это источник данных для пула соединений). Хотя иногда реализации делают это неправильно, так что AS400JDBCManagedConnectionPoolDataSource все же может быть одним из них.   -  person Mark Rotteveel    schedule 20.03.2017
comment
Спасибо, что уделили свое драгоценное время. Очевидно, Q2 и Q3 являются необязательными. Не понял, что вы имеете в виду, ConnectionPoolDataSource не является пулом соединений? Он обеспечивает физическое соединение в пуле. Я использую AS400JDBCManagedConnectionPoolDataSource, который реализует ConnectionPoolDataSource и DataSource и представляет собой рекомендуется документацией, так как предполагается поддержка кэширования операторов. Я вижу, что он работает как пул соединений.   -  person prayagupd    schedule 20.03.2017


Ответы (2)


Несколько важных замечаний относительно кэширования операторов:

  • Поскольку объекты Statement являются дочерними объектами данного Connection, после закрытия Connection все дочерние объекты (например, все объекты Statement) также должны быть закрыты.
  • Невозможно связать инструкцию из одного соединения с другим соединением.
  • Пул операторов может выполняться или не выполняться заданным драйвером JDBC. Пул операторов также может выполняться уровнем управления соединениями (т. е. сервером приложений).
  • Согласно спецификации JDBC, значение по умолчанию для Statement.isPoolable() == false и PreparedStatement.isPoolable() == true, однако этот флаг является лишь подсказкой для драйвера JDBC. Спецификация не гарантирует, что объединение операторов произойдет.

Во-первых, я не уверен, что драйвер JT400 кэширует операторы. Документ, на который вы ссылались в своем комментарии к вопросу, Оптимизировать доступ к DB2 для i5/OS из Java и WebSphere, относится к использованию драйвера JDBC JT400 с сервером приложений WebSphere и На слайде №3 показано, что кэширование операторов осуществляется на уровне управления соединениями WebSphere, а не на уровне собственного драйвера JDBC. Учитывая это, я собираюсь предположить, что драйвер JDBC JT400 сам по себе не поддерживает кэширование операторов.

Итак, на этом этапе вы, вероятно, захотите подключиться к какому-то серверу приложений (если только вы не хотите реализовать кэширование операторов самостоятельно, что является своего рода изобретением колеса). Я точно знаю, что оба продукта WebSphere Application Server (традиционный и Liberty) поддерживают кэширование операторов для любого драйвера JDBC.

Для WebSphere Liberty (более новый продукт) конфигурация источника данных выглядит следующим образом:

<dataSource jndiName="jdbc/myDS" statementCacheSize="10">
    <jdbcDriver libraryRef="DB2iToolboxLib"/>
    <properties.db2.i.toolbox databaseName="YOURDB" serverName="localhost"/>
</dataSource>

<library id="DB2iToolboxLib">
    <fileset dir="/path/to/jdbc/driver/dir" includes="jt400.jar"/>
</library>

Бит ключа представляет собой атрибут statementCacheSize для <dataSource>, значение которого по умолчанию равно 10.

(Отказ от ответственности, я разработчик WebSphere, поэтому я буду говорить о том, что знаю)

person Andy Guibert    schedule 21.03.2017
comment
Привет, Энди, поэтому, когда вы говорите, что операторы не кэшируются при нескольких подключениях, применимо ли это, если я использую серверы приложений (будь то Tomcat или веб-сервер)? В моем случае приложение, которое я использую, является автономным приложением, но если сервер приложений позволяет global statements caching, я готов к серверу приложений, у меня уже есть Tomcat в моем стеке. Я только что проверил кэш операторов tomcat, в котором упоминается StatementCache#max = cached statements across the connection pool. The default value is 50. - person prayagupd; 21.03.2017
comment
операторы не кэшируются между соединениями, даже на серверах приложений. Однако серверы приложений могут создавать иллюзию кэширования операторов для нескольких соединений, сохраняя открытые соединения в пуле серверов приложений и предоставляя приложению соединения-оболочки. Таким образом, когда приложение закрывает соединение, оно закрывает только оболочку, а сервер приложений цепляется за реальное соединение. Драйверы JDBC также могут делать подобные вещи внутри, но я не уверен. - person Andy Guibert; 21.03.2017
comment
и относительно Tomcat: документ, на который вы ссылаетесь, выглядит как механизм, эквивалентный тому, что я описывал с помощью Liberty. - person Andy Guibert; 21.03.2017
comment
Хорошо, теперь это имеет для меня смысл. Возможно, возвращаемое значение AS400JDBCManagedConnectionPoolDataSource#getConnection тоже делает это. Я вернусь к одному соединению (не объединенному в пул), а затем сравню показатели, чтобы подготовить отчеты. Совершенно не уверен, что кэш операторов находится между соединениями, как говорится в документе The statements are cached PER CONNECTION., я тоже попробую. Ваш ответ очень и очень ценен. Большое спасибо - person prayagupd; 21.03.2017

Во-первых, документация IBM i Java находится здесь: IBM Toolbox for Java< /а>

Во-вторых, я не вижу, где вы устанавливаете "extended dynamic" в true, что обеспечивает

механизм кэширования динамических операторов SQL на сервере. При первой подготовке конкретного оператора SQL он сохраняется в пакете SQL на сервере. Если пакет не существует, он создается автоматически. При последующей подготовке того же оператора SQL сервер может пропустить значительную часть обработки, используя информацию, хранящуюся в пакете SQL. Если установлено значение «true», то имя пакета должно быть установлено с использованием свойства «package».

Я думаю, вы упустили некоторые шаги в использовании управляемого пула... вот первый пример в документы IBM

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.sql.DataSource;

import com.ibm.as400.access.AS400JDBCManagedConnectionPoolDataSource;
import com.ibm.as400.access.AS400JDBCManagedDataSource;


public class TestJDBCConnPoolSnippet
{
    void test()
    {
        AS400JDBCManagedConnectionPoolDataSource cpds0 = new AS400JDBCManagedConnectionPoolDataSource();

        // Set general datasource properties.  Note that both connection pool datasource (CPDS) and managed
               // datasource (MDS) have these properties, and they might have different values.
        cpds0.setServerName(host);
        cpds0.setDatabaseName(host);//iasp can be here
        cpds0.setUser(userid);
        cpds0.setPassword(password);


        cpds0.setSavePasswordWhenSerialized(true);

        // Set connection pooling-specific properties.
        cpds0.setInitialPoolSize(initialPoolSize_);
        cpds0.setMinPoolSize(minPoolSize_);
        cpds0.setMaxPoolSize(maxPoolSize_);
        cpds0.setMaxLifetime((int)(maxLifetime_/1000));  // convert to seconds
        cpds0.setMaxIdleTime((int)(maxIdleTime_/1000));  // convert to seconds
        cpds0.setPropertyCycle((int)(propertyCycle_/1000));  // convert to seconds
        //cpds0.setReuseConnections(false);  // do not re-use connections

        // Set the initial context factory to use.
        System.setProperty(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.fscontext.RefFSContextFactory");


        // Get the JNDI Initial Context.
        Context ctx = new InitialContext();

        // Note: The following is an alternative way to set context properties locally:
        //   Properties env = new Properties();
        //   env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.fscontext.RefFSContextFactory");
        //   Context ctx = new InitialContext(env);

        ctx.rebind("mydatasource", cpds0);  // We can now do lookups on cpds, by the name "mydatasource".

        // Create a standard DataSource object that references it.

        AS400JDBCManagedDataSource mds0 = new AS400JDBCManagedDataSource();
        mds0.setDescription("DataSource supporting connection pooling");
        mds0.setDataSourceName("mydatasource");
        ctx.rebind("ConnectionPoolingDataSource", mds0);

        DataSource dataSource_ = (DataSource)ctx.lookup("ConnectionPoolingDataSource");

        AS400JDBCManagedDataSource mds_ = (AS400JDBCManagedDataSource)dataSource_;

        boolean isHealthy = mds_.checkPoolHealth(false);  //check pool health

        Connection c = dataSource_.getConnection(); 

    }

}
person Charles    schedule 20.03.2017
comment
Я тоже использую AS400JDBCManagedConnectionPoolDataSource#setExtendedDynamic(true). Одно отличие, которое я вижу в вашем примере, заключается в использовании AS400JDBCManagedDataSource. Я использую только AS400JDBCManagedConnectionPoolDataSource для установления соединения. - person prayagupd; 20.03.2017