Django ORM, gevent и грабли в зелени

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

Gevent тоже выбирают из-за того, что он простой, очень шустрый и не несёт за собой callback hell.

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


Django ORM и пул соединений БД



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

Возьмём простой пример, который имитирует бурную деятельность с HTTP-запросами. Пусть это будет сервис для сокращения ссылок:

# testproject/__init__.py
__import__('gevent.monkey').monkey.patch_all()


# testproject/main/models.py
from django.db import models

class LinkModel(models.Model):
    url = models.URLField(max_length=256, db_index=True, unique=True)


# testproject/main/views.py
import urllib2, httplib
from django.core.urlresolvers import reverse
from django.http import HttpResponse, HttpResponseRedirect
from .models import LinkModel

def check_url(url):
    request = urllib2.Request(url)
    request.get_method = lambda: 'HEAD'
    try:
        response = urllib2.urlopen(request)
    except (urllib2.URLError, httplib.HTTPException):
        return False
    response.close()
    return True

def remember(request):
    url = request.GET['url']
    try:
        link = LinkModel.objects.get(url=url)
    except LinkModel.DoesNotExist:
        if not check_url(url):
            return HttpResponse('Oops :(')
        link = LinkModel.objects.create(url=url)
    return HttpResponse('http://localhost:8000' + reverse(
            go_to, args=(str(link.id).encode('base64').strip(), )))

def go_to(request, code):
    obj = LinkModel.objects.get(id=code.decode('base64'))
    return HttpResponseRedirect(obj.url)



Без gevent этот код работал бы невероятно медленно и с трудом обслуживал бы два-три одновременных запроса, но с gevent всё летает.

