Борьба с производительностью Tastypie API

от автора

Как я перетащил пакет Tastypie на Pony ORM и что из этого получилось.

Данная статья продолжает историю моей борьбы за производительность приложений на python и django.

Краткое содержание предыдущих серий:
ORMы тормозят, причем очень сильно — в 3-5 и более раз
А вот заляпочка для Django ORM
Ух ты, вот этот ORM почти не тормозит и кеширует все что только можно
Вот так можно прикрутить высокопроизводительный Pony ORM к проекту Django и использовать его в ключевых местах

Введение

Получив вполне удовлетворительные (по сравнению с Django ORM) результаты по скорости обращения к СУБД с помощью Pony ORM, я начал думать, как мне использовать их на практике. Для начала, я сделал отображение модели данных Django на модель данных Pony. Теперь, готовую модель данных можно использовать в проекте.

Там где код пишется с нуля, использовать модель Pony вместо Django стало не просто, а очень просто: атрибут `p` у всех моделей Django теперь указывает на модель Pony. Однако — что же делать с теми готовыми пакетами, которые уже используют Django ORM?

К сожалению, ответ один: переписывать или дописывать, поскольку синтаксис и семантика обращения к моделям и коллекциям у Pony ORM кардинально отличается от Django ORM — настолько, что замаскировать обращение к Pony ORM под обращение к Django ORM пока не представляется возможным. Кроме того, я сильно подозреваю, что такая маскировка, будучи произведена, убьет все преимущества использования Pony ORM за счет потерь на промежуточном слое. Все-таки python — интерпретатор, и потери например на вызове функции велики достаточно, чтобы быть заметными даже на фоне обращения к СУБД. Именно такие потери, в числе прочих, вероятно убивают производительность Django ORM и SQLAlchemy.

Мы планируем перевести весь интерфейс (или его бОльшую часть) нашего приложения на клиентскую сторону, используя сервер как поставщик данных через HTTP API. Таким образом, ключевым элементом становится API, которое мы реализуем с помощью пакета Tastypie, так что повышение его производительности самым непосредственным образом должно отразиться на производительности интерфейса приложения и снижении общей нагрузки на сайт.

И я взялся за Tastypie.

Как мне помогла структура Tastypie

Библиотека Tastypie, хоть и опирается на Django, имеет слой абстракции источников данных (Resource), независимый от Django ORM. Лишь следующий наследник, ModelResource, использует специфические для Django ORM обращения к данным. Таким образом, как я предполагал вначале, мне нужно было только унаследоваться от Resource и реализовать функционал, аналогичный ModelResource.

На практике, мне пришлось также сделать класс, параллельный ToManyField, поскольку к моему разочарованию, в последнем оказалось несколько строк, ориентированных именно на Django ORM. Свой класс я обозвал SetField, поскольку именно класс Set используется в Pony ORM для обозначения отношения один-к-многим.

Что получилось

Получилось все просто замечательно. В качестве образца, я использовал модель данных из django.contrib.auth, заполнив ее примерно 1000 пользователей. Одному из пользователей я присвоил 159 прав на различные типы данных в системе (их — типов данных — у нас много накопилось) и запрашивал этого пользователя вместе с его правами через API, используя этот вызов в качестве образца.

Вот как выглядело определение источника данных для старого API v2, использовавшего Django ORM (обратите внимание, что аутентификация и авторизация исключены — для корректного измерения производительности).

class PermissionResource(ModelResource):     class Meta:         queryset = auth_models.Permission.objects.all()         object_model = queryset.model         filtering = dict([(n,ALL_WITH_RELATIONS) for n in object_model._meta.get_all_field_names()])         resource_name = 'auth/permission'  class UserResource(ModelResource):     user_permissions = ToManyField(PermissionResource,'user_permissions',related_name='user_set',null=True)     class Meta:         queryset = auth_models.User.objects.all()         object_model = queryset.model         filtering = dict([(n,ALL_WITH_RELATIONS) for n in object_model._meta.get_all_field_names()])         resource_name = 'auth/user' 

