Oracle Thin JDBC Driver tuning

May 19, 2010

Oracle Thin JDBC Driver tuning

[ru] [en]

Please, see the beginning of this dramatic story here

[ page 1 ][ page 2 ][ page 3 ][ page 4 ]

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

К тому же, я, кажется, уже научился читать по лицам моих начальников, и они одобряют этот пост.

Основным источником информации является документ Oracle JDBC Memory Management. Прочитав его, я сделал такие

основные выводы

  • никогда не писать select * from, поскольку уже при парсинге sql-запроса (а не при его исполнении) выделяется память под кэши в Prepared Statement для каждой возвращаемой запросом колонки из расчета максимального размера данных в колонке, умноженного на fetchSize (по-умолчанию равен 10 и означает количество строк из запроса, что транспортный слой драйвера должен получить из БД). Никогда не выгружать из базы те столбцы, что не собираетесь использовать (вот, кстати, один из ответов на вопрос - "а почему вы не используете Hibernate?")
  • если используется старый Oracle JDBC-драйвер, то минимизировать использование памяти под кэши можно только изменением fetchSize, других механизмов нет. То есть рекомендуется использовать максимально свежую версию драйвера даже для коннекта со старыми релизами базы данных
  • никогда не кэшировать Prepared Statement средствами стороннего пула (например, не стоит использовать такую опцию из стандартного DBCP) - они и так кешируются в Oracle JDBC-дайвере, причем отключить это кэширование достаточно сложно (невозможно до версии 11.2, как я помню). Иначе совершенно неправильно будет работает "внутренняя" логика в Prepared Statement по созданию и управлению буфферами под кэш значений, что приводит к очень сильному потреблению памяти под кэши
  • выделять "побольше памяти", вернее, смириться с тем, что приложению необходимо больше памяти, чем, к примеру, в случае использования MySQL
  • следует использовать драйвер 11.2.x.x и тогда можно будет задавать размеры буфферов в ручную
  • Особенностью oracle драйвера является то, что он не может работать без кэша. Так что просто необходимо управлять его параметрами, поскольку по-умолчанию они настроены странно

Настройка

В зависимости от версии Oracle JDBC драйвера, Вы можете использовать такие перечисленные далее свойства (что наследуются драйвером от старых версий к более свежим). Сами свойства можно задавать как свойства запуска виртуальной машины Java (параметр -D<key>=<value>), или как свойства при создании коннекшена к базе. Например, если вы используете Spring для инициализации DBCP пула, то конфигурация может выглядеть так (сорри, в следующем блоке горизонтальный скролл)
<bean id="daoPropertyConfigurer"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="placeholderPrefix"><value>$jdbc{</value></property>
        <property name="locations">
            <list>
                <value>classpath:jdbc.properties</value>
            </list>
        </property>
    </bean>

    <bean id="oracle_prop" lazy-init="false"
          class="org.springframework.util.StringUtils"
          factory-method="collectionToDelimitedString">
        <constructor-arg index="0">
            <list>
                <value>v$session.program=MY-jdbc-pool</value>
                <value>useFetchSizeWithLongColumn=true</value>
                <value>defaultRowPrefetch=10</value>
                <value>disableDefineColumnType=true</value>
                <value>oracle.jdbc.useThreadLocalBufferCache=true</value>
                <value>oracle.jdbc.implicitStatementCacheSize=50</value>
                <value>oracle.jdbc.maxCachedBufferSize=18</value>
            </list>
        </constructor-arg>
        <constructor-arg value=";" index="1" type="java.lang.String"/>
    </bean>

    <bean id="my_oracle_datasource" 
          class="org.apache.commons.dbcp.BasicDataSource" 
          destroy-method="close">
        <property name="driverClassName" value="oracle.jdbc.OracleDriver"/>
        <property name="url" value="$jdbc{jdbc.url}"/>
        <property name="username" value="$jdbc{jdbc.username}"/>
        <property name="password" value="$jdbc{jdbc.password}"/>
        <property name="initialSize" value="2"/>
        <property name="maxIdle" value="5"/>
        <property name="maxActive" value="10"/>
        <property name="maxWait" value="10"/>
        <property name="defaultAutoCommit" value="false"/>
        <property name="testOnBorrow" value="false"/>
        <property name="testWhileIdle" value="true"/>
        <property name="timeBetweenEvictionRunsMillis" value="60000"/>
        <property name="validationQuery" value="select 1 from dual"/>
        <property name="connectionProperties" ref="oracle_prop"/>
        <property name="poolPreparedStatements" value="false"/>
        <property name="maxOpenPreparedStatements" value="0"/>
    </bean>
P.S. не забудьте положить в classpath файл jdbc.properties с объявлением свойств jdbc.url, jdbc.username, jdbc.password

Далее я приведу перевод наиболее содержательных абзацев из документации (с редкими комментариями - их видно по стилю).

Oracle Database Release 10.2.0.4

  • oracle.jdbc.freeMemoryOnEnterImplicitCache=true - полностью очищать буфер данных для Prepared Statement, когда он достается из пула (но буфер праметров при этом не трогается, кстати). Уже из определения свойства видно, что его использование вряд-ли как-то серьезно может помочь в управлении потребления памяти. Во-первых, очистка буфера и уменьшение его размера - это не одно и то же :), а во-вторых лично я вижу хоть какую-то пользу от этой операции если бы она выполнялась когда Statement возвращается в кэш, а не берется из него.

