Пользователь
0,0
рейтинг
7 ноября 2013 в 19:26

Разработка → Борьба с производительностью 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
Всеволод Новиков @nnseva
карма
32,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (9)

  • +3
    Непонятно только, как Pony ORM использовать с такой лицензией — она же требует открывать код сайта, который Pony ORM использует. Это ведь даже не GPL, а AGPL. Или я где-то что-то упускаю?
    • 0
      видимо только купить Enterprise license
    • 0
      Да, упускаете. Помимо AGPL (GNU Affero General Public License 3.0), есть также возможность легального свободного использования для некоммерческих целей: «Free for non-commercial use».

      ponyorm.com/license-and-pricing.htm
    • 0
      Если я не ошибаюсь, то требуется публиковать только код измененного продукта которые находится под лицензией AGPL, разве лицензия требует публикации кода продуктов которые «импортируют» AGPL-лицензированный софт?
      • 0
        If the Program as you received it is intended to interact with users through a computer network and if, in the version you received, any user interacting with the Program was given the opportunity to request transmission to that user of the Program's complete source code, you must not remove that facility from your modified version of the Program or work based on the Program, and must offer an equivalent opportunity for all users interacting with your Program through a computer network to request immediate transmission by HTTP of the complete source code of your modified version or other derivative work


        Использование либы это точно не modified version, остаётся вопрос является ли это derivative work? Учитывая что даже по поводу dynamic linking никто точно не может сказать приводит ли это к derivative work, то в случае питона вообще трудно разобраться )=
  • +1
    Я что-то не понял, вы в тестах сравниваете скорость извлечения Pony ORM данных из своего кэша со скоростью извлечения Django ORM данных из БД?
    • 0
      Нет. Если посмотреть исходники tastypie_djony, можно увидеть, что все функции диспетчеризации запросов обернуты в db_session, это означает, что курсор и весь набор кешированных данных создается именно в момент обращения к ним. Что говорит о том, что каждое обращение к API приводит к реальной выборке данных из БД.

      Этот факт я проверял и отдельно, включив отладку запросов.
  • –1
    Можно еще использовать tastypie в связке с django-haystack.
  • +1
    django-haystack — очень-очень кривой инструмент. видимо, так вышло в попытке обеспечить поддержку нескольких движков полнотекстового поиска с принципиально разными интерфейсами. никому никогда не советую его использовать. лучше взять django-приложение для конкретного движка. проверено на xapian и sphinx.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.