Ну а вот так выглядит определение для нового API v3, ориентированного на использование Pony ORM совместно с пакетом Djony:

class PermissionResource(DjonyResource):     class Meta:         object_model = auth_models.Permission         filtering = dict([(n,ALL_WITH_RELATIONS) for n in object_model._meta.get_all_field_names()])         resource_name = 'auth/permission'  class UserResource(DjonyResource):     user_permissions = SetField(PermissionResource,'user_permissions',related_name='user_set',null=True)     class Meta:         object_model = auth_models.User         filtering = dict([(n,ALL_WITH_RELATIONS) for n in object_model._meta.get_all_field_names()])         resource_name = 'auth/user' 

Обратите внимание, что в определении метаданных используется модель Django а не Pony. Это связано с тем, что модель Pony не содержит многих атрибутов, характерных для Django ORM, но не нужных для определения собственно структуры данных, таких как например help_text. Вопрос о размещении таких метаданных на самом деле важен, но вероятно, его обсуждение выходит за рамки данной статьи.

Сервер был развернут на моем скромном рабочем компьютере, имеющим 2 ядра на 2.2 ГГц и 3 Гб оперативной памяти. Тестовый клиент запускался с того же компьютера. В качестве последнего, запускался пакет ab (Apache Benchmark):

seva@SEVA (2):~/djony$ ab -n 100 -c 4 "http://localhost:8080/api/v2/auth/user/3/?format=json" This is ApacheBench, Version 2.3 <$Revision: 655654 $> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Software Foundation, http://www.apache.org/  Benchmarking localhost (be patient).....done   Server Software:        nginx/1.1.19 Server Hostname:        localhost Server Port:            8080  Document Path:          /api/v2/auth/user/3/?format=json Document Length:        5467 bytes  Concurrency Level:      4 Time taken for tests:   17.331 seconds Complete requests:      100 Failed requests:        0 Write errors:           0 Total transferred:      582900 bytes HTML transferred:       546700 bytes Requests per second:    5.77 [#/sec] (mean) Time per request:       693.256 [ms] (mean) Time per request:       173.314 [ms] (mean, across all concurrent requests) Transfer rate:          32.84 [Kbytes/sec] received  Connection Times (ms)               min  mean[+/-sd] median   max Connect:        0    0   1.5      0       9 Processing:   313  685 486.1    575    3357 Waiting:      312  684 485.8    574    3355 Total:        313  685 486.7    575    3357  Percentage of the requests served within a certain time (ms)   50%    575   66%    618   75%    647   80%    670   90%    819   95%   1320   98%   2797   99%   3357  100%   3357 (longest request) seva@SEVA (2):~/djony$ ab -n 100 -c 4 "http://localhost:8080/api/v3/auth/user/3/?format=json" This is ApacheBench, Version 2.3 <$Revision: 655654 $> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Software Foundation, http://www.apache.org/  Benchmarking localhost (be patient).....done   Server Software:        nginx/1.1.19 Server Hostname:        localhost Server Port:            8080  Document Path:          /api/v3/auth/user/3/?format=json Document Length:        5467 bytes  Concurrency Level:      4 Time taken for tests:   8.339 seconds Complete requests:      100 Failed requests:        0 Write errors:           0 Total transferred:      582900 bytes HTML transferred:       546700 bytes Requests per second:    11.99 [#/sec] (mean) Time per request:       333.557 [ms] (mean) Time per request:       83.389 [ms] (mean, across all concurrent requests) Transfer rate:          68.26 [Kbytes/sec] received  Connection Times (ms)               min  mean[+/-sd] median   max Connect:        0    0   0.0      0       0 Processing:   137  317 375.9    243    2753 Waiting:      137  316 375.7    243    2751 Total:        137  317 375.9    243    2753  Percentage of the requests served within a certain time (ms)   50%    243   66%    264   75%    282   80%    299   90%    351   95%    433   98%   2670   99%   2753  100%   2753 (longest request)  

Как видите, применение Pony ORM дало здесь повышение производительности API в 2 раза.

Во втором примере, я запрашиваю все объекты из таблицы. Здесь повышение производительности еще более значительно:

