Тестирование производительности Python ORM методом, основанном на бенчмарке TPC-C
- среда, 8 апреля 2020 г. в 00:27:43
При написании приложений на Python, для работы с базами данных часто используются объектно-реляционные мапперы (ORM). Примерами ORM являются SQLALchemy, PonyORM и объектно-реляционный маппер, входящий в состав Django. При выборе ORM довольно важную роль играет её производительность.
На Хабре, да и в интернете в целом, можно найти не один тест производительности. Как пример качественного бенчмарка python ORM можно привести бенчмарк от Tortoise ORM (ссылка на репозиторий). Данный бенчмарк анализирует скорость работы шести ORM для одиннадцати различных видов SQL-запросов.
В целом бенчмарк от tortoise хорошо позволяет оценить скорость выполнения запросов при использовании разных ORM, но у такого подхода к тестированию я вижу одну проблему. ORM зачастую используют в веб приложениях, где одновременно несколько пользователей могут посылать различные запросы, но я не нашел ни одного бенчмарка, оценивающего работу ORM при таких условиях. Вследствие этого я решил написать свой бенчмарк и сравнить с помощью него PonyORM и SQLAlchemy. За основу я взял бенчмарк TPC-C.
Компания TPC с 1988 года разрабатывает тесты, направленные на обработку данных. Они давно стали индустриальным стандартом и используются почти всеми вендорами оборудования на различных образцах аппаратного и программного обеспечения. Главная особенность этих тестов состоит в том, что они нацелены на тестирование при огромной нагрузке в условиях, максимально приближенных к реальным.
TPC-C симулирует работу сети складов. Он включает в себя комбинацию из одновременно выполняемых транзакций пяти различных типов и сложности. База данных состоит из девяти таблиц с большим количеством записей. Производительность в тесте TPC-C измеряется в транзакциях в минуту.
Я решил протестировать два Python ORM (SQLALchemy и PonyORM) с использованием метода тестирования TPC-C, адаптированного под данную задачу. Целью теста является оценка скорости обработки транзакций при обращении к базе одновременно нескольких виртуальных пользователей.
Описание теста
В написанном мной тесте сначала создается и наполняется база данных, которая представляет из себя базу сети складов. Схема БД выглядит так:
База данных состоит из восьми отношений:
В ходе теста обрабатываются транзакции, посылаемыe одновременно от лица нескольких виртуальных пользователей. Каждая транзакция состоит из нескольких запросов. Всего в данном тесте существует пять видов транзакций, которые подаются на обработку с разной процентной вероятностью:
Вероятность появления транзакций такая же, как и в оригинальном тесте TPC-C.
По сравнению с оригинальным тестом TPC-C данный тест несколько упрощен, в связи с техническими ограничениями и тем, что я хочу проверить производительность ORM, а не устойчивость железа к нагрузке. Оригинальный тест проводится на серверах с 64+ ГБ оперативной памяти, большим количеством процессоров и огромным дисковым пространством.
Основные различия:
Далее я кратко опишу, что делает каждая транзакция.
New Order
Payment
Order Status
Delivery
Stock Level
Результаты тестирования
В тестировании участвуют два ORM:
Ниже приведены результаты запуска теста на 10 минут с 2 параллельными процессами, обращающимися к базе. Процессы запускаются с помощью модуля multiprocessing.
Ось Х — время в минутах
Ось У — количество выполненных транзакций
В качестве СУБД используется PostgreSQL
Запуск со всеми транзакциям
Сначала я запустил тест со всеми пятью транзакциями, как и предполагается в тесте TPC-C. В результате данного теста Pony оказалась быстрее примерно в два с половиной раза.
Средняя скорость:
Pony — 2543 тран/мин
SQLAlchemy — 1055.8 тран/мин
После этого я решил отдельно оценить производительность данных ORM на каждой из пяти транзакций по отдельности. Ниже приведены результаты для каждой отдельной транзакции.
Транзакция “New Order”
Средняя скорость:
Pony — 3349.2 тран/мин
SQLAlchemy — 802.1 тран/мин
Транзакция “Payment”
Средняя скорость:
Pony — 7175.3 тран/мин
SQLAlchemy — 4110.6 тран/мин
Транзакция “Order Status”
Средняя скорость:
Pony — 16645.6 тран/мин
SQLAlchemy — 4820.8 тран/мин
Транзакция “Delivery”
Средняя скорость:
SQLAlchemy — 716.9 тран/мин
Pony — 323.5 тран/мин
Транзакция “Stock Level”
Средняя скорость:
Pony — 677.3 тран/мин
SQLAlchemy — 167.9 тран/мин
Анализ результатов тестирования
После получения результатов я проанализировал, почему в различных ситуациях одна ORM работает быстрее другой и пришел к следующим выводам:
Пример подобного запроса на PonyORM:
stocks = select(stock for stock in Stock
if stock.warehouse == whouse
and stock.item in items).order_by(Stock.id).for_update()
Пример аналогичного запроса на SQLAlchemy:
stocks = session.query(Stock).filter(
Stock.warehouse == whouse
and Stock.item in items).order_by(text("id")).with_for_update()
Вот пример такого запроса, как он записан в логах SQLAlchemy:
INFO:sqlalchemy.engine.base.Engine:UPDATE order_line SET delivery_d=%(delivery_d)s WHERE order_line.id = %(order_line_id)s
INFO:sqlalchemy.engine.base.Engine:(
{'delivery_d': datetime.datetime(2020, 4, 6, 14, 33, 6, 922281), 'order_line_id': 316},
{'delivery_d': datetime.datetime(2020, 4, 6, 14, 33, 6, 922272), 'order_line_id': 317},
{'delivery_d': datetime.datetime(2020, 4, 6, 14, 33, 6, 922261))
Вывод
По итогу данного тестирования, могу сказать, что Pony работает гораздо быстрее при выборке из базы данных, а SQLAlchemy в некоторых случаях может с заметно большей скоростью производить запросы типа Update.
В дальнейшем я планирую протестировать таким способом и другие ORM (Peewee, Django).
Ссылки
Код теста: ссылка на репозиторий
SQLAlchemy: документация, комьюнити
Pony: документация, комьюнити