Пользователь
0,0
рейтинг
17 октября 2013 в 15:30

Разработка → Внедрение высокопроизводительного Pony ORM в проект Django

Предыдущий пост посвященный производительности, описывал Pony ORM, показавший фантастические результаты по сравнению с Django ORM и SQLAlchemy.

Впечатленный столь неординарными результатами и озабоченный производительностью собственного проекта, я решил внедрить Pony ORM в свой проект. Что из этого получилось, см подкатом.


Соблазны


Конечно, можно было бы переписать проект заново. Этот соблазн всегда витает над разработчиком, встретившим проблемы, корни которых лежат в используемом инструменте. Однако, объем уже написанного кода, огромная разветвленная модель данных, да и впечатляющее количество использованных в проекте вполне рабочих плагинов-приложений для Django ставят на этом пути большой и жирный крест.

Альтернатива


Некоторое время назад, в поисках альтернативы Django ORM, я наткнулся на интересный проект Aldjemy. Это небольшая навеска над Django ORM, позволяющая использовать структуру моделей Django для построения альтернативной иерархии моделей SQLAlchemy. Такой подход дает возможность, сохраняя основу проекта (в том числе — всю модель данных Django), использовать SQLAlchemy именно и только там, где вы захотите. Вдохновленный идеей этого проекта и слизав некоторое количество строк кода, я сделал аналогичную библиотеку для прикрутки Pony ORM к Django ORM, назвав ее djony (DJango pONY).

Краткий экскурс