seva@SEVA (2):~/djony$ ab -n 20 -c 4 "http://localhost:8080/api/v2/auth/user/?format=json&limit=0" This is ApacheBench, Version 2.3 <$Revision: 655654 $> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Software Foundation, http://www.apache.org/  Benchmarking localhost (be patient).....done   Server Software:        nginx/1.1.19 Server Hostname:        localhost Server Port:            8080  Document Path:          /api/v2/auth/user/?format=json&limit=0 Document Length:        306326 bytes  Concurrency Level:      4 Time taken for tests:   40.891 seconds Complete requests:      20 Failed requests:        0 Write errors:           0 Total transferred:      6133760 bytes HTML transferred:       6126520 bytes Requests per second:    0.49 [#/sec] (mean) Time per request:       8178.157 [ms] (mean) Time per request:       2044.539 [ms] (mean, across all concurrent requests) Transfer rate:          146.49 [Kbytes/sec] received  Connection Times (ms)               min  mean[+/-sd] median   max Connect:        0    0   0.1      0       0 Processing:  6235 7976 1035.4   7980   10671 Waiting:     6225 7959 1033.0   7958   10654 Total:       6235 7976 1035.4   7980   10671  Percentage of the requests served within a certain time (ms)   50%   7980   66%   8177   75%   8287   80%   8390   90%  10001   95%  10671   98%  10671   99%  10671  100%  10671 (longest request) seva@SEVA (2):~/djony$ ab -n 20 -c 4 "http://localhost:8080/api/v3/auth/user/?format=json&limit=0" This is ApacheBench, Version 2.3 <$Revision: 655654 $> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Software Foundation, http://www.apache.org/  Benchmarking localhost (be patient).....done   Server Software:        nginx/1.1.19 Server Hostname:        localhost Server Port:            8080  Document Path:          /api/v3/auth/user/?format=json&limit=0 Document Length:        306326 bytes  Concurrency Level:      4 Time taken for tests:   11.841 seconds Complete requests:      20 Failed requests:        0 Write errors:           0 Total transferred:      6133760 bytes HTML transferred:       6126520 bytes Requests per second:    1.69 [#/sec] (mean) Time per request:       2368.136 [ms] (mean) Time per request:       592.034 [ms] (mean, across all concurrent requests) Transfer rate:          505.88 [Kbytes/sec] received  Connection Times (ms)               min  mean[+/-sd] median   max Connect:        0    0   0.1      0       0 Processing:  1024 2269 806.2   2227    4492 Waiting:     1017 2252 803.6   2211    4472 Total:       1024 2269 806.2   2227    4492  Percentage of the requests served within a certain time (ms)   50%   2227   66%   2336   75%   2395   80%   2406   90%   4140   95%   4492   98%   4492   99%   4492  100%   4492 (longest request)  

Производительность API повысилась более чем в 4 раза!

Что еще предстоит

Пока не реализованы авторизация и аутентификация, впрочем я не вижу особых проблем при их реализации.

Есть проблема с реализацией фильтров по критериям вида regex, week_day и search, поскольку в Pony ORM аналогичные критерии пока отсутствуют.

Заключение

Внедрение Pony ORM в качестве замены Django ORM в готовые пакеты-приложения Django является вполне возможным и очень полезным в плане повышения производительности занятием. Делу сильно помогает хорошая абстракция классов внутри пакета, в частности — наличие слоя, не зависящего от Django ORM.

Я приглашаю всех, кто заинтересован в повышении производительности своих приложений Django, присоединяться к открытым проектам djony (скрещивание Pony и Django) и tastypie_djony (ускорение Tastypie с помощью Pony ORM) для их быстрейшего доведения до устойчивого промышленного образца.

Проект Pony ORM является проектом с открытым исходным кодом.

Традиционно напоминаю, что разработчики Pony ORM являются пока еще readonly-пользователями Хабрахабра:

AlexeyMalashkevich m.alexey@gmail.com

metaprogrammer alexander.kozlovsky@gmail.com

ссылка на оригинал статьи http://habrahabr.ru/post/201294/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *