Сообщений 1    Оценка 0        Оценить  
Система Orphus

Тестирование производительности ORM в языках Python и С++

Автор: Найденов Вячеслав Викторович
Опубликовано: 11.01.2015
Исправлено: 10.12.2016
Версия текста: 1.1
Введение
Тестовый стенд
Оборудование и версии ПО
Состав набора тестов
Методика тестирования
Результаты тестирования
Влияние отключения логов
Выводы
Список литературы

Введение

Описанные в известной книге Мартина Фаулера [Фаулер, 1] идеи оказали сильное влияние на то, как современные приложения взаимодействуют с реляционными СУБД. Одним из наиболее продуктивных подходов в этой области является объектно-реляционный преобразователь или «маппер» (англ. Object-relational mapper, ORM). В первом приближении, маппер создает дополнительный слой абстракции с целью облегчить манипуляции с объектами и связями предметной области, хранящимися в БД. Несмотря на звучащую критику [Fowler, 3], подход ORM вывел на качественно иной уровень реализацию и сопровождение приложений баз данных.

Традиционно, одной из основных областей применения ORM является создание корпоративных приложений (англ. Enterprise applications). Здесь годами лидировали такие языки как Java и C#. Оба имеют в своем арсенале мощные решения в области ORM, например Hibernate для Java и его аналог для C# - NHibernate. Язык Python традиционно ассоциировался с Web-разработкой, однако, благодаря таким инструментам как SQLAlchemy, Python сегодня так же выглядит весьма привлекательным для enterprise.

ПРИМЕЧАНИЕ

SQLAlchemy – один из самых развитых фреймворков ORM для языка Python. SQLAlchemy стремится предоставить разработчику полный набор инструментов для работы с реляционными СУБД. Так, помимо ORM, можно конструировать запросы уровня SQL с помощью примитивов языка Python. Автор проекта, Майкл Бэйер, постулирует [Бэйер, 2], что не нужно скрывать от программиста реляционную геометрию БД. Архитектура фреймворка позволяет очень гибко настраивать отображения таблиц на объекты и связи между ними.

Что касается языка С++, то его сфера применения смещена ближе к высокопроизводительным вычислениям, встраиваемым системам. Сегодня реляционные БД применяются как универсальная форма хранения с удобным механизмом доступа для самых разнообразных данных. Поэтому, хотя и с некоторым опозданием, для С++ также появляются инструменты ORM, например Wt::Dbo [5] или ODB [4]. В данной работе будет рассмотрен фреймфорк YB.ORM [7], в создании которого я принимал участие, идеология которого во многом близка к SQLAlchemy.

Принято считать, что накладные расходы уровня библиотеки для работы с СУБД (привязка входных и выходных параметров, подготовка запроса к выполнению и пр.) невелики по сравнению с самим временем обработки запроса на стороне СУБД [Бэйер, 2]. Действительно, суммарное время выполнения запроса включает в себя «медленные» операции сетевого и, особенно, дискового ввода-вывода. Именно по этой причине, первое, что нужно проанализировать при наличии проблем с производительностью в области БД, это выполняемые приложением запросы к СУБД. На эту тему существует много литературы, как общего плана (как правильно проектировать схему данных, как правильно использовать индексы), так и специфической по конкретным СУБД.

Конечно, все что предшествует отправке запроса, и то, как происходит получение и обработка результата, – все это вносит свой вклад в общее время обработки запроса и потребление ресурсов ЦП. Как можно предположить, сложные многослойные решения типа ORM могут вносить тем более ощутимый вклад. За удобство пользования объектами для доступа к данным, удобство разработки и сопровождения, определенно приходится жертвовать производительностью приложения. Дополнительный вклад в потребление ресурсов может вносить сама платформа программирования, например, за счет использования интерпретатора или виртуальной машины.

Но каковы реальные накладные расходы при использовании тех или иных решений ORM? Действительно ли проблемы производительности SQLAlchemy незначительны, да и те сойдут на нет при использовании JIT компиляции с новым интерпретатором PyPy? Как повлияет на производительность включение отладочных логов? Для ответа на эти вопросы потребуется развернутый набор тестов, который можно прогнать для нескольких различных приложений, реализующих одинаковую логику с помощью различных решений ORM. В данной работе оцениваются накладные расходы при использовании двух решений ORM: SQLAlchemy для языка Python и YB.ORM для языка C++. Отдельно тестируются варианты стенда с SQLAlchemy под управлением интерпретаторов CPython и PyPy. Для стенда с YB.ORM тестируются два бакэнда – посредством библиотеки ODBC с ее драйверами и через C++ библиотеку SOCI [6], которая предоставляет свой набор драйверов.

Тестовый стенд

В качестве приложения для тестового стенда выбран симулятор парковочного автомата. Типичные операции для него включают в себя выдачу талона, подсчет оказанной услуги оплата услуги и выезд автомобиля со сдачей талона. Это схема с оплатой по факту оказания услуги. В дополнение к ней реализована и предоплатная схема, когда пользователь оплачивает некоторое количество времени заранее, имеет возможность продлить парковку, а при неиспользованном остатке он зачисляется на парковочный счет, который впоследствии также может использоваться для оплаты сеансов парковки. Все эти операции хорошо укладываются в CRUD семантику (англ. Create, Read, Update, Delete – вставка, выборка, обновление, удаление).

Исходные коды тестового стенда доступны на хостинге GitHub [8]. В указанном репозитории представлены тесты и две реализации тестового приложения, использующие различные платформы:

Оба приложения «приводятся в движение» с помощью единого набора тестов, который последовательно прогоняет различные случаи использования API тестового стенда. Набор тестов оформлен в виде скрипта на Python, используется модуль unittest. Естественно, оба приложения одинаково проходят все тесты из этого набора.

Чтобы один и тот же набор тестов мог проверять приложения, основанные на различных платформах, он должен использовать средства межпроцессного взаимодействия (англ. Inter-Process Communication, IPC). Наиболее общим видом IPC являются TCP сокеты, которые позволяют связываться процессам как локально, так и удаленно, на разных хостах. Тестовый стенд представляет собой реализацию API, доступ к которому осуществляется посредством HTTP запросов поверх TCP сокета, с использованием текстового формата JSON. В приложении на Python для реализации функционала HTTP сервера использован стандартный класс SimpleHTTPServer. В приложении на С++ для этой же цели использован класс HttpServer (файлы parkingxx/src/micro_http.h, parkingxx/src/micro_http.cpp), позаимствованный из примера examples/auth проекта YB.ORM. Итак, тесты общаются с запущенным локально сервером-тестовым стендом через TCP сокет.

Обработка входящих запросов на практике почти всегда идет в много потоков (нитей) или процессов. В CPython имеются проблемы с много-поточным выполнением, из-за глобальной блокировки GIL. Поэтому часто применяют много-процессный режим. С другой стороны, в C++ такой проблемы нет. На результаты этого теста не должно влиять качество реализации пула процессов/нитей для обработки запросов. Поэтому в данном тесте используется последовательный прогон набора тестов.

В качестве СУБД выбрана MySQL, используется транзакционное хранилище MySQL InnoDB. Оба приложения по очереди работают с одной и той же базой данных.

Настройка журнала в обоих серверных реализациях выполнена примерно одинаково. Выводятся сообщения от WEB-сервера и запросы к СУБД, генерируется сравнимый объем логов. Кроме того, тестируется и режим с отключенной записью логов.

Обе версии тестового стенда реализуют идентичную логику. Для облегчения сопоставления исходных кодов бизнес-логики, при реализации тестовых стендов использована похожая декомпозиция на функции. В данной статье мы не касаемся вопросов выразительных средств, которые должны предоставлять инструменты ORM. Вопрос этот имеет первостепенное значение и требует отдельного рассмотрения. Ограничимся пока сравнением размера исходных кодов. Объем исходного кода обеих версий довольно близок: 20,4 КБ (sa) против 22,5 КБ (yborm) — непосредственно бизнес логика, плюс дополнительно 3,4 КБ (sa) против 5,7 КБ (yborm) — описание модели данных. Ниже приведен пример кода метода бизнес-логики с использованием SQLAlchemy, язык Python:

      def check_plate_number(session, version=None,
                       registration_plate=None): 
    plate_number = str(registration_plate or'') 
    assert plate_number 
    active_orders_count = session.query(Order).filter( 
            (Order.plate_number == str(plate_number)) & 
            (Order.paid_until_ts > datetime.datetime.now()) & 
            (Order.finish_ts == None)).count() 
    if active_orders_count >= 1: 
        raise ApiResult(mk_resp('success', paid='true')) 
    raise ApiResult(mk_resp('success', paid='false')) 

Для сравнения, ниже приведен аналогичный код, но с использованием YB.ORM, язык C++:

ElementTree::ElementPtr check_plate_number(Session &session,
        ILogger &logger, 
        const StringDict &params)
{ 
    string plate_number = params["registration_plate"]; 
    YB_ASSERT(!plate_number.empty()); 
    LongInt active_orders_count = query<Order>(session).filter_by( 
            (Order::c.plate_number == plate_number) && 
            (Order::c.paid_until_ts > now()) && 
            (Order::c.finish_ts == Value())).count(); 
    ElementTree::ElementPtr res = mk_resp("success"); 
    res->add_json_string("paid", active_orders_count >= 1?
                         "true": "false"); 
    throw ApiResult(res); 
}