Запускаем наш проект через uwsgi (который де-факто стал стандартом при деплое Python-сайтов:

uwsgi --http-socket 0.0.0.0:8000 --gevent 1000 -M -p 2 -w testproject.wsgi


Пробуем тестировать десять запросов на сокращение ссылки одновременно и радуемся: все запросы отрабатывают без ошибок за минимальное время.

Запускаем наш новый сервис, сидим и смотрим на его успешное развитие. Нагрузка растёт с 10 до 75 одновременных запросов, и ему такая нагрузка нипочём.

Вдруг в одну из ночей на почту приходит несколько тысяч писем с таким содержанием:

Traceback:
   ...
   > link = LinkModel.objects.get(url=url)
OperationalError: FATAL:  remaining connection slots are reserved for non-replication superuser connections


И это хорошо, если вы поставили локаль en_US.UTF-8 в postgresql.conf, потому что если вы использовали конфигурацию Ubuntu/Debian по умолчанию, то вы получите тысячу писем с сообщением вида:

OperationalError: ?????:  ?????????? ????? ??????????? ??????????????? ??? ??????????? ????????????????? (?? ??? ??????????)


Приложение создавало слишком много соединений с базой данных (по умолчанию — максимум 100 соединений), за что и было наказано.

Вот и самый первый подводный камень: в Django нет пула соединений с базой данных, потому что в синхронном коде он попросту не нужен. Один синхронный процесс Django не может обрабатывать запросы параллельно, он в любой момент времени обслуживает только один запрос, и поэтому у него не возникает надобности в создании более чем одного соединения с базой данных.
На самом деле...
На самом деле Django может работать в многопоточном режиме, в котором один процесс сможет обрабатывать несколько запросов. Именно такой сервер запускает команда manage.py runserver, при этом в документации сказано, что этот режим совершенно не подходит для боевого применения.


Решение одно: нам срочно нужен пул соединений с БД.

Реализаций пула для Django сравнительно немного, например django-db-pool и django-psycopg2-pool. Первый пул основан на psycopg2.TreadedConnectionPool, который бросает исключение при попытке взять соединение из пустого пула. Приложение будет вести себя так же, как и раньше, но при этом другие приложения смогут создать соединение с БД. Второй пул основан на gevent.Queue: при попытке взять соединение из пустого пула гринлет будет заблокирован до тех пор, пока другой гринлет не положит соединение в пул.
Скорее всего вы выберете второе решение как более логичное.

Запросы к БД внутри гринлетов


Мы уже пропатчили приложение с помощью gevent и нам мало синхронных вызовов, так почему бы не выжать максимум из гринлетов? Мы можем параллельно делать несколько HTTP-запросов или создавать подпроцессы. Возможно, нам захочется использовать БД в гринлетах:

def some_view(request):
    greenlets = [gevent.spawn(handler, i) for i in xrange(5)]
    gevent.joinall(greenlets)
    return HttpResponse("Done")


def handler(number):
    obj = MyModel.objects.get(id=number)
    obj.response = send_http_request_somewhere(obj.request)
    obj.save(update_fields=['response'])



Прошло несколько часов, и вдруг наше приложение полностью перестало работать: на любой запрос получаем 504 Gateway Timeout. Что на этот раз случилось? За разъяснением придётся почитать немного кода Django.

Все соединения хранит в себе django.db.connections, который является экземпляром класса django.db.utils.ConnectionHandler. Когда ORM готов сделать запрос, он запрашивает соединение с БД, вызвав connections['default']. ConnectionHandler.__getattr__ в свою очередь проверяет наличие соединения в ConnectionHandler._connections, и если там пусто, то он создаёт новое соединение.

Все открытые соединения обязательно нужно закрыть после использования. Этим занимается сигнал request_finished, который запускается в django.http.HttpResponseBase.close. Django закрывает соединения с БД в самый последний момент, когда к ним уже точно никто не обратится, что вполне логично.

Вся загвоздка именно в том, как ConnectionHandler хранит соединения с БД. Для этого он использует threading.local, который после манкипатчинга превращается в gevent.local.local. Будучи объявленным однажды, эта структура данных работает так, будто она уникальна в каждом гринлете. Контроллер some_view начал обрабатываться в одном гринлете, и в ConnectionHandler._connections уже есть соединение с БД. Мы создали несколько новых гринлетов, в котором ConnectionHandlers._connections оказалась пустой, и для этих гринлетов были взяты ещё соединения из пула. После того, как наши новые гринлеты пропали, пропало и содержимое их local(), соединения с БД безвозвратно утеряны и никто не вернёт их обратно в пул. Со временем пул опустошается полностью.

При разработке на Django+gevent всегда нужно помнить этот нюанс и в конце каждого гринлета закрывать соединения с БД, вызвав django.db.close_connection. Вызывать его нужно в том числе и при возникновении исключительной ситуации, для чего можно использовать небольшой декоратор-contextmanager.

Пример такого декоратора
class autoclose(object):
    def __init__(self, f=None):
        self.f = f

    def __call__(self, *args, **kwargs):
        with self:
            return self.f(*args, **kwargs)

    def __enter__(self):
        pass

    def __exit__(self, exc_type, exc_info, tb):
        from django.db import close_connection
        close_connection()
        return exc_type is None



Использовать эту обёртку нужно разумно: закрывать все соединения перед каждым переключением гринлетов (например перед urllib2.urlopen), а также следить, чтобы соединения не закрывались внутри незавершённой транзакции или цикла по итератору наподобие Model.objects.all().

Используем Django ORM отдельно от Django


Мы можем постичь такие же проблемы, если мы создадим аналог cron или Celery, который будет время от времени делать запросы к БД. То же самое нас ждёт если поднять Django с помощью gevent.WSGIServer и параллельно поднять какие-либо сервисы с другим протоколом, которые будут использовать Django ORM. Главное вовремя возвращать соединения в пул БД, тогда приложение будет работать стабильно и приносить вам радости.

Выводы


В этом посте было сказано об элементарных правилах, что нужно обязательно использовать пул соединений БД и что нужно возвращать соединения обратно в пул сразу после использования. Вы бы точно учитывали это, если бы использовали только gevent и psycopg2. Но Django ORM оперирует настолько высокоуровневыми абстракциями, что разработчик не имеет дела с соединениями БД, и со временем эти правила могут быть забыты и открыты заново.
Метки:
Поделиться публикацией
Похожие публикации
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама
Комментарии 76
  • 0
    Спасибо за статью — ушла в избранное, как ни как HL проекты на Django держу, и все может помочь.
    Есть одна статейка интересная, с ссылкой на патч, тоже довольно интересный
    Если я все правильно понял, то в 1.6 этот патч уже должен быть в боевом строю… поправьте, если не прав.
    • 0
      Судя по этому, данный патч не решает проблему. Просто раньше соединение с БД закрывалось после каждого запроса, а теперь оно будет жить 10 минут. Работать будет быстрее, т.к. не будет устанавливаться новое TCP-соединение с БД на каждый запрос, но если пропатчить всё это дело гринлетами, то соединения так же будут плодиться без ограничений.
      • 0
        да я в курсе, лишь поделился интересной статейкой:)
    • 0
      Когда обсуждали отсутствие встроенного пула у Django, пришли к выводу, что проще использовать локальный pgbouncer. В итоге так и сделали.

      Второй момент — я уверен, что даже под gevent psycopg2 в DjangoORM будет использовать блокирующий режим, т.е. если один из запросов в одном из подключений затормозит, то и весь Python процесс и все гринлеты будут его ждать.
    • –5
      Юзать ORM на HL это всеравно что выстрелить себе в ногу с базуки.
      • +2
        Почему?
        • 0
          пара аргументов:
          собирать программный запрос в sql
          нужно время на создание объектов по результатам выборок из базы, если конечно не использовать .values()

          вообще это очень скользкая тема…
          в крупных HL проектах там скорее уже свои собственные ORM

          • 0
            собирать программный запрос в sql
            нужно время на создание объектов по результатам выборок из базы, если конечно не использовать .values()

            Действительно так, это несколько уменьшает производительность, зато как сильно это экономит время разработчика.
            Если гнаться за сферическим HL в вакууме, то от джанги вообще стоит отказаться. Там не менее трудоёмкий django.core.urlresolvers, куча лишних middleware и достаточно медленный по сравнению с конкурентами шаблонизатор.
            • +1
              В HL экономят не время разработчика а ресурсы. Тем более я понятия октровенно не имею где вы так много экономите времени разработчика при написании запросов через ORM.
              • 0
                Следуя вашей логике, в HL всё должно быть написано на C.
                • +2
                  С это язык а не серебренная пуля, и считать что на нем все круче работает это ошибка. Сравните например С и Erlang по работе с сетью и вы удивитесь тому как С проигрывает(разумеется если вы не пишите на нем второй Erlang со шлюхами и блекджеком).

                  Я лишь говорю о том что в HL выбор технологий исходит не из того что нашней программистам, а из того что разгружает систему. И если к примеру переписание какогото модуля на Си реально решит проблему, то так и стоит сделать.
                  Важно понимать затраты своего приложения при оценке. Если вы можете потратить 3к уе и переписать код на Си который секономит вам 5 серваков, то это хороший вариант и очень дешевый, если же написание занимает 100к уе, в то время как нада купить 20 серваков, то это плохой вариант. Это конечно обобщенно все.

                  ORM же плюха чисто для программеров и не более. Причем в реальных, сложных проектах она только мешает. Попытка обьединеть бизнес с кодом ошибочна в своем понимании. Ибо это всеравно что сказать мы возьмем куб и перенесем его в 5 мерное пространство где он будет выгляди абсолютно так же. По факту перевод данных из области в области, это отображение, причем не всегда биективное.
          • +3
            1) куча левых сущностей.
            2) не прозрачность для разработчика. Тоесть я понятия не имею(без просмотра всего кода ОRМ) как и почему оно строит запросы. А зачастую что бы получить то что надо приходится танцевать с бубном.
            3) Писать оптимизированные запросы куда проще на чистом SQL чем на ОRМ.
            4) Скорость выполнения.
            5) Работа ORM меняется от версии к версии, SQL код работает одинаково всегда(изменение БД во внимание не берем).
            6) ORM очень тяжко работают с мультипроцессорностью и асинхронностью, куча багов, и странных манипуляций.
            7) KISS
            8) Даже и не пытайтесь делать миграцию на рабочем проекте с помощью ORM.

            ORM — это обобщенная фиговина, HL работает с заточенными вещами. Если вы говорите да пофиг купим еще 100 серваков то это нифига не HL.
            HL это когда вам говорят вот у нас есть старенький целерон и он должен тянуть 1к юзеро онлайн. А когда вам такое скажут, то вы начинаете не то что ORM выкидывать, а большую часть функционала языка.
            Для примера раскажу древнюю историю с моей молодости. Работал я в банке и крутили мы на движе Zend Framework код для сотрудников. Возникла ситуация сделать экспорт в CSV файла на 50мб. Я даже не стад ничего писать а взял и сдела экспорт через клас фрейма(который сертифицирован под ентерпрайз). Каково же было мое удивление когда при генирации файла скрипт выжрал всю оперативу на серваке(8ГБ). Как оказалось чудо модуль на каждую запятую генерил по обьекту и в итоге кол-во обьектов своим весом просто вешало сервак.
            Переписав без этой чудо абстракции код, буквально на 5 строчек, получил мего выиграш произвоодитльности.
            Этот маленький пример показывает разницу между контроируемым кодом и неконтролируемым. Любая абстракция это снижение контроля, а снижение контроля ведет к проблемам, особенно в HL.
            • 0
              Все верно. Все обоснованно.
              По скорости — в ОРМ есть косяк в т ом, непонятно сколько времени он затратит на составление запроса.
              И так же есть примеры. В моем случае интересен пример обновления списка товаров с актуальными ценами и прочими подвязками из 1С на сайт.
              Я опущу как было очень рано, когда мне только достался сайт, написанный тем более на РоРе, людьми которые, возможно, только с мышкой дружили… Но из моей практики было так:
              — Обновление около 12 тыс товаров (читать как всего различных записей, описывающих товары) шло 10 минут (данные брались из сформированного файла 1С, указано время работы скрипта)
              — Сейчас обновление занимает 1 минуту и 20-40 сек. Однако товаров (читать как и в предыдущем пункте) около 200 тыс

              Достигнуто все это «игрой» с ОРМ. И да, нет у нас 100 серваков, и оптимизировать свой собственный код мы любим:)
              • 0
                Странное вы сравнение приводите. Вы пришли когда код писали юзеры(тоесть что бы они не написали это Г), а потом вы написали код н ОРМ и все ок. Разумеется будет ок, вы ведь программист, и это заслуга не ОРМ а того что вы пишите норм код. НО, если бы вы написали код вне ОРМ думаю было бы быстрее.
                • 0
                  То ли я писать комментарии не умею, то ли читают их через строку.
                  Попробую еще раз:
                  1. Тот скрипт, который был написан гуру владения мышкой, написали на Ruby on Rails обновление… там было 2000 товаров, обновление шло от 40 минут, и в большенстве случаев заканчивалось неудачей, ну и сайт лежал все это время
                  2. Когда и сайт и скрипты обновления были переписаны на Python/Django все было лучше… количество товаров увеличивалось и достигли значения в 12 тыс… Обновление шло 10 минут… ЭТО МОЙ КОД!!! МОЙ!!! КАК ПРОГРАММИСТА:) (сорри за шифт)
                  3. Набравшись опыта и понимая что у нас кол-во товаров резко возрастет в ближайший месяц, переписал код, использовал уже известные приемы ОРМ (все те же 5-10 приемов, но они отличные от того что было в пункте 2) и смог при увеличенном количестве товаров выдерживать небольшое время обновления.

                  теперь надеюсь понятна аналогия? что я сам свой же код и сравниваю…
                  • 0
                    И каким чудом код на ОРМ в разы победил обычный код?
                    • 0
                      я сдаюсь :)

                      и тот и тот код был с ОРМ (я говорю про свои кода)
                      а тут не чудо… тут техника… с выбором полей, юзаньем транзакция, проверками наличия данных, предподготовки данных… итд итп:)
                      • 0
                        Вы же сами писали.
                        Достигнуто все это «игрой» с ОРМ.

                        Ключевое слово «игрой». Достигнуть высокой производительности с ОРМ можно. Но для этого нужно опустится на уровень ниже + делать много не очень хороших хаков… «играть» с фреймворком.
                        • 0
                          ну игра — было ОРМ «плохое», стало ОРМ заточенное :) Чем не игра:)
                          «низкоуровнего» скуль нет не было и не планирую использовать. (select from...)

                          хаков — ноль:) совершенно нет, чистая технология и возможности фреймворка…

                          Если говорить про слабые места — то их сейчас у меня 2:
                          — Т.к. django 1.3.4 юзаю — то проблема с подключениями постоянными
                          — Темплейт процессор у джанги хиленький… нужно менять :) Тесты можно легко найти в сети.

                          Это если не трогать базу:) (в плане сидеть на реляции)
                          • 0
                            Низкоуровневый это общение с базой минуя SQL парсер, а SQL это не низкоуровневый доступ))

                            А чем отличается плохое ОРМ от хорошего?
                            И что такое заточенное ОРМ? это типа вызов метода в котором внутри уже прописан правельный SQL запрос?
                            • +2
                              прям вынуждаете статью написать:)

                              пойду простым методом — если эта запись наберет 50 плюсов — напишу статью о том какие приемы есть в программировании с ORM в HL проекте.
                              • +1
                                А давайте не будем «выманивать» плюсики за статью.
                                Напишите, ее прочитают и оценят. Что может быть проще?
                                • +1
                                  ок, пишите вопросы, интересные Вам для чтения на тематику Django + HL
                                  поделюсь трехлетним опытом использования данной «связки»

                                  Уже, как понял, из обязательного:
                                  — Приемы работы с БД посредством Django-ORM

                                  что еще интересно?

                                  до вторника собираю вопросы, можно в личку, в чт-пт выкладываю статью
                                  • 0
                                    Раз уж пошла такая пляска, то напишите, когда стоит использовать ORM, а когда не стоит.
                                  • +2
                                    Интересно, почему именно Django. Какие преимущества дает этот фреймворк (и его ОРМ) по сравнению с другими фреймворками / технологиями.
                                    • +1
                                      Как на джанговской ОРМ написать такой запрос? ;-)

                                      «SELECT COUNT(`user_ptr_id`) as cnt, `user_ptr_id`, `p_second_name`, `p_name`, `p_third_name`, `p_phone_code`, `p_phone_numb`
                                      FROM `personal_person`
                                      GROUP BY `p_name`, `p_second_name`, `p_third_name`, `p_phone_code`, `p_phone_numb`
                                      HAVING COUNT(`user_ptr_id`) > 1
                                      ORDER BY cnt DESC»

                                      Есть клоны профилей юзеров, зарегистрированные в разное время с совпадениями по Ф.И.О. и Телефону. Нужно вывести список всех юзеров сгруппировав по ФИОТ, показав кол-во дублей у каждого совпадения и не выводя юзеров у которых нету дублей.
                                      Сейчас всё реализовано через такой, прямой запрос в БД :)
                                      • +1
                                        Написать можно, вопрос во что это превратится. Проблема в том что я могу взять ОРМ и составить логически такой запрос как у вас выше, но я понятия не буду иметь в какой запрос SQL он в реальности трансформируется, а без этого я не могу говорить о его эффективности, что не допустимо в сложных проектах. А если лезть в дебри ОРМ изучать принцип построения то вопрос тогда зачем его использовать.
                                        Мой лично принцип говорит если что то можно сделать без библиотеки не сильно перерабатывая, то нада делать без нее. Ибо понимание того как работает твой код крайне важно при нагрузках. А когда 90% кода сторонние библиотеки которые никто не знает как работаю то считай проект на самотеке и зависит не от разработчиков, а от разработчиков сторонних библиотек. Для тех людей кто умеет считать риски думаю понятно куда заводят такие вещи.

                                        Вообщем метод KISS еще ни разу меня не подводил.
                                        • –1
                                          Вассерман, перелогиньтесь
                                          • 0
                                            Решил воспользоваться случаем, вдруг кто подскажет ответ на то, что я сам не смог найти решение. А ты меня сразу Вессерманишь :-(
                                            • 0
                                              я попробую ответить на твой вопрос.
                                          • 0
                                            Вам обязательно выбирать user_ptr_id? Если да, то тут с джангой могут быть проблемы. Возможно это получится обойти, играясь с queryset.query.group_by. Но кажеться что у Джанги есть защита по поводу того, чтобы поля в select'е и в group by совпадали. Если можно обойтись без id, то:

                                            qs = User.objects.values('username', 'email').annotate(cnt=Count('id')).filter(cnt__gt=1).order_by('-cnt')
                                            print(qs.query)
                                            # SELECT "auth_user"."username", "auth_user"."email", COUNT("auth_user"."id") AS "cnt" FROM "auth_user" GROUP BY "auth_user"."username", "auth_user"."email" HAVING COUNT("auth_user"."id") > 1  ORDER BY "cnt" DESC
                                            

                                            Список полей думаю не имеет значения

                                            Если же нужен хотя бы один id из группы, например мы хотим удалить дубликаты, но оставить первый, можно применить такой хак, хотя для HL такое возможно не подойдет

                                            qs = User.objects.values('username', 'email').annotate(cnt=Count('id'), first_id=Min('id')).filter(cnt__gt=1).order_by('-cnt')
                                            print(qs.query)
                                            # SELECT "auth_user"."username", "auth_user"."email", COUNT("auth_user"."id") AS "cnt", MIN("auth_user"."id") AS "first_id" FROM "auth_user" GROUP BY "auth_user"."username", "auth_user"."email" HAVING COUNT("auth_user"."id") > 1  ORDER BY "cnt" DESC
                                            
                                            • 0
                                              Заметьте, в приведенном выше запросе нету еще JOIN'ов и подзапросов, с ними будет повеселее. Не важно, можно ли выразить средствами ОРМ любой запрос. Дело в дополнительном уровне сложности. Приведенный пример отлично показывает, что эта сложность не нужна и даже вредна, она нам только мешает.

                                              Простой и лаконичный запрос мы переписываем на ОРМ, а про себя проговариваем «хм, а может без этого поля… или вот такой хак». А потом смотрим какой же запрос сгенерировала эта ОРМ, вручную проверяем его. Главный вопрос, а зачем нам тогда ОРМ, если все равно мы работаем по факту с SQL?
                                              • 0
                                                Вам может и сложнее, а для многих програмистов запись через ORM проще, чем через RAW SQL. К тому же, ORM кроме генерации SQL дает еще и удобный способ обработки результатов.

                                                Я не отрицаю, есть задачи когда raw SQL будет выполняться значительно быстрее, чем запрос через ORM. Есть сложные запросы, которые описать через ORM невозможно, или возможно, но запись будет громоздкой и менее читабельной, чем SQL. И для django'вой ORM таких запросов больше, чем скажем для SQL Alchemy.

                                                Но если нам хватает возможности/производительности ORM, то я не вижу причин ее не использовать

                                                Простой и лаконичный запрос мы переписываем на ОРМ, а про себя проговариваем «хм, а может без этого поля… или вот такой хак». А потом смотрим какой же запрос сгенерировала эта ОРМ, вручную проверяем его.

                                                Ну так это издержки задачи — сделать запрос к ORM, чтобы он сгенерировал заданный SQL.
                                                • 0
                                                  Что это за программист такой которому сложно запрос на SQL написать?
                                                  • 0
                                                    Я говорил не «сложно/просто», а «сложнее/проще». И повторюсь — ORM не только генерирует SQL.

                                                    Примерно с таким же успехом можно сказать "что это за программист такой, которому сложно разобраться с ORM"

                                                    Это 2 разных инструмента, чтобы выбирать данные из базы. У каждого есть плюсы и минусы.
                                                • 0
                                                  Не, ну если так рассуждать. То с помощью ОРМ сложно создавать и работать с триггерами, функциями, процедурами и много чем ещё, что позволяет SQL. Зато есть и несколько плюсов. К примеру портирования на разные БД поддерживаемые джангой. Ну или из запредельного, не обязательность глубоких знаний скуль запросов разработчиком. Достаточно ограничится знаниями ОРМ.
                                                  Плюс, джанговские связи между таблицами. ОРМ запрос на связи проще и красивее писать, чем городить прямой запрос с кучей джоинов. Так же и про вложенные запросы.
                                                  Везде есть своя грань, которую нужно понимать и применять инструменты там где это действительно нужно.
                                                • 0
                                                  Я вот придерживаюсь Вашего мнения… нафига в HL продакт такое? Касаемо standalone скриптов — вполне возможно, касаемо веб-решений — в топку
                                                  • 0
                                                    Да, можно и без user_ptr_id. Мне нужна была цифра, визуально отображаемая, сколько дублей есть. Так как делалось прямым запросом в БД, проблем выбора поля для COUNT() не ставилось. Можно смело брать другое поле из тех что есть в GROUP BY. Правда не помню, позволит ли джанговская ОРМ взять его.
                                                    • 0
                                                      Портировать на другую базу простые SELECT не проблема. Проблемы начинаются когда мы хотим использовать хранимые процедуры, функции, триггеры и т.п. Но ОРМ ведь их и не поддерживают.
                                                      А глубоко знать SQL нужно. При этом выучить синтаксис ерунда, важно знать и понимать принципы выполнения запросов. Мы ведь о HL говорим, так ведь? :)
                                                      • 0
                                                        Зато 98% всех обращений к БД делать проще средствами ОРМ джанги. И только под оставшиеся 2% приходится лезть в глубину SQL`я. При этом как говорилось выше, средствами джанги удобно в последствии и обрабатывать полученные результаты. Говорю по личному, субъективному опыту.
                                                        Просто не стоит городить велосипед нагромождая кучу прямых запросов там где это не нужно. А только «узкие» и «скользкие» места.

                                                        P.S. Есть отдельная специализация «программист баз данных». Тут тоже нужно знать грань, когда питон программист бакэнда и фронтенда, лезет в дебри базы данных. При действительно большом HL проекте, стоит задумываться о вынесении обязанностей по работе с БД на отдельного специалиста. А то можно на программиста и дизайн с контентом по вешать, рассуждая что должен знать и уметь всё.
                                                        • 0
                                                          Вы сами себе противоречите. То на HL проектах нужно использовать ОРМ, то нужно отдельного специалиста заводить. Вы действительно думаете, что такой специалист будет возиться со сгенерированной схемой, со всякими appname_modelname? И что он не захочет выносить часть логики на триггеры/хранимые процедуры. Что тогда, половину проекта делать через ОРМ, половину через SQL?
                                                          • 0
                                                            Я вам открою секрет как человек переносивший с MySQL на PostgreSQL проекты. Они может визуально и похожи, но в реальности очень разные. И то что работает на мускуле не всегда работает на Postgre, а то что есть на Postgre нету на мускуле. Вот к примеру на мускуле нет геоиндекса, как его будете переносить? — А никак, ибо невозможно. Таких нюансов сотни. Да конечно бложик перенесете без проблем, а вот HL проект с вылизанными запросами под минимизацию нагрузки никогда в жизни. Посему забудте о портировании.
                                                            Во вторых обобщенное никогда не работает лучше специализированного.

                                                            Программист баз данных есть только в Ораклах, ибо там реально програмят под БД. Остальные программят в коде а БД оптимизируют под скорость.
                                                            • 0
                                                              Вот к примеру в мускуле нет геоиндекса, и ОРМ его не поддерживает, ведь должна одинаково хорошо работать под любой базой. И как вы будете писать такое под ОРМ? — А никак. Конечно бложик вы напишете на ОРМ без проблем, но вот создать проект HL проект, где придется вылизывать запросы под минимизацию нагрузки… Да никогда в жизни.

                                                              И да, обобщенное никогда не работает лучше специализированного. И использовать ОРМ для «обобщения» возможностей баз данных не самая лучшая идея.

                                                              Я не говорил что SQL благо, и его легко портировать. Легко портировать то подмножество, что используется в ОРМ (get by id и т.п.).
                                                              • +1
                                                                anjensan, ответ был не вам а syschel, просто вложенность закончилась)

                                                                SQL благо ибо не требует изучения лишней фиговины. Это можно сравнить с шаблонизаторами для пыхи к примеру. Вот зачем человеку учить Smarty что бы писать шаблоны, не лучше ли сразу выучить пыху? Нативные шаблонизаторы работают куда быстрее, проще, больше функционала.
                                                                SQL тоже нада портировать, но логичнее портировать SQL делая так что софт юзает максимум возможностей системы, нежели портировать с ОРМ урезать себя в возможностях и как следствие в скорости работы и качеству софта.
                                                                Потому как к примеру есть вообще другие БД, ннапример Mongo, Couchbase, как на них портировать? А если нельзя то в чем прок? Под одну БД портируем ОРМ а под другую пишем нативные запросы? — это бредово.

                                                                Вообщем за всю свою жизнь я не увидел плюсов в ОРМ, разве что для строительства бложков с нагрузкой в 100 уников/сутки
                                                                • 0
                                                                  andreynikishaev, приношу извенения. Заметил, что мы продвигаем по сути одну идею, уже после того, как комментарий был написан :)
                                            • 0
                                              Статья в работе. Не обещаю мегаобъемы, но все же суть будет
                                              Если не появится за завтра — ждите ближе к ночеру в сб по времени Мск
                      • 0
                        необоснованное суждение
                        без думы юзать — согласен — выстрел получится предельно точным
                        если с умом подходить — получается вполне эффективный код… А буквально то нужно держать в голове 5-10 правил юзанья ОРМ и не лениться их пользовать.
                        на джанге с ОРМ работает у меня проект, который под НГ выдерживал нагрузку под 500к просмотров ежедневно. Вполне успешно, на среднем сервере. Пришел правда к этому постепенно, в течении года, но все же это реально.
                        • 0
                          Это же 5-10 запросов в секунду? Вроде немного совсем.
                          • 0
                            5-10 правил юзанья (построения) ОРМ, а не 5-10 запросов в секунду… внимательней
                            • 0
                              Вероятно, имелось в виду 500к запросов каждый день. Усредненно выходит как раз 5-6 запросов (HTTP) в секунду.
                              • 0
                                возможно… только я говорю про 500к просмотров сайта:) каждый просмотр — в среднем (т.к. инет магазин с кучей всего) от 17 до 32 запросов на страницу… + учитывайте что они не равномерно распределены в сутках + как ни как уже оптимизировано.
                                • 0
                                  32 запроса на страницу? о_О 1000 онлайн и 32к запросов что по сути около 150-320к опсов(на примере GAE). Под таким натиском любой сервак ляжет. У мну юзеров 8-10к и генерят они только 4-7к опсов. И это не сайт а игра.
                                  • 0
                                    Согласен что много. Работаю над уменьшением периодически.
                                    Сейчас, если посмотреть статистику, время генерации страницы по сайту в среднем от 0,03 сек до 1,08 — этот диапазон в основном. Бывают проскакивают записи когда выхожу за рамку в 1,5 сек по генерации страницы, над этими страницами я и работаю по оптимизации в первую очередь. Местами удается уложиться в 8мь запросов. И это не может не радовать.
                                    Сейчас на одном из разделов тестирую работу алгоритмов отличной от ОРМ связки. Верней там ОРМ конечно присутствует, но все же алгоритм работы другой.
                                    Работает зашибись, скорости генерации страниц высоки, но пока есть ошибки в стабильности работы… все должно как часы там работать, а иногда появляются сбои. Как решу данную проблему, буду переписывать остальные части сайта и, наверное, начну гнобить чистый ОРМ :)
                                    • 0
                                      используйте многоуровневый кеш и подумайте над денормализацией БД. Я сейас стараюсь перевести весь софт к работе с локальным кешем вместо БД. Тоесть в разрезе сессии человек не обращается к бд а работает с локальным кешем, потом он сносится в базу. Это существенно снижает нагрузку на БД и ускоряет работу. Правда к сайту это не подойдет.

                                      Для сайтов нужно делать систему виджетов со своей локальной обработкой и подгрузкой асинхронно. Тоесть контент генерится странице все осталньое сторонние виджеты. Это позволяет делать качественное кеширование и не задормаживать загрузку основного контента.
                                      • 0
                                        есть кеши, есть есть очень много решений с использованием нерелятивных баз, все работает все как часы, постепенно, постепенно выведу код чтобы деражть минимум 1кк запросов в день…
                                        • 0
                                          я обычно в проектах юзал twisted, tornado(дописанный) потом перешел на gevent. Правда скрещивать это с синхронным фреймом не очень гуд, появляется много нюансов. Куда проще написать свой микрофрейм и юзать его.
                          • 0
                            Вообще одними общими правилами юзания ОРМ дело не ограничивается. Нам надо знать структуру реляционной базы. Какие есть индексы, их типы, списки полей, включенные в индексы. В общем случае нужно знать, какие join порождает ОРМ, в противном случае можно натолкнуться на грабли.

                            Нет, если мы используем ОРМ в стиле «дай мне сущность по id» или «отфильтруй мне сущности по такому полю, я знаю что есть индекс». Но тут возникает вопрос нужна ли нам ОРМ.

                            ОРМ — еще один уровень абстракции. HL проекты вынуждают нас спускаться на более низкий уровень. Даже если мы и не пишем голые SQL запросы, то все равно учитываем реляционную структуру данных (а не объектную), фактически работаем на уровне базы.
                            • 0
                              я именно про такое и говорю… и в общем реально в 5-10 правил можно уложить все эти ньюансы. ОРМ, без голого СКУЛЬ, в рамках джанго ферймворка.
                        • 0
                          троллейбус из буханки хлеба.jpg
                          • 0
                            лётчик.jpg
                            • +2
                              в случаях когда нужна асинхронность — лучше изучить и применить вменяемый инструмент заточенный под эту задачу twisted/tornado, node.js, erlang в конце концов. вместо использования говнофреймворка где нипопадя.
                              • 0
                                1. twisted/tornado? Что вы скажете на это?
                                2. node.js ещё сырой, плюс это javascript, очень специфический язык, который зачастую в холиварах бросают в одну кучу с PHP.
                                3. erlang — вещь хорошая, но, к сожалению, непопулярная. И как ни странно, на эрланге тоже есть фреймворки, их нельзя использовать?

                                Почему не упомянули про Go, Distributed Haskell, eventmachine, Stackless Python, boost::asio, libevent? Это тоже мощные инструменты для асинхронного программирования.
                                • +3
                                  Несерьезно говорить про сырость node.js после топика про манкипатчинг половины фреймворка и стандартной библиотеки.
                                  • 0
                                    привел для примера. инструментов много.
                                    www.techempower.com/blog/2013/03/28/framework-benchmarks/

                                    по вашей инфе gevent быстрее twisted/tornado ок. упустим что это 2010 года информация.
                                    но логика просто «отличная» — прикрутить монстра django к gevent и якобы будет та же призводительность.
                                    • 0
                                      >И как ни странно, на эрланге тоже есть фреймворки, их нельзя использовать?
                                      А зачем они нужны? Когда решаешь какие то специфичные задачи — как правило надобность во фреймворках отпадает.
                                      А писать полностью сайты (т.е. использование классического MVC — взаимодействие с выводом, шаблоны, БД) на ерланг, nodejs и т.п. — это бред я считаю.
                              • НЛО прилетело и опубликовало эту надпись здесь
                                • НЛО прилетело и опубликовало эту надпись здесь
                                  • +1
                                    Смысл статьи — как городить костыли, когда грабли уже расставлены.
                                    • НЛО прилетело и опубликовало эту надпись здесь
                                • 0
                                  Начиная с версии 1.9, uwsgi сам умеет патчить, надо только добавить при запуске опцию –gevent-monkey-patch
                                  Ну и если запускать через nginx(как это делаю я), надо прописать в конфиге nginx: uwsgi_buffering off;

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