Борьба с производительностью 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
    • +10
    • 6,6k
    • 9
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 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.

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