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

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

Предыдущий пост посвященный производительности, описывал 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

Источник

Поделиться

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