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

в 15:26, , рубрики: django, orm, pony, python, высокая производительность, метки: , , ,

Как я перетащил пакет 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

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js