Oracle Database Release 11.1.0.7.0

  • oracle.jdbc.maxCachedBufferSize - по-умолчанию равен Integer.MAX_VALUE. Максимальный размер буфера, который будет сохранен во внутреннем кэше
  • oracle.jdbc.useThreadLocalBufferCache=true - оптимизация выделения буферов по потокам, а не по количеству коннекшенов (не выделяются буффера под неактивные коннекшены)

Oracle Database Release 11.2.x.x

  • oracle.jdbc.maxCachedBufferSize - с версии драйвера 11.2 инициализация параметра maxCachedBufferSize может увеличить производительность очень больших систем с большим кэшем Prepared Statement и SQL-запросами, требующими совершенно разный настроек буферов кэша. В 11.2 значение maxCachedBufferSize интерпретируется как логарифм по основании 2 от максимального размера буфера. К примеру, если maxCachedBufferSize имеет значение 20, то максимальный буфер в кэше равен 1048576 (но в документе не указана единица измерения! Судя из экспериментов - это килобайты). Для обратной совместимости, значения более 30 интерпретируются как полное значение, а не как log2, но использование степени двойки предпочтительнее.
  • oracle.jdbc.useThreadLocalBufferCache=true поддерживается как и ранее
  • oracle.jdbc.implicitStatementCacheSize - Начальный размер кэша Prepared Statement. Задав этому параметру положительное значение вы включите кэширование Implicit Statement Cache.

А также

  • не забудьте про размер fetchSize - он задается при помощи свойства defaultRowPrefetch - не самом деле этот параметр должен поддерживать не только Oracle, но и прочие jdbc-драйвера
  • судя из документации, в версии 11.2.x.x должны корректно поддерживаться разные значения fetchSize, заданные для разных PreparedStatement#setFetchSize(int), а не только в общих настройках драйвера (помните, что именно этот параметр играет одно из решающих значение на потребление памяти драйвером)
  • полезно также ознакомится с Connection Properties Recognized by Oracle JDBC Drivers и Oracle Extended Data Source Properties

Стратегия настройки

  • Анализируете сколько памяти Вам не жалко и прописываете это в параметр oracle.jdbc.maxCachedBufferSize - не забудте что указанное значение будет интерпретироваться драйвером как "степень двойки"
  • Если получаете exeption типа OutOfMemory "ноги" которого ростут из соответстующего места Оracle-драйвера - то начинайте уменьшать значение defaultRowPrefetch. Если вы опустились до "1" (достаточно бредовое значение, но я, например, больше чем "5" поставить не могу - и так Oracle гиг сжирает, т.е. maxCachedBufferSize = 20) но exception все-равно происходит - ну нет вариантов, покупайте память и увеличивайте oracle.jdbc.maxCachedBufferSize
  • Кстати, величина этого параметра очень сильно влияет на скорость, с которой oracle начинает реально отдавать данные с сервера в приложение независимо от числа передаваемых записей, если в запросе очень большое количество полей. То есть, если вы столкнулись с тем что все в принципе работает, но первый запрос дико "тупит" - увеличивайте oracle.jdbc.maxCachedBufferSize