Оборудование и версии ПО

Тестирование проводится в среде Ubuntu Linux, которую является довольно распространенной платформой для развертывания серверных приложений. В качестве тестовой машины был использован домашний компьютер с процессором Intel(R) Core(TM) i5 760 @2.80GHz, объем ОЗУ 4GB, под управлением 64-битной Ubuntu Linux версии 12.04.

Версии установленного ПО достаточно стандартные для поставки Ubuntu, за исключением пакетов PyPy, PyMySQL, SOCI и YB.ORM, которые в стандартную поставку не входят:

Состав набора тестов

Состав набора тестов по функциям приведен в [таблица 1]. Функции, выполняющие модификацию данных, выделены курсивом. Вклад в общее время выполнения приведен приблизительно, на основе данных варианта сервера с YB.ORM и бакэндом SOCI.

Таблица 1. Состав набора тестов по функциям

Вызов

кол-во обращений при одном прогоне набора

вклад в общее время выполнения набора, %

get_service_info

22

8,95

create_reservation

10

26,67

pay_reservation

8

19,30

get_user_account

7

1,24

stop_service

6

13,81

account_transfer

6

13,14

cancel_reservation

4

10,69

check_plate_number

3

0,60

leave_parking

1

2,69

issue_ticket

1

2,91

всего

68

100

Для того, чтобы понять насколько сравнимы в принципе две реализации на разных языках программирования нужно также рассмотреть состав данного теста с точки зрения выполняемых SQL операторов, см. [таблица 2]. Для варианта с YB.ORM и с SQLAlchemy показания будут несколько отличаться из-за деталей реализации ORM и самого тестового приложения. Однако на функционал, как показывают тесты, эти различия не влияют.

Таблица 2. Состав набора тестов по операторам SQL

Оператор

кол-во вызовов из SQLAlchemy

кол-во вызовов из YB.ORM

SELECT

78

106

SELECT FOR UPDATE

88

89

UPDATE

45

42

INSERT

27

27

DELETE

0

0

Всего

238

264

Безусловно, данный набор тестов создает нагрузку далекую от того, что можно наблюдать в реальной жизни. Однако, он позволяет составить некоторое представление о производительности одной прослойки ORM в сравнении с другой.

Методика тестирования

При тестировании производились последовательные запуски имеющегося в нашем распоряжении набора тестов в пакетном режиме по 20 прогонов. Для уменьшения погрешности, для каждой модификации сервера измерение времени работы пакета производилось по пять раз. Итого для каждой модификации сервера набор тестов прогонялся по 100 раз.

Для замера времени использовалась стандартная Unix команда time, которая выводит время, проведенное процессом в режимах пользовательского процесса (user) и ядра (sys), а также реальное время (real) выполнения команды. Это именно то время, которое влияет на впечатление пользователя от работы с системой.

Для клиентской части учитывается реальное время выполнения (real). Стандартный вывод и вывод ошибок перенаправлены в /dev/null.

