В последние месяцы мы наблюдаем небольшой, но постоянный процент сбоев наших операций со странным исключением — org.springframework.jdbc.CannotGetJdbcConnectionException — «Не удалось получить соединение JDBC; Вложенное исключение — java.sql.SQLException: истекло время ожидания попытки клиента установить соединение . »
Нашим естественным предположением было то, что у нас есть своего рода конфликт в нашем пуле соединений C3P0, когда клиенты, пытающиеся получить соединение, должны ждать, пока оно не станет доступным. Нашим лучшим предположением было то, что именно это утверждение вызывает таймауты.
Поэтому, конечно, первое, что мы сделали, это увеличили максимальное количество соединений в пуле соединений. Однако как бы высоко мы ни устанавливали ограничение, это не помогло. Затем мы попытались изменить параметры тайм-аута соединения. Это не дало никаких лучших результатов.
В этот момент укоренился интеллект, и так как гадание, похоже, не сработало, мы решили измерить. Используя простую оболочку в пуле соединений, мы увидели, что даже когда у нас есть свободные соединения в пуле соединений, мы все равно получаем тайм-ауты проверки.
Исследование накладных расходов пула соединений
Чтобы исследовать издержки пула соединений, мы выполнили тест, состоящий из 6 раундов, каждый из которых включал 20 000 операций SQL (с соотношением чтения / записи 1:10), выполненных с использованием 20 потоков с пулом соединений из 20 соединений. Наличие 20 потоков, использующих пул с 20 соединениями, означает, что нет конкуренции за ресурсы (соединения). Поэтому любые издержки вызваны самим пулом соединений.
Мы проигнорировали результаты первого прогрева, взяв статистику из последующих 5 прогонов. Из этих данных мы получаем время проверки соединения, время освобождения соединения и общую нагрузку на пул.
Код проекта эталонного теста можно найти по адресу: https://github.com/yoavaa/connection-pool-benchmark
Мы протестировали 3 разных пула соединений:
- C3P0 — com.mchange: c3p0: 0.9.5-pre3 — класс C3P0DataSourceBenchmark
- Bone CP — com.jolbox: bonecp: 0.8.0-rc1 — класс BoneDataSourceBenchmark
- Apache DBCP — commons-dbcp: commons-dbcp: 1.4 — класс DbcpDataSourceBenchmark
(В проекте есть еще один тест моего собственного экспериментального асинхронного пула — https://github.com/yoavaa/async-connection-pool . Однако для целей этого поста я собираюсь его проигнорировать).
Для того, чтобы запустить тест самостоятельно, вы должны настроить MySQL со следующей таблицей
1
2
3
4
5
6
7
8
|
CREATE TABLE item ( file_name varchar(100) NOT NULL, user_guid varchar(50) NOT NULL, media_type varchar(16) NOT NULL, date_created datetime NOT NULL, date_updated timestamp AUTO_INCREMENT NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY(file_name) ) |
Затем обновите объект Credentials, чтобы он указывал на эту установку MySQL.
При выполнении тестов пример результата будет выглядеть так:
1
2
3
4
5
6
7
8
9
|
run, param, total time , errors, under 1000 nSec,1000 nSec - 3200 nSec,3200 nSec - 10 µSec,10 µSec - 32 µSec,32 µSec - 100 µSec,100 µSec - 320 µSec,320 µSec - 1000 µSec,1000 µSec - 3200 µSec,3200 µSec - 10 mSec,10 mSec - 32 mSec,32 mSec - 100 mSec,100 mSec - 320 mSec,320 mSec - 1000 mSec,1000 mSec - 3200 mSec,other 0, acquire,29587,0,0,5,1625,8132,738,660,1332,1787,2062,2048,1430,181,0,0,0 0, execution, , ,0,0,0,0,0,0,0,1848,6566,6456,5078,52,0,0,0 0, release, , ,0,8,6416,9848,3110,68,77,115,124,148,75,11,0,0,0 0, overhead, , ,0,0,49,4573,5459,711,1399,1812,2142,2157,1498,200,0,0,0 1, acquire,27941,0,0,125,8153,499,658,829,1588,2255,2470,2377,1013,33,0,0,0 1, execution, , ,0,0,0,0,0,0,6,1730,6105,6768,5368,23,0,0,0 1, release, , ,0,49,15722,3733,55,42,69,91,123,101,14,1,0,0,0 1, overhead, , ,0,0,2497,5819,869,830,1610,2303,2545,2448,1042,37,0,0,0 |
Эта информация была импортирована в файл Excel (также включен в эталонный проект) для анализа.
C3P0
Именно с C3P0 мы изначально видели оригинальное исключение в нашей производственной среде. Давайте посмотрим, как это работает:
Чтение графиков:
Первые три графика (приобретение, выпуск, накладные расходы) являются диаграммами ведра, основанными на производительности. Ось Y указывает количество операций, выполненных в определенном временном диапазоне (показано на оси X). Основное правило по умолчанию заключается в том, что чем выше полоски слева, тем лучше. Четвертая диаграмма — это диаграмма водопада, где каждая горизонтальная линия указывает одну операцию БД. Коричневый указывает время ожидания для установления соединения, зеленый — время выполнения операции с БД, синий — время возврата соединения в пул соединений.
Глядя на графики, мы видим, что обычно C3P0 получает соединение в течение 3,2-10 микросекунд и освобождает соединения в течение 3,2-10 микросекунд. Это определенно впечатляющая производительность. Тем не менее, C3P0 имеет еще один пик примерно в 3,2-32 миллисекунды, а также длинный хвост, достигающий 320-1000 миллисекунд. Это второй пик, который вызывает наши исключения.
Что происходит с C3P0? Чем обусловлен этот небольшой, но значительный процент чрезвычайно длительных операций, в то время как большую часть времени производительность довольно удивительна? Глядя на 4- й график, мы можем указать направление ответа.
Четвертая диаграмма имеет четкую диагональную линию слева направо и снизу вверх, что указывает на то, что в целом установление соединения начинается последовательно. Но мы можем идентифицировать что-то странное — мы можем видеть коричневые треугольники, указывающие случаи, когда несколько потоков пытаются установить соединение, первый поток ожидает больше времени, чем последующие. Это означает две группы производительности для установления соединения. Некоторые потоки получают соединение очень быстро, в то время как некоторые достигают истощения, ожидая подключения, в то время как запросы потоков опоздавших отвечают ранее.
Такое поведение, когда ранний поток ожидает дольше, чем последующий поток, означает несправедливую синхронизацию. Действительно, при копании в коде C3P0 мы видели, что во время получения соединения C3P0 использует ключевое слово « synchronized » три раза. В Java ключевое слово « synchronized » создает несправедливую блокировку, которая может вызвать голодание потока.
Мы можем попробовать установить исправление C3P0 с помощью надежных блокировок позже. Если мы сделаем это, мы, естественно, поделимся своими выводами.
Конфигурация C3P0 для этого теста:
- Минимальный размер бассейна: 20
- Начальный размер пула: 20
- Максимальный размер пула: 20
- Прирост приращения: 10
- Количество вспомогательных потоков: 6
BoneCP
Мы пробовали BoneCP в Wix со смешанными результатами, так что на данный момент мы не уверены, нравится ли нам это. Мы включили результаты тестов BoneCP в эти посты, хотя анализ не настолько исчерпывающий.
Глядя на графики, мы видим, что производительность захвата соединения Bone является выдающейся — большинство операций завершается за 3,2 микросекунды, намного быстрее, чем C3P0. Однако мы также наблюдаем, что время освобождения соединения является значительным, примерно 1-10 миллисекунд, слишком большим. Мы также наблюдаем, что у Bone есть длинный хвост операций с накладными расходами, достигающими 320 миллисекунд.
Глядя на данные, кажется, что BoneCP лучше по сравнению с C3P0 — как в нормальных, так и в «экстремальных» случаях. Однако разница невелика, о чем свидетельствуют графики. Глядя на 4- й график, мы видим, что у нас меньше коричневого цвета по сравнению с C3P0 (так как получение соединения лучше), но появляются синие висячие линии, указывающие периоды времени, в течение которых потоки ожидают освобождения соединения.
Как уже упоминалось выше, поскольку мы в лучшем случае амбивалентны в отношении использования BoneCP, мы не инвестировали значительные ресурсы в анализ проблем производительности этих пулов соединений.
Apache DBCP
Apache DBCP известен как Old Faithful из источников данных. Давайте посмотрим, как это поживает по сравнению с двумя другими.
Очевидно одно — производительность DBCP превосходит как C3P0, так и Bone. Во всех отношениях он превосходит другие альтернативы, будь то с точки зрения времени проверки соединения, времени освобождения соединения и в форме диаграммы водопада.
Так какой источник данных вы должны использовать?
Ну, это нетривиальный вопрос. Понятно, что в отношении производительности пула соединений у нас есть явный победитель — DBCP. Также кажется, что C3P0 легко исправить, и мы можем просто попробовать это.
Тем не менее, важно помнить, что область этого исследования была ограничена только производительностью фактического получения / освобождения соединения. Фактический выбор источника данных является более сложной проблемой. Этот тест, например, игнорирует некоторые важные аспекты, такие как увеличение и сжатие пула, обработка сетевых ошибок, обработка отказов в случае сбоя БД и многое другое.