Результаты

В предыдущем посте я приводил график потребления памяти не настроенным драйвером. Третий sql-запрос приводил к возникновению ошибки OutOfMemory
После первой же настройки (просто указал ограничение для oracle.jdbc.maxCachedBufferSize) картина радикально изменилась
"Пила" на графике - это именно выделение памяти под кэши JDBC-драйвера. На графике, во-первых, видно, что после проведения sql-запроса по перестройке индексов обработка пошла гораздо быстрее, чем было в самом начале операции (это, разумеется, специфика приложения). А во-вторых - виртуальная машина даже вернула в heap неиспользованную память так, что в итоге JRE занимало только 268 Mb.

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

THE END

P.S. как только у меня еще появятся умные мысли - я вам сообщу.
The fascination of technical documentation in the WIKI is in the simplicity of its theft. We call this "tolerance". Almost all of what I'm going to tell now was reached in the research objectives of the project CDI, but, in fact, I will not tell you anything more than you could find yourself out of open documents. Only a few pictures and comments by me will help you to navigate.

Besides, I think I have learned to read the faces of my superiors, and they approve this post.

The mein source document is Oracle JDBC Memory Management. After reading it, I made such

main conclusions

  • newer use the select * from, because even at parsing of the sql-query (not at execution of it) a big volume of memory becomes used by caches of Prepared Statement. This volume is a sum of all “column-caches” each of which is equals to max size of column multiplied by fetchSize (it's 10 by default and mean the quantity of rows that transport layer should get from DB). So, never load the columns from the DB that you do not need to use now.(that is the answer why I don't use Hibernate in hi-load application)
  • with old Oracle JDBC-driver just one fetchSize parameter can be used to manage cache volume. That is why Oracle recommends to use the lates version of JDBC drive even for old DB instances
  • never cache Prepared Statements by some other pool (never use such option of the DBCP for example) – they will be cached by default by the Oracle JDBC-driver internally and you can't disable this cache in easy way (at least up to version11.2 as I can remember). Otherwise the internal logic of driver's buffers with cached values in Prepared Statement will be broken and you will use much more memory that is really needed
  • allocate "more memory" and accept the fact that your application will need more memory than, for example, in the case of MySQL
  • you should use driver 11.2.x.x and you will be able to manage such buffers manually
  • the feature of the oracle diver that it can't work without caches. So you should to manage they manually, because of they default tuning is unusable

Tuning

