About Blog Dev | Alfa Romeo SZ Conkeror wishlist

All articles, tagged with “dbqueries”

Пользователь и его профиль

Известная штука, что у Django есть статическая (неизменяемая официально поощряемыми путями) модель User и костыль для дополнительных полей (которые может каким-либо образом использовать приложение) в виде настройки USER_PROFILE, указывающей на модельку-профиль. В результате использования такого костыля, если не делать дополнительных телодвижений, количество запросов возрастает (пример для данного блога, где каждому комментирующему ставится ссылка на его сайт) на число комментариев (даже не комментировавших, а комментариев!).

Для улучшения ситуации можно применить разные методы, в том числе и load_related, который использовал я. Недостаток заключается в том, что об этом деле надо всегда помнить и везде его применять - неоправданное усложнение кода, имхо.

Потому, после продолжительных колебаний и сомнений, я решил сделать всё радикальнее - удалить всю модель UserProfile, применив вместо неё monkey patching к стандартной модели:

User.add_to_class('site', models.URLField(verify_exists=False, blank=True))
User.add_to_class('email_new', models.EmailField(blank=True))

User._meta.admin.fields += (
    ('Byteflow Extensions', {'fields': ('site', 'email_new')}),
    )

Конечно, главная проблема здесь - это то, что способ совершенно не стандартный и вряд ли кто-то будет ожидать, что табличка auth_user будет меняться. Но такой способ настолько выгоднее и удобнее, что я решил наплевать на эти трудности. :-)

И ещё одно - спасибо Амиту, который и показал конкретно, как это сделать. ;-)

Связанные объекты

Сегодня наконец-то отбросил свою лень в сторону и сделал то, о чём так долго твердили большевики! :)

Перед всем этим текстом хочу сказать спасибо Диме Догадайло, который собственно всё это и сделал. Недавно он мне говорил, что хочет этот код выложить в опенсорс, но так как этого пока не произошло - я значит буду первый, благо он мне разрешил код использовать в блоге. :-)

В Джанге, как известно, встроен свой механизм аутентификации, который часто бывает довольно удобен (самое большое удобство заключается именно в его встроенности - интеграции со всем джанговским хозяйством), но имеет одну принципиальную проблему: модель пользователя самого практически нереально расширять.1 Пока из адекватных рабочих путей (т.е. даже если смотреть по сторонам, не обращая внимания на слова “гарантированный”, “официальный”, “документированный” ;) есть только создание отдельной модели - профиля (всем заинтересованным - читать пост Джеймса Беннетта, благо он хорошо описал2), но при этом сразу возникает другая проблема - профиль, живущий в отдельной модели, подтягивается к объекту юзеру в общем случае отдельным запросом.

В принципе, эту проблему должен решать select_related, но он же идёт только по прямым ссылкам, а не обратным - т.е. для профилей бы он вытягивал пользователей, но не наоборот. Проблема эта решаема, решать её можно по-разному - и я её решил несколько иначе, приспособив и без того полезную штуку.

Штука эта - несколько функций, которые позволяют загружать обратные связи за один запрос. Т.е. если мы получаем 5 постов в блог, а потом к ним комментарии - это выходит 2 запроса в базу, а не 6 (1 на посты и 5 на комментарии к каждому). В принципе, никакого rocket science’а нету, но я за время работы с джангой всегда изворачивался другим образом, если попадал на подобные грабли - не всегда это было лучше, но кое-как выходило. :-) И вот эти функции я использовал для подгрузки профилей к пользователям в отображении комментариев здесь, что уменьшило количество запросов на каждый пост в разы. :)

Я ещё успел натолкнуться на проблему, что рассчитано это было на что-то подобное “последним двадцати постам или картинкам” - уникальным объектам, и из-за этого кэш получал только первый объект из тех, кто его хотел. А в случае с комментариями такое не прокатывает никак, тут один и тот же человек комментирует несколько раз. :-) Это меня и заставило разобраться в коде и добавить поддержку неуникальных объектов.

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

P.S.После написания этого поста и игр с sup мне захотелось сделать сноски, подобные тем, что есть у Адама Гомаа. :-)

1 -

мне кажется, что с помощью переделки модели юзера в создающуюся динамически эту проблему можно решить.

2 -

хотя я использую AutoOneToOneField Ивана Сагалаева, но большой роли это не играет. :)

Кеширование ContentType в память

Сегодня обсуждали немного generic relation’ы из Джанги, и пришли к выводу, что неплохо было бы сами ContentType‘ы закешировать прямо в память - мы постоянно используем один и тот же набор из нескольких типов (модельки, которые можно тегать и ставить рейтинги), и кеш даёт гарантию того, что для них будет всего 1 запрос в базу за всё время жизни апачевского ребёнка.

Я даже написал в django-users, на что мне Малькольм указал на уже существующий кеш, который, однако, работает, только если у меня уже есть модель. А мне-то наоборот, надо её получить!

Вылилось в первом приближении всё это в 10 минут вечером (почему-то при обдумывании этого на работе мне в голову лезли всякие непристойности с кучей кода) и замену метода get у менеджера модели ContentType. Кеш вышел рабочим, но тупым - отдельным от существующего джанговского, хранящим результаты выборки в словаре с ключом только по названию модели.

Подкрепление ослабленного организма позволило понять, что ведь можно же и использовать джанговский внутренний кеш, хотя и придётся пробегать его в цикле ;) - мы обычно не используем название приложения при доставании типа.

Короче, много времени это не заняло. Встречайте, а вдруг кому-то пригодится? :)

Подумывал это оформить патчем в джангу, но как-то это всё выглядит не слишком чистым пока. Как минимум надо убрать get_without_app

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