$ time ( for x in `yes | head -n 20`; do python parking_http_tests.py -apu http://localhost:8111/ &> /dev/null ; done ) 

real  0m24.762s 
user  0m3.312s 
sys  0m1.192s 

Для выполняющегося серверного процесса интерес представляют времена user и sys, так как именно они показывают фактическое потребление ресурса CPU. Время real будет включать в себя и простой в ожидании входящего запроса, поэтому в результатах оно не приводится. Кроме того, чтобы увидеть измеренное время, нужно остановить сервер, так как в норме он работает в бесконечном цикле, принимая и обрабатывая запросы.

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

Всего тестирование проводилось для 4 модификаций сервера:

Для каждой модификации производилось измерение в двух режимах: с включенной записью логов и без нее. Итого — 8 вариантов. Усреднение проводилось по итогам 5 измерений. Измеренное время работы пакета из 20 последовательных запусков набора тестов приведено на [рисунок 1].


Рисунок 1. Время работы пакета тестов на клиенте, чем меньше, тем лучше

С точки зрения администрирования не менее важно потребление ресурсов процессора на стороне сервера. Чем больше потребляется CPU на сервере, тем больше нужно ядер для обслуживания одного и того же количества входящих запросов, тем больше будет энергопотребление. Потребление CPU складывается из времени, проведенного процессом в пользовательском режиме и режиме ядра. Измеренные значения приведены на [рисунок 2].


Рисунок 2. Потребление ресурсов центрального процессора на сервере, чем меньше тем лучше

Влияние отключения логов

При анализе возможных путей увеличения производительности часто обращают внимание на отладочную функциональность записи логов. При использовании ORM без ведения отладочных логов просто не обойтись: необходимо иметь способ проконтролировать автоматику ORM. В боевой же среде запись логов зачастую можно свети к минимуму или отключить совсем, особенно когда функционал приложения меняется нечасто и уже достаточно отлажен.

Но каков генерируемый объем логов? В данном тестовом приложении в лог записывались сообщения от HTTP сервера, все SQL запросы, а также входные и выходные данные этих запросов. Как было сказано выше, набор тестов запускался по 100 раз для каждой из модификаций сервера. При этом объем полученных логов можно увидеть в [таблица 3].

Таблица 3. Объем логов после 100 запусков набора тестов

pypy sa

cpython sa

yborm soci

yborm odbc

объем логов, МБ

19,02

18,97

20,78

20.95

объем тыс. строк

138,5

138,5

180,0

180,0

Проведенные измерения показывают [таблица 4] насколько процентов уменьшается время выполнения пакета тестов на клиенте (первая строка таблицы), и на сколько процентов уменьшается потребление ресурса процессора на сервере (вторая строка таблицы) при отключении записи логов на стороне сервера.

Таблица 4. Прирост производительности в процентах при отключении записи логов

pypy sa

cpython sa

yborm soci

yborm odbc

тесты на клиенте

10.0

12.4

11.5

12.6

потребление CPU сервера

11.0

26.0

19.2

21,4

Выводы

Среди рассмотренных реализаций потребление CPU наиболее быстрой реализацией на С++ (yborm odbc) в три раза меньше, чем наиболее быстрой на Python (cpython sa). При этом объем кода отличается менее чем на 20 %.

Время ответа от сервера, то есть время ожидания пользователя, при определенных условиях тоже может уменьшиться при использовании варианта с YB.ORM. В данном примере наблюдалось улучшение порядка 38 %.

Производительность при использовании многослойных фреймворков, обещанная новым интерпретатором PyPy, пока не достигнута.

По причинам, требующим дальнейшего исследования, при использовании YB.ORM бакэнд SOCI показал слегка худшую производительность, чем бакэнд ODBC.

Отключение записи логов на стороне серверного приложения дает не столь ощутимый прирост производительности, как можно было бы предположить. В частности, самый значительный прирост на стороне сервера наблюдается при использовании связки CPython+SQLAlchemy: 26 %. Для вариантов с YB.ORM этот показатель колеблется в районе 20 %.

Язык Python уже зарекомендовал себя как язык высокой продуктивности разработчика. В том числе за счет более короткого цикла «правка» — «выполнение». Наличие прекрасных инструментов вроде SQLAlchemy еще более усиливает позиции этой платформы. В то же время нельзя забывать о возможных проблемах производительности. В некоторых случаях выгодней использовать среду Python для прототипирования, а окончательную реализацию поручить языку C++, для которого сейчас уже появились сравнимые по функциональности фреймворки.

Список литературы

  1. Мартин Фаулер. Архитектура корпоративных программных приложений.: Пер. с англ. — М.: Издательский дом "Вильямc", 2006. — 544 с.
  2. Майкл Бэйер. Архитектура приложений с открытым исходным кодом. Т. 2: глава 20. SQLAlchemy.: Пер. с англ. А. Панин. URL: http://rus-linux.net/MyLDP/BOOKS/Architecture-Open-Source-Applications/Vol-2/sqlalchemy-01.html (дата публикации перевода: 23.10.2013).
  3. Martin Fowler. OrmHate [статья в личном блоге]. URL: http://martinfowler.com/bliki/OrmHate.html (дата публикации: 08.05.2012).
  4. ODB: C++ Object-Relational Mapping (ORM) / Code Synthesis Tools CC. [страница продукта]. URL: http://www.codesynthesis.com/products/odb/ (дата последнего выпуска: 30.10.2013).
  5. Koen Deforche. Wt::Dbo Tutorial. [он-лайн руководство]. URL: http://www.webtoolkit.eu/wt/doc/tutorial/dbo/ (дата обновления: 14.07.2011).
  6. Mateusz Loskot, Vadim Zeitlin, Maciej Sobczak и др. Документация проекта SOCI [сайт]. URL: http://soci.sourceforge.net/doc/3.2/ (дата публикации: 12.09.2013)
  7. Найденов Вячеслав. Документация проекта YB.ORM [сайт]. URL: http://sourceforge.net/p/yborm/wikidoc/ru_About/ (дата публикации: 17.07.2014).
  8. Найденов Вячеслав. Исходные коды тестового стенда «Парковка» и набор тестов [репозиторий]. URL: https://github.com/vnaydionov/teststand-parking (дата публикации: 10.08.2014).


Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав.
    Сообщений 1    Оценка 0        Оценить