Проблемы производительности open source библиотек
-
Upload
vladimir-sitnikov -
Category
Software
-
view
138 -
download
1
Transcript of Проблемы производительности open source библиотек
© Netcracker 2016 2
• Владимир Ситников• Инженер по производительности в Netcracker, 10 лет
• [email protected]• @VladimirSitnikv
Кто я
© Netcracker 2016 10
• Когда нужен бин, то выполняем
beanFactory.getBean(Security.class)
В UI spring пока не наступил
© Netcracker 2016 11
• Когда нужен бин, то выполняем
beanFactory.getBean(Security.class)
• Но где взять beanFactory?
В UI spring пока не наступил
© Netcracker 2016 12
• Когда нужен бин, то выполняем
beanFactory.getBean(Security.class)
• Но где взять beanFactory?
• Правильно, Паблик Морозов поможет нам
В UI spring пока не наступил
© Netcracker 2016 13
public class AppContext implements BeanFactoryAware { static public BeanFactory beanFactory; ...
Получаем бин
© Netcracker 2016 14
public class AppContext implements BeanFactoryAware { static public BeanFactory beanFactory; ...
AppContext.beanFactory.getBean(Security.class)
Получаем бин
© Netcracker 2016 17
• Работает• Но медленно-медленно• На getBean и 50% времени может уходить
Spring.getBean
© Netcracker 2016 18
• Java Flight Recorder
Подходящие для анализа инструменты
© Netcracker 2016 19
• Java Flight Recorder• kill -3 profiler (poormansprofiler.org)
Подходящие для анализа инструменты
© Netcracker 2016 20
• Java Flight Recorder• kill -3 profiler (poormansprofiler.org)• Инструментирующий профилировщик. Например самописный, настроенный на getBean
Подходящие для анализа инструменты
© Netcracker 2016 23
• SPR-6870 Cache by-type lookups in DefaultListableBeanFactory
Проверяем известные проблемы
© Netcracker 2016 24
• SPR-6870 Cache by-type lookups in DefaultListableBeanFactory
Проверяем известные проблемы
ЯЗЬ!!!
© Netcracker 2016 25
• SPR-6870 Cache by-type lookups in DefaultListableBeanFactory
• Fix version: 3.2 M1, а у нас 4.1+
Проверяем известные проблемы
© Netcracker 2016 27
protected Object getCacheKey( Class<?> beanClass, String beanName) { return beanClass.getName() + "_" + beanName;}
AbstractAutoProxyCreator
© Netcracker 2016 28
protected Object getCacheKey( Class<?> beanClass, String beanName) { return beanClass.getName() + "_" + beanName;}
AbstractAutoProxyCreator
© Netcracker 2016 29
protected Object getCacheKey( Class<?> beanClass, String beanName) { return beanName == null ? beanClass : ...;}
github.com/spring-projects/spring-framework/pull/913
AbstractAutoProxyCreator
© Netcracker 2016 30
public class SharedSecrets { @Inject public Security security; @PostConstruct public void init() { INSTANCE = this; }
public static volatile SharedSecrets INSTANCE;
Как выжить без обновления Spring?
© Netcracker 2016 31
• Не стоит злоупотреблять prototype bean’ами. Если singleton, то singleton
Prototype vs singleton
© Netcracker 2016 32
• Не стоит злоупотреблять prototype bean’ами. Если singleton, то singleton
• Замена getBean на javax.inject.Provider<...> лишь ухудшает время работы
•
Prototype vs singleton
© Netcracker 2016 34
• OutOfMemory: perm gen
Spring AOP
© Netcracker 2016 35
• OutOfMemory: perm gen• В хипдампе куча java.lang.reflect.Method
Spring AOP
© Netcracker 2016 36
• OutOfMemory: perm gen• В хипдампе куча java.lang.reflect.Method• Method’ы занимают 100-500MiB
Spring AOP
© Netcracker 2016 37
• OutOfMemory: perm gen• В хипдампе куча java.lang.reflect.Method• Method’ы занимают 100-500MiB• Все они лежат в AspectJExpressionPointcut
Spring AOP
© Netcracker 2016 39
• Решение: добавлять within(my.package.service.business..*)
Spring AOP vs AspectJ
© Netcracker 2016 40
• Решение: добавлять within(my.package.service.business..*)
• Разумеется, AspectJ рекомендуют использовать within всегда: http://dev.eclipse.org/mhonarc/lists/aspectj-users/msg10969.html
•
Spring AOP vs AspectJ
© Netcracker 2016 42
• Cglib используется много где: рукотворный код, тот же Spring
• Наверняка уже всё исправлено давным-давно
Cglib
© Netcracker 2016 43
• Cglib используется много где: рукотворный код, тот же Spring
• Наверняка уже всё исправлено давным-давно
• Наверняка туда давно никто не заглядывал
Cglib
© Netcracker 2016 44
@Benchmarkpublic Object newProxy() { return Beans.newProxy(Beans.class);}
Cglib: замеряем
© Netcracker 2016 45
Cglib 3.10
2000400060008000
1000012000140001600018000
1 поток 2 потока 4 потока 8 потоков
Замеры Cglib, μs/op
Бы
стре
е
© Netcracker 2016 46
Cglib 3.1 Cglib 3.2.20
2000400060008000
1000012000140001600018000
1 поток 2 потока 4 потока 8 потоков
Замеры Cglib, μs/op
Бы
стре
е
© Netcracker 2016 47
Benchmark ScorenewProxy 2.1 ± 0.3 μs/opnewProxy.alloc.rate 1240 B/op
Стало newProxy 0.14 ± 0.02 μs/opnewProxy.alloc.rate 256 B/op
Cglib 3.2.2: ждём в очередном Spring
© Netcracker 2016 48
• Skip finalize while building proxyhttps://github.com/cglib/cglib/pull/51
• Concurrent cache of generated classeshttps://github.com/cglib/cglib/pull/53
План захвата Cglib
© Netcracker 2016 49
• А, может, ну его этот cglib, есть же быстрый ByteBuddy?
Но есть же ByteBuddy
© Netcracker 2016 50
• А, может, ну его этот cglib, есть же быстрый ByteBuddy?
@Benchmarkpublic ExampleClass benchmarkCglib() { Enhancer enhancer = new Enhancer(); enhancer.setUseCache(false);
ByteBuddy, jmh тест
© Netcracker 2016 51
• А, может, ну его этот cglib, есть же быстрый ByteBuddy?
@Benchmarkpublic ExampleClass benchmarkCglib() { Enhancer enhancer = new Enhancer(); enhancer.setUseCache(false);
ByteBuddy, jmh тест
© Netcracker 2016 52
• JMH тесты в ByteBuddy показывают скорость создания классов
• Постоянно создавать классы нехорошо• Значит, нужно измерять скорость кэшированного обращения
ByteBuddy, jmh тест
© Netcracker 2016 54
• КО: «Бери pgjdbc»• КО: «Используй batch statements»
Подключаемся к PostgreSQL
© Netcracker 2016 55
• КО: «Бери pgjdbc»• КО: «Используй batch statements»• Что может пойти не так?
Подключаемся к PostgreSQL
© Netcracker 2016 56
Connection con = ...;PreparedStatement ps = con.prepareStatement("SELECT..."); ...ps.close();
PreparedStatement
© Netcracker 2016 57
Connection con = ...;PreparedStatement ps = con.prepareStatement("SELECT..."); ...ps.close();
PreparedStatement
© Netcracker 2016 58
PARSE S_1 as ...; // con.prepareStmt BIND/EXECDEALLOCATE // ps.close()PARSE S_2 as ...; BIND/EXECDEALLOCATE // ps.close()
Работа с PostgreSQL курильщика
© Netcracker 2016 59
PARSE S_1 as ...; BIND/EXEC BIND/EXEC BIND/EXEC BIND/EXEC BIND/EXEC ...DEALLOCATE
Работа с PostgreSQL здорового человека
© Netcracker 2016 60
PARSE S_1 as ...; 1 раз в жизни BIND/EXEC обработка REST BIND/EXEC BIND/EXEC ещё REST BIND/EXEC BIND/EXEC ...DEALLOCATE желательно «никогда»DEALLOCATE
Работа с PostgreSQL здорового человека
© Netcracker 2016 61
Вывод: чтобы работало быстрее, закрывать statement’ы не нужноps = con.prepareStatement(...)ps.execueQuery();ps = con.prepareStatement(...)ps.execueQuery();...
Счастливые statement’ов не закрывают
© Netcracker 2016 62
Вывод: чтобы работало быстрее, закрывать statement’ы не нужноps = con.prepare...ps.execueQuery();ps = con.prepare...ps.execueQuery();...
Счастливые statement’ов не закрывают
© Netcracker 2016 63
@Benchmarkpublic Statement leakStatement() { return con.createStatement();}
pgjdbc < 9.4.1202, -Xmx128m, OracleJDK 1.8u40# Warmup Iteration 1: 1147,070 ns/op# Warmup Iteration 2: 12101,537 ns/op# Warmup Iteration 3: 90825,971 ns/op# Warmup Iteration 4: <failure>java.lang.OutOfMemoryError: GC overhead limit exceeded
OpenJDK: не все JRE одинаково полезны
© Netcracker 2016 64
@Benchmarkpublic Statement leakStatement() { return con.createStatement();}pgjdbc >= 9.4.1202, -Xmx128m, OracleJDK 1.8u40# Warmup Iteration 1: 30 ns/op# Warmup Iteration 2: 27 ns/op...github.com/pgjdbc/pgjdbc/pull/299
Убираем finalize из класса PgConnection
© Netcracker 2016 65
Вывод: чтобы работало быстро, нужно как-то кэшировать statement’ыps = con.prepareStatement("select id, name ...");ps.execueQuery();ps.close();ps2 = con.prepareStatement("select id, name ...");
Счастливые statement’ов не закрывают
© Netcracker 2016 66
• Кэш запросов появился в версии 9.4.1202 (2015-08-27)см. https://github.com/pgjdbc/pgjdbc/pull/319
Кэш запросов в PgJDBC
© Netcracker 2016 67
• Кэш запросов появился в версии 9.4.1202 (2015-08-27)см. https://github.com/pgjdbc/pgjdbc/pull/319
• Работает прозрачно для приложения
Кэш запросов в PgJDBC
© Netcracker 2016 68
• Кэш запросов появился в версии 9.4.1202 (2015-08-27)см. https://github.com/pgjdbc/pgjdbc/pull/319
• Работает прозрачно для приложения• Скорость такая, что PL/PgSQL не нужен
Кэш запросов в PgJDBC
© Netcracker 2016 69
• Кэш запросов появился в версии 9.4.1202 (2015-08-27)см. https://github.com/pgjdbc/pgjdbc/pull/319
• Работает прозрачно для приложения• Скорость такая, что PL/PgSQL не нужен• Server-prepare активируется после 5-го выполнения (prepareThreshold)
Кэш запросов в PgJDBC
© Netcracker 2016 70
• Конечно, затраты planning time напрямую зависят от сложности запросов
Цифры где?
© Netcracker 2016 71
• Конечно, затраты planning time напрямую зависят от сложности запросов
• У нас доходило до 20мс+ planning time на OLTP запросах: 10КиБ запрос, 170 строк explain
•
Цифры где?
© Netcracker 2016 72
• Конечно, затраты planning time напрямую зависят от сложности запросов
• У нас доходило до 20мс+ planning time на OLTP запросах: 10КиБ запрос, 170 строк explain
• Стало ~0мс
Цифры где?
© Netcracker 2016 73
Если типы параметров меняются, то server-prepared statement приходится менятьps.setInt(1, 42);...ps.setNull(1, Types.VARCHAR);
JDBC: Типы параметров
© Netcracker 2016 74
Если типы параметров меняются, то server-prepared statement приходится менятьps.setInt(1, 42);...ps.setNull(1, Types.VARCHAR);
JDBC: Типы параметров
© Netcracker 2016 75
• Вывод: даже у NULL’ов должен быть верный тип
JDBC: Тип параметров изменять нельзя
© Netcracker 2016 76
• Вывод: даже у NULL’ов должен быть верный тип
• Ещё раз: setObject(1, null) использовать нельзя
JDBC: Тип параметров изменять нельзя
© Netcracker 2016 78
• Перешли на prepared, и запрос замедлился в 5'000 раз. Как так?
Нежданчик
A. Бага C. ФичаB. Фича D. Бага
© Netcracker 2016 79
https://gist.github.com/vlsi -> 01_plan_flipper.sql
select * from plan_flipper -- <- таблица where skewed = 0 -- 1 млн строк and non_skewed = 42 -- 20 строк
Нежданчик
© Netcracker 2016 80
https://gist.github.com/vlsi -> 01_plan_flipper.sql0.1мс 1-е выполнение 0.05мс 2-е выполнение 0.05мс 3-е выполнение 0.05мс 4-е выполнение 0.05мс 5-е выполнение 250 мс 6-е выполнение
Нежданчик
© Netcracker 2016 81
https://gist.github.com/vlsi -> 01_plan_flipper.sql0.1мс 1-е выполнение 0.05мс 2-е выполнение 0.05мс 3-е выполнение 0.05мс 4-е выполнение 0.05мс 5-е выполнение 250 мс 6-е выполнение
Нежданчик
© Netcracker 2016 82
Запрещаем использование индекса через +0:select * from plan_flipper where skewed+0 = 0 ~ /*+no_index*/ and non_skewed = 42
Чиним
© Netcracker 2016 83
Как сделать опциональные фичи?• NO_RESULTS• BOTH_ROWS_AND_STATUS• DESCRIBE_ONLY
Фичи
© Netcracker 2016 85
Конечно, enum!enum ... {
NO_RESULTS,BOTH_ROWS_AND_STATUS,DESCRIBE_ONLY;
}А на java 1.4?
Фичи
© Netcracker 2016 86
В java 1.4, конечно, interface!interface ... {
int NO_RESULTS = 1;int BOTH_ROWS_AND_STATUS = 2;int DESCRIBE_ONLY = 4;
}
Фичи: java 1.4 наносит ответный удар
© Netcracker 2016 87
Меняем 1 строку, и скорость работы batch insert возрастает в 10 раз:https://github.com/pgjdbc/pgjdbc/pull/380
- static int QUERY_FORCE_DESCRIBE_PORTAL = 128;+ static int QUERY_FORCE_DESCRIBE_PORTAL = 512;
Ужасы нашего городка
© Netcracker 2016 88
Меняем 1 строку, и скорость работы batch insert возрастает в 10 раз:https://github.com/pgjdbc/pgjdbc/pull/380
- static int QUERY_FORCE_DESCRIBE_PORTAL = 128;+ static int QUERY_FORCE_DESCRIBE_PORTAL = 512;
// оказалось, значение 128 уже было занято static int QUERY_DISALLOW_BATCHING = 128;
Ужасы нашего городка
© Netcracker 2016 91
• WildFly 8.2, JMS• Всего ~100 JMS/сек• 1 вызов sendMessage – 5-30 секунд
HornetQ
© Netcracker 2016 92
at java.util.concurrent.Semaphore.tryAcquireat org.hornetq ... ClientProducerCreditsImpl.acquireCredits at com ... JMSSender.sendMessage
kill -3 профайлер
© Netcracker 2016 93
at java.util.concurrent.Semaphore.tryAcquireat org.hornetq ... ClientProducerCreditsImpl.acquireCredits at com ... JMSSender.sendMessage
kill -3 профайлер
© Netcracker 2016 94
at java.util.concurrent.Semaphore.tryAcquireat org.hornetq ... ClientProducerCreditsImpl.acquireCredits at com ... JMSSender.sendMessage
kill -3 профайлер
© Netcracker 2016 95
at java.util.concurrent.Semaphore.tryAcquireat org.hornetq ... ClientProducerCreditsImpl.acquireCreditsat org.hornetq ... ClientProducerImpl.sendRegularMessage at com ... JMSSender.sendMessage
kill -3 профайлер
© Netcracker 2016 96
at java.util.concurrent.Semaphore.tryAcquireat org.hornetq ... ClientProducerCreditsImpl.acquireCreditsat org.hornetq ... ClientProducerImpl.sendRegularMessageat org.hornetq ... ClientProducerImpl.doSend at com ... JMSSender.sendMessage
kill -3 профайлер
© Netcracker 2016 97
at java.util.concurrent.Semaphore.tryAcquireat org.hornetq ... ClientProducerCreditsImpl.acquireCreditsat org.hornetq ... ClientProducerImpl.sendRegularMessageat org.hornetq ... ClientProducerImpl.doSendat org.hornetq ... ClientProducerImpl.send at com ... JMSSender.sendMessage
kill -3 профайлер
© Netcracker 2016 98
at java.util.concurrent.Semaphore.tryAcquireat org.hornetq ... ClientProducerCreditsImpl.acquireCreditsat org.hornetq ... ClientProducerImpl.sendRegularMessageat org.hornetq ... ClientProducerImpl.doSendat org.hornetq ... ClientProducerImpl.sendat org.hornetq ... HornetQMessageProducer.doSendxat com ... JMSSender.sendMessage
kill -3 профайлер
© Netcracker 2016 100
• 'Producer Window Size’ на connection factory• address-settings -> address-setting -> max-size-bytes
Backpressure
© Netcracker 2016 101
• 'Producer Window Size’ на connection factory• address-settings -> address-setting -> max-size-bytes• Если в JMS очереди накапливается 10МиБ, то отправка
JMS начинает притормаживать
Backpressure
© Netcracker 2016 102
• 'Producer Window Size’ на connection factory• address-settings -> address-setting -> max-size-bytes• Если в JMS очереди накапливается 10МиБ, то отправка
JMS начинает притормаживать
<address-settings> <address-setting match="#"> <max-size-bytes>10 485 760</max-size-bytes> …
Backpressure
© Netcracker 2016 104
• Настраиваем max-size-bytes для каждой очереди
• Либо смотрим в сторону RxJava
Backpressure: чиним
© Netcracker 2016 106
• Старт приложения на WF 8.2 занимает 2-5 минут
• Concurrent deploy работает плохо
WildFly: пытаемся взлететь
© Netcracker 2016 107
• Старт приложения на WF 8.2 занимает 2-5 минут
• Concurrent deploy работает плохо• Spring xml app config анализирует все jar’ники
WildFly: пытаемся взлететь
© Netcracker 2016 108
• Уменьшать размер jar (исключать лишние)• WildFly копирует jar в /tmp при запуске
WildFly: как чинить
© Netcracker 2016 109
• Уменьшать размер jar (исключать лишние)• WildFly копирует jar в /tmp при запуске
• Делать патчи на WF, чтобы он не складывал строки
• https://github.com/jbossas/jboss-vfs/pull/25• https://github.com/wildfly/wildfly-core/pull/1219
WildFly: как чинить
© Netcracker 2016 110
• Уменьшать размер jar (исключать лишние)• WildFly копирует jar в /tmp при запуске
• Делать патчи на WF, чтобы он не складывал строки
• https://github.com/jbossas/jboss-vfs/pull/25• https://github.com/wildfly/wildfly-core/pull/1219
• Профилировать запуск вашего WF (kill -3 profiler)
WildFly: как чинить
© Netcracker 2016 112
https://wiki.jenkins-ci.org/display/JENKINS/EnvInject+Plugin
Jenkins: EnvInjectPlugin
© Netcracker 2016 113
Пишем по образу и подобию:
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$ORACLE_HOME/…PATH=$PATH:$ORACLE/bin:…
Jenkins: EnvInjectPlugin
© Netcracker 2016 115
EnvInjectEnvVars.resolveVars() -> hudson.Util.replaceMacro()
Jenkins: EnvInjectPlugin
© Netcracker 2016 116
EnvInjectEnvVars.resolveVars() -> hudson.Util.replaceMacro() -> OutOfMemoryError.<init>()
Jenkins: EnvInjectPlugin
© Netcracker 2016 117
$LD_LIBRARY_PATH:$ORACLE_HOME/lib :$ORACLE_HOME/lib:$ORACLE_HOME/lib :$ORACLE_HOME/lib:$ORACLE_HOME/lib :$ORACLE_HOME/lib:$ORACLE_HOME/lib :$ORACLE_HOME/lib:$ORACLE_HOME/lib :$ORACLE_HOME/lib:$ORACLE_HOME/lib :$ORACLE_HOME/lib:$ORACLE_HOME/lib :$ORACLE_HOME/lib:...
Jenkins: EnvInjectPlugin
© Netcracker 2016 118
https://issues.jenkins-ci.org/browse/JENKINS-19856^^^ с вами с 2013 года
Jenkins: EnvInjectPlugin
© Netcracker 2016 119
• Общение Jenkins master и slave тщательно логируется
Jenkins: master vs slave
© Netcracker 2016 120
• Общение Jenkins master и slave тщательно логируется
• «Разумеется», в памяти хранятся последние 1000 записей
Jenkins: master vs slave
© Netcracker 2016 121
• Общение Jenkins master и slave тщательно логируется
• «Разумеется», в памяти хранятся последние 1000 записей
• Н а эти логи может уходить 100-200МиБ
Jenkins: master vs slave
© Netcracker 2016 122
• Общение Jenkins master и slave тщательно логируется
• «Разумеется», в памяти хранятся последние 1000 записей
• Н• Да, да. Логи хранятся в памяти и никогда не попадают в файл
Jenkins: master vs slave
© Netcracker 2016 123
• Хорошо, что есть опция• -Dhudson.remoting.ExportTable.unexportLog=0
Jenkins: master vs slave
© Netcracker 2016 124
• Хорошо, что есть опция• -Dhudson.remoting.ExportTable.unexportLog=0
• Разумеется, в Google ровно один результат
Jenkins: master vs slave
© Netcracker 2016 125
• Хорошо, что есть опция• -Dhudson.remoting.ExportTable.unexportLog=0
• Разумеется, в Google ровно один результат• На исходный код, где она определена :)
Jenkins: master vs slave
© Netcracker 2016 126
• Оказалось, что запуск master занимает 50 минут
• 3000 jobs, 10-500 runs per job• 24 CPU, 64 GiB RAM• -Xmx40g
Jenkins: запускаемся
© Netcracker 2016 127
• При старте, Jenkins инициализирует job’ы
Jenkins: файлы разные нужны, файлы разные важны
© Netcracker 2016 128
• При старте, Jenkins инициализирует job’ы• Maven job загружает данные по всем запускам
Jenkins: файлы разные нужны, файлы разные важны
© Netcracker 2016 129
• При старте, Jenkins инициализирует job’ы• Maven job загружает данные по всем запускам
• В каждом запуске есть fingerprint’ы, они тоже грузятся
Jenkins: файлы разные нужны, файлы разные важны
© Netcracker 2016 130
• При старте, Jenkins инициализирует job’ы• Maven job загружает данные по всем запускам
• В• В итоге много-много операций с диском• и тоже грузятся
Jenkins: файлы разные нужны, файлы разные важны
© Netcracker 2016 132
• Решение в лоб: удалить fingerprint’ы перед запуском
• Более сложное: не плодить fingerprint’ы
Jenkins: чиним
© Netcracker 2016 133
• Решение в лоб: удалить fingerprint’ы перед запуском
• Более сложное: не плодить fingerprint’ы• Поможет и сокращение хранимой истории
Jenkins: чиним
© Netcracker 2016 134
• Решение в лоб: удалить fingerprint’ы перед запуском
• Более сложное: не плодить fingerprint’ы• Поможет и сокращение хранимой истории• В идеальном мире нужно разбираться с maven-jenkins-plugin
Jenkins: чиним
© Netcracker 2016 136
• Владимир Ситников• Инженер по производительности в Netcracker• [email protected]• @VladimirSitnikv
С вами был