Использование djony более чем просто. После установки djony в систему (например с помощью команды pip install git+git://github.com/nnseva/djony.git@master#egg=djony), мы можем указать djony в качестве одного из используемых приложений в settings.py. Нужно только помнить, что djony обязательно должен быть самым последним приложением в списке.

Теперь у каждой из моделей Django (именно моделей — как классов) появился атрибут `p` (от слова pony). Это и есть модель Pony ORM. Ее можно использовать везде, где согласно документации Pony, нужно использовать модель. Вам понадобится также модуль orm, его можно импортировать из модуля pony (from pony import orm). Альтернативно, вы можете использовать модуль djony.orm, содержащий все переменные модуля pony.orm, а также специфические для djony функции и переменные.

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

Тест


Попробуем теперь потестировать на производительность одну из самых популярных операций — проверку прав пользователя.

Для Django мы будем использовать готовую функцию User.has_perm. Разумеется, для Pony ORM нужно будет написать код, примерно эквивалентный этой функции::

def has_perm(user,perm):
    if not user.is_active:
        return False
    app_label,codename = perm.split('.')
    for p in orm.select(
            p for p in Permission.p
            if
                (user in p.user_set or user in p.group_set.user_set) and
                p.codename == codename and
                p.content_type.app_label == app_label
    ):
        return True
    return False


Посмотрим на результаты тестирования (для тестирования, в базе было заведено 1000 пользователей, выполняющих для теста роль балласта).

>>> import test_pony
>>> import test_django
>>> test_django.test_django()
check user permissions: django req/seq: 170.308759221 req time (ms): 5.8716886
>>> test_pony.test_pony()
check user permissions: pony req/seq: 729.517146462 req time (ms): 1.3707697


Как видим, мы получили прирост более чем в 4 раза. Очень неплохо!

Приложение. Код тестов.



test_django.py


import datetime

from django.contrib.auth.models import User

def test_django():
    t1 = datetime.datetime.now()
    for i in range(10000):
        test()
    t2 = datetime.datetime.now()
    print "check user permissions: django req/seq:",10000/(t2-t1).total_seconds(),'req time (ms):',(t2-t1).total_seconds()/10.

def test():
    user = User.objects.get(username='testuser')
    return user.has_perm('auth.add_user')


test_pony.py


import datetime

from django.contrib.auth.models import User, Permission

def test_pony():
    t1 = datetime.datetime.now()
    for i in range(10000):
        test()
    t2 = datetime.datetime.now()
    print "check user permissions: pony req/seq:",10000/(t2-t1).total_seconds(),'req time (ms):',(t2-t1).total_seconds()/10.

from djony import orm

@orm.db_session
def test():
    user = User.p.get(username='testuser')
    return has_perm(user,'auth.add_user')

def has_perm(user,perm):
    if not user.is_active:
        return False
    app_label,codename = perm.split('.')
    for p in orm.select(
            p for p in Permission.p
            if
                (user in p.user_set or user in p.group_set.user_set) and
                p.codename == codename and
                p.content_type.app_label == app_label
    ):
        return True
    return False
Всеволод Новиков @nnseva
карма
32,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

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

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

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

  • 0
    Я, кстати, был одним из первых юзеров пони, т.к. они на реддит проскочили. Но не в Django, конечно, а просто в виде ORM.

    Вопрос — пони уже поддерживает connection pooling?
    • 0
      Насколько я понимаю, да. В том числе в тредах (по кр.мере судя по коду, сам пока не тестировал).
  • 0
    Очень вкусно выглядит, надо будет на своих проектах посмотреть, если действительно так, то перейти. Спасибо за статью.
  • 0
    А не рассматривали такой вариант как MongoEngine с mongodb?
    • +1
      oRm, R = Relational.
      • 0
        Это понятно, автор статьи писал об озабоченности производительностью и соблазне все переписать. Насколько я знаю MongoEngine может работать с django и имеет интрефейс Django ORM. Понятное дело в данном случае нужно будет перевсети данные под монгу, но если текущая модель нормально ляжет на монгу, то это будет гораздо проще сделать, чем переписать приложение заново, и возможно получить выгоду за счет нового ORM/ODM.
    • 0
      Признаться, у меня есть серьезные сомнения в производительности монги, но тестов я еще не проводил.
    • –1
      Лучше сразу в /dev/null, не? :p
  • +1
    платная ORM, непривычно
    • 0
      GPL 3 же (вроде как) либо платите, если не опен сурс
      • 0
        Не совсем так, не opensource, а именно GPL3, это вирусная лицензия.
        В прочем в случае веб-приложения вы и так отдаёте заказчику исходники, так что никаких проблем не вижу.
        • 0
          Отдаете заказчику это не GPL, вы должны опубликовать исходники.
          • 0
            Отдаете заказчику это не GPL

            Извините, но не распарсил фразу, не могли бы перефразировать?
            • 0
              В прочем в случае веб-приложения вы и так отдаёте заказчику исходники, так что никаких проблем не вижу.


              Как это позволит удовлетворить условиям GPL?
              • 0
                А чем это мешает GPL?
                • 0
                  Ничем не мешает, но и не удовлетворяет.

                  For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights.


                  Код нужно будет сделать открытым всем.
                  • +1
                    Не всем, а только тем, кому распространяется программа. В данном случае — только заказчику.
                    • 0
                      Ну вы же понимаете, что потом заказчику надо будет открывать всем, кто пользуется web-приложением?
                      • 0
                        Нет, не надо будет.
                        Но если вам хочется, чтобы было так, то используйте AGPL (http://ru.wikipedia.org/wiki/GNU_Affero_General_Public_License).
                        • 0
                          ponyorm.com/license-and-pricing.html:

                          «Pony ORM can be used under one of the following licenses:

                          — Open source project: GNU Affero General Public License 3.0»

                          Я думал мы знаем, что обсуждаем.
                          • 0
                            Признаю свою невнимательность, я лишь отвечал на комментарий про GPL
                            GPL 3 же (вроде как) либо платите, если не опен сурс
    • 0
      Платных опенсорс продуктов немало, и сама лицензия не ограничивает финансовую модель.
  • 0
    Да кстати, если вы хотите получить здесь комментарии разработчиков Pony ORM и у вас случайно завалялась пара инвайтов, вот их аккаунты на хабре: AlexeyMalashkevich,
    metaprogrammer.
  • +1
    Тестовая функция has_perm для test_pony намного проще оригинальной реализации в Джанго. Наверное, для чистоты эксперименты было бы лучше создать пару тестовых моделей.

    Интересно какая версия Джанги тестировалась в этой и предыдущих статьях.

    Насколько различаются SQL запросы генерируемые Django ORM и Djorny.

    Как поменяются результаты если делать случайные выборки имен пользователей, а не одного пользователя как сейчас.
    • 0
      Тестовая функция has_perm для test_pony намного проще оригинальной реализации в Джанго.


      Не намного, но да, попроще. Однако предыдущие статьи по-моему вполне убедительно показывают приращение скорости при использовании Pony ORM, по сравнению с Django ORM на эквивалентных запросах, вплоть до идентичного сгенерированного SQL.

      Интересно какая версия Джанги тестировалась в этой и предыдущих статьях.


      В этой статье использовалась Django 1.5, в предыдущей по-моему 1.4, сейчас не помню.

      Насколько различаются SQL запросы генерируемые Django ORM и Djorny.


      В простейших случаях, запросы идентичны. Более сложные требуют абсолютно различного синтаксиса обращений в Django и Pony, поэтому сравнивать сгенерированные запросы не совсем корректно.

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


      Разумеется, общее время несколько увеличится за счет бОльшей нагрузки на СУБД, поэтому относительное преимущество Pony будет снижено. Хочу однако отметить, что при этом потери Django ORM никуда не денутся и по-прежнему будут создавать неизменную в абсолютном выражении паразитную нагрузку на процессор.

      На наших (у нас в конторе) задачах, при использовании ТОЛЬКО Django ORM для доступа к данным, нагрузка в целом распределяется как 5-10% на SQL и 85-90% на python. В случае замены на доступ напрямую к библиотеке СУБД, нагрузка распределяется примерно поровну. При использовании Pony ORM по предварительным результатам, нагрузка распределяется примерно как 25%/70%.

      Приглашаю почитать также следующую статью почти на ту же тему — habrahabr.ru/post/201294/
      • 0
        > Не намного, но да, попроще.

        — Это не ответ для бенчмарков. Вы должны обеспечить идентичность условий. В Джанго этот метод запускает поиск по коллекции бэкенднов авторизации, и многое зависит от конфигурации сайта.

        В любом случае, — для сравнительного тестирования условия должны быть идентичными.

        А самое интересное, — за счет чего достигается эта скорость.

        Часто бывает, что люди лукавят, и делают кеширование, например, в словаре, потребляя понапрасну память. В таком случае показатели будут фантастическими. У Вас случайно не такой же случай? Беглый греп исходников намекает именно на это.

        Если так, — то это очень плохо. Потому что это расходы памяти. А что если я хочу кешировать на ином уровне, кешировать сразу фрагмент HTML с кучей запросов? При этом возникает вопрос относительно инвалидации кеша. Например, у меня регулярно обновляется какой-то атрибут модели, который не должен инвалидировать кеш.

        Кроме того, — это было бы просто не справедливо. У Джанги тоже есть приложения для кеширования запросов в БД, которые работают на уровне ОРМ. И если все дело в кеше, — то следовало бы обеспечить равные условия.

        К тому же, этот примитивный кеш может оказаться бесполезным на реально больших объемах данных. Где он будет больше потреблять ресурсы, чем экономить их, из-за низкого коэффициента повторных запросов.

        Возникает, следовательно, вопрос, относительно многопоточности и мультипроцессинга. Можно ли кеш сделать общим для всех потоков (memcache и пр.)? Как обстоит дело с инвалидацией? Как обстоит дело с параллельным доступом? Не перезапишется ли кеш старыми данными после инвалидации и до коммита?

        Что делать если мне кеш на этом уровне вообще не нужен? Если у меня кеширование решено на ином уровне (скажем, кеш БД меня устраивает, или кешируются целые фрагменты HTML страниц)?

        В общем, я обозначил далеко не полный список вопросов. Статья больше похожа на рекламу, нежели на стремление помочь разработчикам выбирать качественные инструменты.

        Бегло глянул исходники этого пони. Скажем так, исходники SQLAlchemy учат хорошо кодировать. Навигация в коде простая. Все понятно, слои абстракции выбраны качественно. Может, я конечно, плохо разглядел это пони, но первые впечатления были не ахти.
    • 0
      > Как поменяются результаты если делать случайные выборки имен пользователей, а не одного пользователя как сейчас.

      — Совершенно справедливый вопрос, исключающий влияние возможного внутреннего кеширования.

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