It depends on version of Oracle JDBC driver, but in general you can use parameters listed below (they are inherited by newest versions of driver from oldest). Each property can be passed as a Java Runtime Property (by -D<key>=<value>), or as a JDBC Connection property. For example, if you use Spring to init the DBCP pool, your config can be looked as (sorry fro horizontal scroll here)
<bean id="daoPropertyConfigurer"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="placeholderPrefix"><value>$jdbc{</value></property>
        <property name="locations">
            <list>
                <value>classpath:jdbc.properties</value>
            </list>
        </property>
    </bean>

    <bean id="oracle_prop" lazy-init="false"
          class="org.springframework.util.StringUtils"
          factory-method="collectionToDelimitedString">
        <constructor-arg index="0">
            <list>
                <value>v$session.program=MY-jdbc-pool</value>
                <value>useFetchSizeWithLongColumn=true</value>
                <value>defaultRowPrefetch=10</value>
                <value>disableDefineColumnType=true</value>
                <value>oracle.jdbc.useThreadLocalBufferCache=true</value>
                <value>oracle.jdbc.implicitStatementCacheSize=50</value>
                <value>oracle.jdbc.maxCachedBufferSize=18</value>
            </list>
        </constructor-arg>
        <constructor-arg value=";" index="1" type="java.lang.String"/>
    </bean>

    <bean id="my_oracle_datasource" 
          class="org.apache.commons.dbcp.BasicDataSource" 
          destroy-method="close">
        <property name="driverClassName" value="oracle.jdbc.OracleDriver"/>
        <property name="url" value="$jdbc{jdbc.url}"/>
        <property name="username" value="$jdbc{jdbc.username}"/>
        <property name="password" value="$jdbc{jdbc.password}"/>
        <property name="initialSize" value="2"/>
        <property name="maxIdle" value="5"/>
        <property name="maxActive" value="10"/>
        <property name="maxWait" value="10"/>
        <property name="defaultAutoCommit" value="false"/>
        <property name="testOnBorrow" value="false"/>
        <property name="testWhileIdle" value="true"/>
        <property name="timeBetweenEvictionRunsMillis" value="60000"/>
        <property name="validationQuery" value="select 1 from dual"/>
        <property name="connectionProperties" ref="oracle_prop"/>
        <property name="poolPreparedStatements" value="false"/>
        <property name="maxOpenPreparedStatements" value="0"/>
    </bean>
P.S. and you should provide the jdbc.properties file in classpath with declaration of jdbc.url, jdbc.username, jdbc.password properties.

Now I'll list more helpful paragraphs of documentation (with my comments).

Oracle Database Release 10.2.0.4

  • oracle.jdbc.freeMemoryOnEnterImplicitCache=true - to fully clean the buffer of Prepared Statement values on taking it from the pool (but the buffers of parameters will never be cleaned, by the way). This property can never help you mush with memory usage. At first – cleansing of buffers is NOT a reducing of buffer's size :), and the second – it will be much more helpful to have this feature on returning the statement to the pool but not on getting of it.

Oracle Database Release 11.1.0.7.0

  • oracle.jdbc.maxCachedBufferSize - default value is Integer.MAX_VALUE - should be decreased. It is the max byes buffer that can be stored in memory
  • oracle.jdbc.useThreadLocalBufferCache=true - to create one buffer per thread not per Connection (so, inactive connections in pool will not use cache)

Oracle Database Release 11.2.x.x

  • oracle.jdbc.maxCachedBufferSize - from version 11.2 the correct value of the maxCachedBufferSize parameter can significantly increase performance on really big systems with really big amount of memory used by Prepared Statement cache and SQL-queries each of which require different cache memory management. From 11.2 the value of maxCachedBufferSize is interpreted as logarithm to base 2 from max buffer size. For example if the maxCachedBufferSize is equals to 20 the max cached buffer will be 1048576 of parrots (the documentation does not specify a unit of measurement! Judging by my test - this is kilobytes). For backward compatibility all values greater than 30 will be uses by direct value but not as a log2.
  • oracle.jdbc.useThreadLocalBufferCache=true supported too
  • oracle.jdbc.implicitStatementCacheSize - initial Prepared Statement cache value. The negative value will fully disable Implicit Statement Cache.

And also

The strategy of settings

  • Specify as much memory as you really can by parameter oracle.jdbc.maxCachedBufferSize - and do not forgot about log2
  • When you get the OutOfMemory - start to decrease the defaultRowPrefetch. When the value is "1" (for example now I can't use more that "5" - Oracle takes 1Gb with maxCachedBufferSize = 20) but you still takes an exception – you should only buy more memory and decrease the oracle.jdbc.maxCachedBufferSize
  • By the way, the value of the parameter is dramatically improves performance of the oracle driver. When you get an application with big amount of columns in queries and it “looks like worked but too slow” - just try to increase the oracle.jdbc.maxCachedBufferSize

Rezults

At the previous post I've show you a graph of “not tuned” driver. The third sql-query was terminated by OutOfMemory
Just setting of the limit for oracle.jdbc.maxCachedBufferSize change the behavior radically
The "saw" on the chart - this is the memory allocation for caches by JDBC-driver. On the chart it is clear that rebuild of the indexes speedup the application (this is, of course, application specifics). And secondly - the virtual machine even release some unused heap memory, so the JRE took only 268 Mb eventually.

By the way, we have recently migrate to Oracle a similar application. Before tuning of the driver - a huge memory consumption by the JDBC-driver, processors were loaded up to 100%, but only a "garbage collector" (GC) has worked exclusively. So take my advice and always spend some time on configuring caches of Oracle JDBC Driver. (It seems that Oracle so engrossed in the idea that ​​"each Oracle installation should be managed by Oracle certified admin", that even a JDBC-driver requires management too).

THE END

P.S. I'll let you know as soon as I will have some other clever ideas

0 comments:

Post a Comment