Заметки для построения эффективных Django-ORM запросов в нагруженных проектах

    Написано, т.к. возник очередной холивар в комментариях на тему SQL vs ORM в High-Load Project (HL)

    Преамбула


    В заметке Вы сможете найти, местами, банальные вещи. Большая часть из них доступна в документации, но человек современный часто любит хватать все поверхностно. Да и у многих просто не было возможности опробовать себя в HL проектах.
    Читая статью, помните:
    • Никогда нельзя реализовать HL-проект на основе только одной манипуляции с ORM
    • Никогда не складывайте сложные вещи на плечи БД. Она нужна Вам чтобы хранить инфу, а не считать факториалы!
    • Если вы не можете реализовать интересующую Вас идею простыми средствами ORM — не используйте ORM для прямого решения задачи. И тем более не лезте в более низкий уровень, костыли сломаете. Найдите более элегантное решение.
    • Извините за издевательски-юмористический тон статьи. По другому скучно :)
    • Вся информация взята по мотивам Django версии 1.3.4
    • Будьте проще!

    И-и-и да, в статье будут показаны ошибки понимания ORM, с которыми я столкнулся за три с лишним года работы с Django.


    Не понятый ORM


    Начну с классической ошибки, которая меня преследовала довольно долго. В части верований в племя уругвайских мартышек. Я очень сильно верил во всемогучесть Django ORM, а именно в
    Klass.objects.all()
    

    например:
    all_result = Klass.objects.all()
    result_one = all_result.filter(condition_field=1)
    result_two = all_result.filter(condition_field=2)


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

    Вы, наверное, уже догадываетесь, что волшебных мартышек не существует и в данном случае мы имеем три запроса. Но, я Вас огорчу. В данном случае мы все же имеем два запроса, а если быть еще точнее — то ни одного запроса нет по результатам работы данного скрипта (но в дальнейшем мы конечно так не будем изголяться). Почему, спросите Вы?
    Объясняю по порядку. Докажем что в данном коде три запроса:
    • Первая строка, при вычислениях, аналог
      select * from table;
      

    • Вторая строка, при вычислениях, аналог
      select * from table where condition_field=1;
      

    • Третяя строка, при вычислениях, аналог
      select * from table where condition_field=2;
      


    Ура! Мы доказали что у нас есть три запроса. Но главное фраза — «при вычислениях». По сути, мы переходим ко второй части — доказательство что у нас всего два запроса.
    Для данной задачки нам поможет следующее понимание ORM (в 2х предложениях):
    • Пока мы ничего не вычислили — мы только формируем запрос, средствами ORM. Как только начали вычислять — вычисляем по полученному сформированному запросу.

    Итак, в первой строке мы обозначили переменную all_result с интересующим нас запросом — выбрать все.
    Во второй и третьей строке, мы уточняем наш запрос на выборку доп. условиями. Ну и следовательно получили 2 запроса. Что и следовало доказать
    Внимательные читатели (зачем вы еще раз взглянули в предыдущие абзацы?) уже должны были догадаться, что никаких запросов то мы и не сделали. А во второй и третьей строке мы так же просто сформировали интересующий нас запрос, но к базе так с ним и не обратились.
    Так что занимались мы ерундой. И вычисления начнутся, например, с первой строки нижестоящего кода:
    for result in result_one:
      print result.id
    


    Не всегда нужные функции и обоснованные выборки

    Попробуем поиграться с шаблонами, и любимой некоторыми функцией __unicode__().
    Вы знаете — классная функция! В любом месте, в любое время и при любых обстоятельствах мы можем получить интересующее нас название. Супер! И супер до тех пора, пока у нас в выводе не появится ForeignKey. Как только появится, считай все пропало.
    Рассмотрим небольшой пример. Есть у нас новости одной строкой. Есть регионы к которым привязаны эти новости:
    class RegionSite(models.Model):
        name = models.CharField(verbose_name="название", max_length=200,)
    
        def __unicode__(self):
            return "%s" % self.name
    
    
    class News(models.Model):
        region = models.ForeignKey(RegionSite, verbose_name="регион")
        date = models.DateField(verbose_name="дата", blank=True, null=True, )
        name = models.CharField(verbose_name="название", max_length=255)
    
        def __unicode__(self):
            return "%s (%s)" % (self.name, self.region)
    

    Нам нужно вывести 10 последних новостей, с названием, как у нас определено в News.__unicode__()
    Расчехляем рукава, и пишем:
    news = News.objects.all().order_by("-date")[:10]
    

    В шаблоне:
    {% for n in news %}
    {{ n }}
    {% endfor %}
    

    И вот тут мы вырыли себе яму. Если это не новости или их не 10 — а 10 тыс, то будьте готовы к тому, что вы получите 10 000 запросов + 1. А все из-за грязнокровки ForeignKey.
    Пример лишних 10 тыс запросов (и скажите спасибо что у нас мелкая модель — так бы выбирались все поля и значения модели, будь то 10 или 50 полей):
    SELECT `seo_regionsite`.`id`, `seo_regionsite`.`name` FROM `seo_regionsite` WHERE `seo_regionsite`.`id` = 1 
    SELECT `seo_regionsite`.`id`, `seo_regionsite`.`name` FROM `seo_regionsite` WHERE `seo_regionsite`.`id` = 1 
    SELECT `seo_regionsite`.`id`, `seo_regionsite`.`name` FROM `seo_regionsite` WHERE `seo_regionsite`.`id` = 2
    SELECT `seo_regionsite`.`id`, `seo_regionsite`.`name` FROM `seo_regionsite` WHERE `seo_regionsite`.`id` = 1 
    -- итп
    

    Почему так происходит? Все до генитальности просто. Каждый раз, когда вы получаете название новости, у нас происходит запрос к RegionSite, чтобы вернуть его __unicode__() значение, и подставить в скобки для вывода названия региона новости.
    Аналогично нехорошая ситуация начинается когда мы, например, в шаблоне средствами ORM пытаемся добраться до нужного нам значения, например:
    {{ subgroup.group.megagroup.name }}
    

    Вы не поверите какой жесткий запрос может там быть :) Я уж и не говорю о том, что таких выборок у Вас в шаблоне может быть десятки!
    Нас так просто не возьмешь — всхлипнули мы и воспользовались следующей отличной возможностью ORM — .values().
    Наша строчка кода магическо-клавиатурным способом превращается в:
    news = News.objects.all().values("name", "region__name").order_by("-date")[:10]
    

    А шаблон:
    {% for n in news %}
    {{ n.name }} ({{ n.region__name }})
    {% endfor %}
    

    Обратите внимание на двойное подчеркивание. Оно нам в скором времени пригодится. (Для тех кто не в курсе — двойное подчеркивание, как бы связь между моделями, если говорить грубо)
    Такими нехитрыми манипуляциями мы избавились от 10 тыс запросов и оставили лишь один. Кстати да, он получится с JOIN'ом и с выбранными нами полями!
    SELECT `news_news`.`name`, `seo_regionsite`.`name` FROM `news_news` INNER JOIN `seo_regionsite` ON (`news_news`.`region_id` = `seo_regionsite`.`id`) LIMIT 10 
    

    Мы до безумства рады! Ведь только что мы стали ORM-оптимизаторами:) Фиг-то там! Скажу Вам я:) Данная оптимизация — оптимизация до тех пор пока у нас не 10 тыс новостей. Но мы можем еще быстрее!
    Для этого забъем на наши предрассудки по количеству запросов и в срочном порядке увеличиваем количество запросов в 2 раза! А именно, займемся подготовкой данных:
    regions = RegionSite.objects.all().values("id", "name")
    region_info = {}
    for region in regions:
      region_info[region["id"]] = region["name"]
    
    news = News.objects.all().values("name", "region_id").order_by("-date")[:10]
    for n in news:
      n["name"] = "%s (%s)" % (n["name"], region_info[n["region_id"]])
    

    И дальше вывод в шаблоне нашей свежезаведенной переменной:
    {% for n in news %}
    {{ n.name }}
    {% endfor %}
    

    Да, понимаю… Данными строками мы нарушили концепцию MVT. Но это лишь пример, который можно легко переделать в строки, не нарушающие, стандарты MVT.
    Что же мы сделали?
    1. Мы подготовили данные по регионам и занесли инфо о них в словарь:
      SELECT `seo_regionsite`.`id`, `seo_regionsite`.`name` FROM `seo_regionsite`
      

    2. Выбрали из новостей все что нас интересует + обратите внимание на одинарное подчеркивание.
      SELECT `news_news`.`name`, `news_news`.`region_id` FROM `news_news` LIMIT 10 
      

      Именно одинарным подчеркиванием мы выбрали прямое значение связки в базе.
    3. Связали средствами питона две модели.

    Поверьте, на одинарных ForeignKey Вы прироста в скорости почти не заметите (особенное если выбираемых полей мало). Однако, если Ваша модель имеет связь через фориджн более чем с одной моделью — вот тут и начинается праздник данного решения.
    Продолжим изголяться над двойным и одинарным подчеркиванием.
    Рассмотрим до банальности простой пример:
    item.group_id vs. item.group.id
    

    Не только при построении запросов, но и при обработке результатов можно напороться на данную особенность.
    Пример:
    for n in News.objects.all():
        print n.region_id 
    

    Запрос будет всего один — при выборке новостей
    Пример 2:
    for n in News.objects.all():
        print n.region.id
    

    Запросов будет 10 тыс + 1, т.к. в каждой итерации у нас будет свой запрос на id. Он будет аналогичен:
    SELECT `seo_regionsite`.`id`, `seo_regionsite`.`name` FROM `seo_regionsite` WHERE `seo_regionsite`.`id` = 1 
    

    Вот такая вот разница из-за одного знака.
    Многие продвинутые джанговоды сейчас тыкают пальцем в куклу Вуду с моим кодом. И при этом задают мне вопрос — ты чего за пургу творишь с подготовкой данных, и где values_list(«id», flat=True) ?
    Рассмотрим замечательный пример, показывающий необходимость в аккуратности работы с value_list:
    regions_id = RegionSite.objects.filter(id__lte=10).values_list("id", flat=True)
    for n in News.objects.filter(region__id__in=regions_id):
        print n.region_id
    

    Данными строками кода мы:
    1. Подготавливаем список интересующих нас id-шников регионов по какому-то абстрактному условию.
    2. Получившийся результат вставляем в наш новостной запрос и получаем:
      SELECT `news_news`.`id`, `news_news`.`region_id`, `news_news`.`date`, `news_news`.`name` FROM `news_news` WHERE `news_news`.`region_id` IN (SELECT U0.`id` FROM `seo_regionsite` U0 WHERE U0.`id` <= 10 ) 
      

    Запрос в запросе! Уууух, обожаю :) Особенно выбирать 10 тыс новостей при вложенном селекте с IN (10 тыс айдишников)
    Вы конечно же понимаете чем это грозит? :) Если нет — то поймите — ничем, совершенно ничем хорошим!
    Решение данного вопроса так же до гениальности проста. Вспомним начало нашей статьи — никакой запрос не появляется без вычисления переменной. И сделаем ремарку, например, на второй строке кода:
    for n in News.objects.filter(region__id__in=list(regions_id)):
    

    И таким решением мы получим 2 простых запроса. Без вложений.
    У вас еще не захватило дух от падл, припасенных для нас ORM? Тогда капнем еще глубже. Рассмотрим код:
    regions_id = list(News.objects.all().values_list("region_id", flat=True))
    print RegionSite.objects.filter(id__in=regions_id)
    

    Данными двумя строками мы выбираем список регионов, по котором у нас есть новости. Все в этом коде замечательно, за исключением одного момента, а именно получившегося запроса:
    SELECT `seo_regionsite`.`id`, `seo_regionsite`.`name` FROM `seo_regionsite` WHERE `seo_regionsite`.`id` IN (1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9) LIMIT 21
    

    Ахаха, ORM, прекрати! Что ты делаешь!
    Мало того что он из всех новостей (у меня в примере их 256, вроде) он выбрал id регионов и просто их подставил, так он еще взял откуда-то limit 21. Про лимит все просто — так устроен print большого количества значений массива (я другого оправдания не нашел), а вот со значениями тут явно засада.
    Решение, как и в предыдущем примере, просто:
    print RegionSite.objects.filter(id__in=set(regions_id)).values("id", "name")
    

    Убрав лишние элементы через set() мы получили вполне адекватный запрос, как и ожидали:
    SELECT `seo_regionsite`.`id`, `seo_regionsite`.`name` FROM `seo_regionsite` WHERE `seo_regionsite`.`id` IN (1, 2, 3, 4, 9) LIMIT 21
    

    Все рады, все довольны.
    Пораскинув немного глазами по исторически написанному коду, выделю еще одну закономерность о которой Вы должны знать. И опять пример кода:
    region = RegionSite.objects.get(id=1)
    t = datetime.datetime.now()
    for i in range(1000):
        list(News.objects.filter(region__in=[region]).values("id")[:10])
        # list(News.objects.filter(region__id__in=[region.id]).values("id")[:10])
        # list(News.objects.filter(region__in=[1]).values("id")[:10])
        # list(News.objects.filter(region__id__in=[1]).values("id")[:10])
    print datetime.datetime.now() - t
    

    Каждая из строк итерации была последовательно включена (чтобы работала только одна). Итого мы можем получить следующие приближенные цифры:
    • 1 строка — 6.362800 сек
    • 2 строка — 6.073090 сек
    • 3 строка — 6.431563 сек
    • 4 строка — 6.126252 сек

    Расхождения минимальные, но видимые. Предпочтительные 2 и 4 варианты (я в основном пользуюсь 4м). Основная потеря времени — это то, как быстро мы создадим запрос. Тривиально, но показательно, я считаю. Каждый читатель сделает выводы самостоятельно.
    И завершим мы статью страшным словом — транзакция.
    Частный случай:
    • У вас InnoDB
    • Вам нужно обновить данные в таблице, в которую клиенты не пишут, а лишь читают (например список товаров)

    Делается обновление/вставку на раз-два
    1. Подготавливаем 2 словаря — на вставку данных и на обновление данных
    2. Каждый из словарей кидаем в свою функцию
    3. PROFIT!

    Пример реальной функции обновления:
    @transaction.commit_manually
    def update_region_price(item_prices):
        """
        Обновляем одним коммитом базу
        """
    
        from idea.catalog.models import CatalogItemInfo
    
        try:
            for ip in item_prices:
                CatalogItemInfo.objects.filter(
                    item__id=ip["item_id"], region__id=ip["region_id"]
                ).update(
                    kost=ip["kost"],
                    price=ip["price"],
                    excharge=ip["excharge"],
                    zakup_price=ip["zakup_price"],
                    real_zakup_price=ip["real_zakup_price"],
                    vendor=ip["vendor"],
                    srok=ip["srok"],
                    bonus=ip["bonus"],
                    rate=ip["rate"],
                    liquidity_factor=ip["liquidity_factor"],
                    fixed=ip["fixed"],
                )
    
        except Exception, e:
            print e
            transaction.rollback()
            return False
        else:
            transaction.commit()
            return True
    


    Пример реальной функции добавления:
    @transaction.commit_manually
    def insert_region_price(item_prices):
        """
        Добавляем одним коммитом базу
        """
    
        from idea.catalog.models import CatalogItemInfo
    
        try:
            for ip in item_prices:
                CatalogItemInfo.objects.create(**ip)
    
        except Exception, e:
            print e
            transaction.rollback()
            return False
        else:
            transaction.commit()
            return True
    

    Зная эти моменты, можно строить эффективные приложения с использованием Django ORM, и не влезать в SQL код.

    Ответы на вопросы:

    Раз уж пошла такая пляска, то напишите, когда стоит использовать ORM, а когда не стоит. (с) lvo
    Считаю что ОРМ стоит использовать всегда, когда оно просто. Не стоит складывать на плечи ORM, а уж тем более базы запросы типа:
    User.objects.values('username', 'email').annotate(cnt=Count('id')).filter(cnt__gt=1).order_by('-cnt')
    

    Тем более на HL-продакшн. Заведите для себя отдельный системный сервачок, в котором так изголяйтесь.
    Если у Вас нет возможности писать простыми «ORM-запросами», то измените алгоритм решения задачи.
    Для примера, у клиента в ИМ есть фильтрация по характеристикам, с использованием регулярок. Крутая гибкая штука, до тех пор пока посетителей сайта не стало очень много. Сменил подход, вместо стандартного Клиент-ORM-База-ORM-Клиент, переписал на Клиент-MongoDB-Питон-Клиент. Данные в MongoDB формируются по средствам ORM на системном сервере. Как было сказано раньше — HL нельзя достигнуть путем одних манипуляций с ORM

    Интересно, почему именно Django. Какие преимущества дает этот фреймворк (и его ОРМ) по сравнению с другими фреймворками / технологиями. (с) anjensan
    Исторически. Питон начал изучать вместе с Django. И знания в технологии его использования довожу до максимума. Сейчас в параллельном изучении Pyramid. Сравнить я пока могу только с PHP, и их фреймворками, цмс-ками. Наверное скажу общую фразу — я неэффективно тратил свое время, когда писал на PHP.
    Сейчас могу назвать пару серьезных недочетов в Django 1.3.4:
    1. Постоянное соединение/разъединение с базой (в старших версиях подправлено)
    2. Скорость работы template-процессора. По тестам, найденных в сети, она достаточна мала. Нужно менять :)

    А вообще, есть один классный прием, как увеличить скорость работы генерации template-процессора.
    Никогда не передавайте переменные в шаблон через locals() — при объемных функциях и промежуточных переменных — Вы получите молчаливого медленно шевелящегося умирающего монстра :)

    Что это за программист такой которому сложно запрос на SQL написать? (с) andreynikishaev
    Программист, который ценит свое время на программном коде, а не на средстве взаимодействия между База-Код обработки данных. SQL знать нужно — очень часто работаю напрямую с консолью базы. Но в коде — ORM. ORM легче и быстрее подвергается изменениям, либо дополнением. А так же, если пишешь обоснованно-легкими запросами, легко читать и понимать.

    Извините, все! (Бла-бла… жду замечаний, предложений, вопросов, пожеланий)
    Метки:
    Поделиться публикацией
    Похожие публикации
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 113
    • 0
      Спасибо, как ньюфагу в области очень любопытно :). HL штук никогда не писал, и поэтому использовал ORM как выяснилось бездумно. О том, что ORM может быть не эффективен слышал не раз, но конкретных примеров не видел, просветили.
      • +6
        В добавок с статье хотелось бы поделиться полезнейшим инструментом, который позволяет анализировать, какие запросы к базе выполняются. Называется он django-debug-toolbar. Этот инструмент прямо на отрендеренной странице добавляет меню, через которое можно смотреть некоторую отладочную информацию; кроме того, у него есть еще и режим командной строки, в котором для каждого запроса ORM выводится соответствующая SQL-конструкция. Подробнее — смотрите страницу проекта.
        • +4
          По «Не всегда нужные функции и обоснованные выборки» и FK — для этого придуман select_related.

          В чем смысл этого когда?
          regions_id = RegionSite.objects.filter(id__lte=10).values_list("id", flat=True)
          for n in News.objects.filter(region__id__in=regions_id):
              print n.region_id
          

          если вам нужно:
          for n in News.objects.select_related('region').filter(region__id__lte=10):
              print n.region_id
          


          Зачем делать запросы c __id__in? Если вы будете использовать sqlite, то вы быстро привысите максимальную длину запроса.
          И с такими запросами вы придумываете join'ы на стороне python'а.
          • 0
            И почему n.region_id, а не n.region.id?
            • 0
              Рассмотрен и тот и тот пример. А так же разница между ними. Внимательней, пожалуйста, читайте… внимательней :(
              • +1
                Рассмотрен и тот и тот пример. А так же разница между ними. Внимательней, пожалуйста, читайте… внимательней :(

                Сорри, упустил.

                Но если вы сделаете select_related, то вызовы с .id не будут создавать дополнительных запросов =)
                • –8
                  Не люблю я select_related, не знаю почему… Внутреннее какое-то отвращение к этой «команде». Но все же ее уважаю, тоже не знаю почему.
                  В практике почти не использовал. Возьму на более глубокое понимание данной штуки, скорее всего будет полезна в арсенале. Спасибо.
                  • +1
                    Не люблю я select_related, не знаю почему… Внутреннее какое-то отвращение к этой «команде»

                    А зря, в большинстве проблемных мест помогает.
                    Я, чтобы про него не забывать, для часто используемых во вьюхах моделей, создаю кастомный менеджер с методом for_views, где прописан select_related.
            • 0
              В статье написано:
              Подготавливаем список интересующих нас id-шников регионов по какому-то абстрактному условию.

              ничего личного, лишь пример. Вы верно переделали запрос. Но сломали мысль абзаца.
            • +1
              Вся информация взята по мотивам Django версии 1.3.4

              Это же было очень давно =)
              В 1.4 и 1.5 довольно таки много изменений касательно orm.
              • 0
                По 1.5 смогу где-то через пол года написать особенности… не раньше. Думаю будет уже достаточной практика. А 1.3.4 не так уж и стар :) Гляньте, например, на bitbucket.org — снизу страницы версии ПО
                • 0
                  Гляньте, например, на bitbucket.org — снизу страницы версии ПО

                  Они по своей сути большая интерпрайзная штука, им можно =)

                  Как минимум на 1.4 из-за staticfiles перейти стоит =)
                  • 0
                    а с 1.4 на 1.5 перейти вообще не проблема
                    • +1
                      Да вот нет, пользователей то перелопатили… Это всё-таки добавляет головняка при переходе.
                      • 0
                        Если не избавляться от профайлов, то на 1.5 еще можно по старому жить (будет ворнинг просто в стдауте, что профили — депрекейтед), вроди, только в 1.6 профайлы будут до конца выпилены.

                        Ну а избавиться от профилей — довольно легко, я это через саус миграции за пол часа сделал.
                    • 0
                      А если уже написана не одна сотня тысяч строк?
                      Говорим про HL проект, который долго делали и развиваем. А не про мелкие штамповки на пару тысяч строк кода.

                      Как правило это останавливает к «быстрому» переходу на новую версию.
                      • 0
                        Говорим про HL проект, который долго делали и развиваем.

                        Дык всё-равно нужно обновлять, либо быть большими как atlasian и самим делать секьюрити фиксы на используемую старую версию.

                        А не про мелкие штамповки на пару тысяч строк кода.

                        Такие проекты как раз обычно никогда не обновляются =)

                        Как правило это останавливает к «быстрому» переходу на новую версию.

                        Как правило останавливает лень, непокрытость проекта тестами и использование непубличных api.
                        • НЛО прилетело и опубликовало эту надпись здесь
                          • 0
                            Спасибо за разъяснение.
                        • 0
                          staticfiles добавили начиная с 1.3
                          Сам недавно переводил проект с 1.3.1 на 1.4.5. еще немного и 1.5.1 будет поддерживаться.
                    • +4
                      Спасибо за статью. У меня есть несколько замечаний.

                      Тот факт, что запросы в Django выполняются только тогда, когда мы начинаем итерацию по QuerySet — это особенность Django. С одной стороны удобно, с другой неявно (а мы все знаем, что явное лучше неявного). В общем спорное решение, да и ортогональное ОРМ.vs.SQL.

                      Приведенная проблема «N+1 запроса» может решаться и средствами ОРМ. Вот, например, в SQLAlchemy есть вариант subqueryload. Т.е. в базу идет 2 запроса — по одному на кажду сущность. Фактически ОРМ за нас делает примерно то же самое, что приведено в статье. А можно прямо в модели указать, что отношение между таблицами должно быть eager, и по умолчанию нужно использовать ровно 2 запроса при выборке серии объектов с зависимостями. Разумеется можно использовать и joinload, получив при этом поведение «как в Django».

                      В статье я совершенно не увидел ответа на свой вопрос. Так почему же именно Django-ORM? Почему не SQLAlchemy? Т.е. понятно, что Django может быть продуктивнее PHP, но это разве связано с проблемой ОРМ.vs.SQL. Да и в PHP Вы не использовали никаких ОРМ?

                      А вообще, статья точно про HL-проекты? Вышеназванные проблемы могут подпортить жизнь для очень небольших БД, где до HL еще ого-го как далеко. Вытащить из БД 10К сущностей — нынче это уже HL?
                      • –1
                        Большое спасибо за замечания. Приму к сведению, буду знать в какую сторону развивать свои навыки.

                        Не смогу полностью ответить на Ваш вопрос. Я практик. Сейчас все задачи решаются средствами Django ORM. SQLAlchemy для меня пока не является таким явным, как Django ORM. SQLAlchemy я пробую с Pyramid.

                        По поводу HL — в статье приведены лишь примеры, для понимания тонких мест. Опять же, ничего личного, только хардкор:)
                        В текущей ситуации максимальная работа с количеством сущностей с иcпользованием ORM — это внутренний инструмент по работе с поставщиками. Там объемы, которые лопатятся — сопоставление 1 млн к 1млн записей…
                        В продакшене, нужно рассматривать конкретные задачи — для освещения алгоритмов решения
                        • +3
                          Простите, но как внутренний инструмент может быть HL проектом? Нет, понятно, что база достаточно большая и все такое. Но у Вас что, этим инструментом одновременно пользуется 10К пользователей?
                          • –1
                            База маленькая — всего около 8Гб инфы (картинки в базе не храню :) )
                            Нет, тут именно в расчет пускаю скрипты по обработке данных. Задача — как быстро перелопатить данные, чтобы пользователи, сидящие на продакшене не почувствовали что базу выворачивают в текущий момент наизнанку:) Что-то типа того.
                      • +1
                        Ух ты, мой код попал в статью.

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

                        Потому что с упрощенный запросом я вижу только решение прогнать всех пользователей через collections.Counter или аналог, не уверен быстрее ли это.

                        Кстати, в некоторых случаях помогают ещё команды .iterator, .defer и .only. Но последние две могут при неправильном использовании сильно ударить по производительности.
                        • +11
                          ORM — это часть экосистемы джанги. Её можно легко оторвать, отказавшись в пользу алхимии или чистого SQL, но при этом отказаться и от DRY, тестируемости, мультибазовости, репликаций, миграций и доброго десятка необходимых приложений (south, modeltranslate, tastypie, haystack, taggit, contib.auth, contrib.admin, etc...).

                          К сожалению увидел в статье больше эмоций, чем глубого понимания ORM как инструмента. А как же select_related, prefetch_related, only?

                          Проблема с News.__unicode__() решается прописыванием select_related для выборки:
                          news = News.objects.all().select_related('region').order_by("-date")[:10]

                          Если не хотите при каджой выборке помнить, что нужно сделать select_related — засуньте его вызов в кастомный метод get_query_set и переопределите менеджер.

                          Чем плох запрос?
                          User.objects.values('username', 'email').annotate(cnt=Count('id')).filter(cnt__gt=1).order_by('-cnt')

                          А например в следующем куске кода есть как минимум 2 проблемы:
                          regions_id = list(News.objects.all().values_list("region_id", flat=True))
                          print RegionSite.objects.filter(id__in=regions_id)
                          

                          Первая — 2 запроса в базу, вторая — если попытаться использовать в качестве ключа для кэширования значение из RegionSite.objects.filter(id__in=regions_id).query, то можно огрести exception, в случае пустого значения regions_id.

                          Для полноты статье не хватает поругать _clone метод QuerySet'а и пары искуственных тестов с «медленными» джоинами в мускуле на helloworld приложении.
                          • 0
                            > Её можно легко оторвать, отказавшись в пользу алхимии

                            — Отрывать не обязательно. Можно совместно юзать. Кроме Алхимии есть еще Storm ORM, Peewee, SQLObject. У последнего легко извлекается SQLBuilder, который можно юзать для построения SQL в Джанге. То же можно сделать и с SQLAlchemy. Впрочем, SQLAlchemy можно использовать и целиком, — с помощью aldjemy библиотеки.
                          • 0
                            По большому счету во всей джанге только две хорошие вещи: админка и обилие батареек почти на все случаи жизни. Сейчас поднимает голову flask, обрастает сообществом, так сказать. Достойный преемник.
                            • +1
                              хорошие вещи: админка

                              Вам просто не приходилось её кастомизировать =(

                              А orm в джанге хорошая, проще чем sqlalchemy и в большинстве случаев работает отлично.
                              • 0
                                Смотря что вы подразумеваете под «кастомизировать». Есть несколько хороших батареек, которые позволяют сделать с админкой почти все, что угодно.
                                ORM в джанге подходит для типовых решений/запросов. Алхимия гораздо более гибкая.
                                • 0
                                  > Вам просто не приходилось её кастомизировать =(

                                  — И апгрейдить эти кастомизации под новые релизы Джанги.
                                • 0
                                  Разве не Pyramid сейчас первый претендент на преемника?
                                  • 0
                                    Flask это не преемник, а альтернатива. Преемником он мог бы стать, если бы джанго вдруг прекратила своё развитие и получившуюся нишу должен был бы кто-то заполнить, на данный момент, не думаю, что с джанго идёт миграция пользователей, думаю, многих, очень многих она устраивает. Не вижу причин так не думать т.к. сам пользуюсь Django (и Flask пробовал) и нахожу её прекрасным инструментом для решения своих задач.
                                  • +4
                                    Во-первых, про ваши примеры:
                                    list(News.objects.filter(region__in=[1]).values("id")[:10])
                                    list(News.objects.filter(region__id__in=[1]).values("id")[:10])
                                    


                                    Смотрим какие запросы генерит этот код:
                                    >>> print News.objects.filter(region__in=[1]).values("id").query
                                    SELECT `news_news`.`id` FROM `news_news` INNER JOIN `news_region` ON (`news_news`.`id` = `news_region`.`news_id`) WHERE `news_region`.`id` IN (1)
                                    
                                    >>> print News.objects.filter(region__id__in=[1]).values("id").query
                                    SELECT `news_news`.`id` FROM `news_news` INNER JOIN `news_region` ON (`news_news`.`id` = `news_region`.`news_id`) WHERE `news_region`.`id` IN (1)
                                    


                                    Видим, что эти два запроса генерируют одинаковый SQL, поэтому разницы использовать 3 или 4 вариант — нет. Не вводите людей в заблуждение.
                                    Ваш способ расчета времени порадовал. Вы замеряете скорость работы вот так:
                                    Утверждение: Все нечетные числа — простые.
                                    Доказательство: 3 — простое, 5 — простое, 7 — простое и т.д.

                                    И не забудьте, что база умеет кешировать запросы (в том числе и в процессорном кеше).

                                    Во-вторых, мне кажется, что в статье не хватает рассказа о select_related и prefetch_related. Очень полезные штуки.
                                    • 0
                                      Спасибо за замечания.
                                      Код одинаковый генерируется во всех 4х случаях. Тут разность именно в том, как быстро он генерируется, а не в том как быстро исполняется SQL.

                                      Обе команды у меня в арсенале, почти, не присутствуют. И понимания по ним у меня нет окончательного. Как говорил выше — будем изучать и есть к чему стремиться. Еще раз спасибо:)
                                      • +1
                                        > Тут разность именно в том, как быстро он генерируется
                                        Во-первых, нужно об этом говорить явно. Во-вторых, если это действительно так, зачем вы вообще делает реальные запросы? unicode(queryset.query) было бы достаточно.
                                        • 0
                                          Да Вы все верно говорите. Моя ошибка. Сглупил.
                                      • +1
                                        Главное не перегнуть с select_related — он очень больно кусается в сочетании с GenericField — происходит JOIN для всех данных в двух таблицах и только потом происходит фильтрация, несмотря на порядок действий. Хотя, после целой грядки граблей из GenericField я максимально сократил использование данного функционала. Вот забавный случай, который смог быстро найти:
                                        Вот такой код (rating_score был related GenericField от модели RatingScore на Book):
                                        book_list.order_by('-rating_score')[:10] # выдаёт 10 книг совершенно не отсортированных по rating_score
                                        book_list.filter(rating_score__gt=-1000).order_by('-rating_score')[:10] # список книг выдаётся ожидаемый

                                        Как показали раскопки — в первом случае из-за того, что таблица rating_score не приджойнена зараннее — сортировка просто игнорируется, но поле всё-таки есть — поэтому Django не выдаёт никакой ошибки.
                                        Ествественно, GenericField в данном случае совершенно некрасивое решение, оно было приемлимо до тех пор, пока мы не стали по нему сортировать, и тут оно стало совершенно вредным решением, но всё-таки ORM тоже неправа.
                                      • +2
                                        >>Программист, который ценит свое время

                                        Такой программист как раз и не будет юзать то что даст лишний гемор, ибо больше либ сторонних и софта — больше головной боли и багоправок(а править на HL над 24/7 и быстро).
                                        В статье на самом деле так и не заметил обьяснения почему ОРМ можно юзать для ХЛ. Для сравнения дам вам хороший пример — GAE Datastor API(по сути ОРМ, с очень похожим интерфейсом). НО тот ОРМ и ваш две огромные разницы. Ибо GAE ОРМ написан реально под HL и HA. И суть его сводится не к тому что бы назвать А — Б, а в том что бы спрятать от пользователя всю сложность распределенного хранилища(без которого в HL делать нечего).
                                        А теперь про ваш код. У вас он написан для одного сервера. А теперь представте что у вас 100 серверов бд с распределенными данными, Насколько легко ваш код перейдет от 1 до 100 серверов, от 100 к 10000? Если с напрягом, то какой смысл в абстракциях которые лишь подменяют А на Б, но фактически не делают рост программы простым?
                                        Во вторых вы говорите об экономии времени. А сколько ушло у вас времени на разбор всех нюансов в ОРМ, а сколько уходит когда выходит новая версия?

                                        Теперь что насчет Django, стандартный сайтик на нем дерит около 30-70RPS. Точно такой же сайтик к примеру на Gevent держит 800-1500RPS.
                                        Конечно можно скрестить ежа со слоном, но зачем страдать фигней когда можно изначально писать проще, быстрее и с лучшим результатом.
                                        Django это как wordpress, писать на нем сложные проекты можно конечно, но лучше реально написать с нуля.

                                        Я уже молчу о том что есть куча всяких фигонь которые нужно контролировать в HL. Django это по сути как пхп запрос-ответ, а если надо демон? На Gevent к примеру я могу реализовать все в рамках одной архитектуры без граблей.

                                        Короче Django да еще и со своей ОРМ это и близко не HL тулзы.
                                        • 0
                                          Теперь что насчет Django, стандартный сайтик на нем дерит около 30-70RPS. Точно такой же сайтик к примеру на Gevent держит 800-1500RPS.

                                          Можно пруфы? И будет явно не только gevent, а ещё много тулз или велосипедов. На одном http сервере сайтик не сделаешь =) И что эти тулзы и велосипеды будут работать быстрее и лучше, чем dajngo — далеко не факт. Вот tornado, то да =)

                                          Короче Django да еще и со своей ОРМ это и близко не HL тулзы.

                                          instagram не hl?)
                                          • 0
                                            На gevente счас крутсятся большинство проектов на python. До этого был крут twisted, но когда сделали гринлеты все начали с него слазить ибо колбеки реально делали спагетти из кода, + твистед медленнее, но правда имеет хорошую протокольную базу.
                                            Вы не путайте Twisted и Gevent с Django. Это абсолютно разные фреймы. Джанго это фрейм как пышный зенд, а твистед и гивент как например пхпДемон. Тоесть на них пишут сервера, а не сайтики.

                                            Вы конечне простите но мой один хттп сервер держит больше чем пачка джанг. Вопрос нахрена платить больше? А еще и заниматся администрированием 10 машин и 200 машин разница существенная.

                                            Instagram не имеет сложной логики работы, отчего фронт можно писать хоть на пыхе. Вся нагрузка ложится на Амазон. Тем более посмотрите парк машин довольно не мало. Это Раз.
                                            Два. HL это не много юзеров как многие считают. Я могу иметь миллион юзеров и миллиард серверов и это не будет HL. HL это когда вместо купить еще 100 серверов делают оптимизацию, и когда на одном сервере крутится без напряга лям юзеров.
                                            Три. Я не видел что и как они юзают. Вот FB говорит что юзает PHP, но их инженеры по секрету грят что это пиздежь полный и PHP там особой роли не играет.
                                            Четыре. Я писал проекты на Django и занимался оптимизацией их… но хорошего в этом мало, много работы на том что обыно вообще делать не надо.
                                            • 0
                                              Вы не путайте Twisted и Gevent с Django. Это абсолютно разные фреймы.

                                              Это я знаю =) Просто обычную рендерелку страничек и crud api'шку куда проще сделать на django, чем используя gevent.

                                              Вы конечне простите но мой один хттп сервер держит больше чем пачка джанг. Вопрос нахрена платить больше? А еще и заниматся администрированием 10 машин и 200 машин разница существенная.

                                              Это редко когда нужно, большинство проектов на django и из одной машину никогда не вылезут =) Их нет смысла делать на tornado/gevent/twisted/etc.

                                              Немного оффтопа, а как вы к tornado относитесь? Пробовали его в нагруженных-нагруженных проектах?

                                              • 0
                                                Я дописывал торнадо до первой версии, было много траблов с ивентлупом и блокировками. Как счас обстоят дела с проектом хз. Но Gevent в любом случае лучше.

                                                Обычную рендерилку как раз легче делать не на джанго. Ибо я написал 5 методов, запустил обычный хттп сервак гивента и вуаля. А Джанго это дох апи, поднятие гуниеорна или нджинкса с модулями под wsgi.
                                                • +1
                                                  Я дописывал торнадо до первой версии, было много траблов с ивентлупом и блокировками.

                                                  А gevent каким образом поможет избежать локов?

                                                  А Джанго это дох апи, поднятие гуниеорна или нджинкса с модулями под wsgi.

                                                  Но ведь wsgi, gunicorn и nginx лёгкие и производительные штуки.

                                                  А gevent у вас без wsgi и просто смотрит наружу?
                                                  • 0
                                                    Да просто смотрит наружу, если не надо балансировки.

                                                    WSGI это прооко связи между тем же nginx, apache с python скриптом. gunicorn отдельный сервак написаный на питоне, является демоном и wsgi не юзает а запускает скриипты в процессах на прямую.

                                                    Gevent имеет хорошый ивент луп который у меня ни разу не бажил. Торнадовский луп бажил, в версии до 1 там юзалось дофига грязных хаков что бы оно не висло, например открытие пайпа и скидывание туда мессаг что бы оживить процесс. Посему торнадо я не сильно верю.
                                                    • 0
                                                      Да просто смотрит наружу, если не надо балансировки.

                                                      Но ведь тогда у вас будет загружено только одно ядро (если не считать нагрузку от базы, очередей и тд)

                                                      Торнадовский луп бажил, в версии до 1

                                                      Сейчас там с ним всё в порядке.

                                                      дофига грязных хаков что бы оно не висло

                                                      Но gevent ведь это апогей грязных хаков =)
                                                      • +1
                                                        >> Но gevent ведь это апогей грязных хаков =)
                                                        Я тоже так думал. А потом понял, что «it just works». Простой линейный код — это очень ценно.
                                                      • НЛО прилетело и опубликовало эту надпись здесь
                                                • 0
                                                  Вот FB говорит что юзает PHP, но их инженеры по секрету грят что это пиздежь полный и PHP там особой роли не играет
                                                  насколько мне известно, они используют hiphop, а это не совсем PHP в его типичном понимании. Да и даже если PHP, то ничего удивительно — такие проекты как Wikipedia или VK тоже крутятся на PHP и нормуль
                                              • НЛО прилетело и опубликовало эту надпись здесь
                                                • 0
                                                  >>Вопрос. Как вам поможет Gevent в данном случае? Какая разница синхронная или асинхронная модель обработки запроса используется, если все равно нужно ждать порцию данных из базы/кэша?

                                                  Огромная. асинхронный процесс может пока ждет обработать еще два десятка запросов, синхронный не может.

                                                  >>Если проект нагруженный — это еще не значит, что там используется сложная логика запросов к базе, это даже не значит, что там сильно много данных в базе. Это первое.

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

                                                  Я не говорил про монолитность, я говорил о том что кучу дополнительных сервисов можно заменить одним, и секономить время как свое так и сапорта. При этом не увеличивая сложность кода и архитектуры.

                                                  >>У вас же не сразу раз и проект стал высоконагруженным

                                                  Ах если бы. Проекты разрабатываются под планируемые нагрузки. Я делаю игру и планирую что перый онлайн будет 5к, это значит что система должна держать эту нагрузку на текущих мощностях. Я не пишу игру под 10 человек, потом переписываю под 20 и т.п.
                                                  Любая промашка на момент построения архитектуры стоит очень дорого потом. Ибо к примеру вам нужно полностью изменить модель хранения днных дабы повзолить держать большую нагрузку. Вам нужно провести миграцию юзеров с другой модели на новую в риалтайме при загруженных серверах, без доп мощностей, да еще и модели отображаются далеко не просто. А иногда просто архитектура приложения делает невозможным дальнейший рост.
                                                  Поэтому HL это в первую очередь грамотная архитектура которая учитывает рост до хреновой тучи юзеров изначально. Иначе прийдется полностью все переписывать. это факт.

                                                  >>Опять же непонятно какая разница 1 сервер или 1000?

                                                  Разница в том что работать с 1 сераком не трабл. А вот когда у вас данные наодятся на 50 сервера а всего у вас их 1000 и вы еще не 100% знаете на каком сервере что лежит и один запрос Select * from A; может выоиваться в 20 запросов 3 мап/редюса и обьединения, то вот тогда ОРМ идет лесом, ибо она на это тупо не расчитана.
                                                  Я конечно понимаю что все привыкли что есть база и туда хренячим данные, но в реальности модель хранения данных может быть очень сложная, а иногда еще и мультиуровневая например часть в легком кеше, часть в реляционке и часть на ФС.

                                                  Я не люблю юзать продукты которые говорят что они везде помогут. Вопервых это вранье, а во вторых качество таких продуктов всегда низкое. Ибо это всеравно что сравнивать всесезонную резнину с шиповками в цепях. Для общей езды вроде всесезонка подходит, но когда начинается хард вам жопа.
                                                  • НЛО прилетело и опубликовало эту надпись здесь
                                                  • 0
                                                    > Gevent — это хак, на крайний случай. Его используют для патчинга сокетов и обхода отсуствия в Python асинхронной модели обработки.

                                                    А вот это вы зря. Его можно использовать как хак, но совсем необязательно. Если использовать библиотеки, которые сразу написаны на gevent, то никаких манкипатчингов не понадобится. Проблема лишь в том, что таких библиотек мало, но их и для твистеда днём с огнём не сыщешь.
                                                    • НЛО прилетело и опубликовало эту надпись здесь
                                                  • НЛО прилетело и опубликовало эту надпись здесь
                                                    • НЛО прилетело и опубликовало эту надпись здесь
                                                      • –2
                                                        Парсинг запросов, генерацию ответов, куки, сесси и прочее — у вас это вызывает страх?
                                                        Куки есть. Сессии в кеше. Генерация ответов как хотите так и шлите любой протокол канает, а не тока хттп. Парсинг запросов вообще смешно, 10 строк кода, если не надо чего то извращенного.
                                                        Вообщем все реализуется вами и вашими руками, за счет этого вы не офигиваете от того что в какой то момент весь проект падает потому что какойто идиот в коде джанги написал какуюто муть(а такой хрени я видел много).

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

                                                        Люди да что вы млин к этим фреймам привязались. Или писать руками нынче уже не в почете. Куда не глянь вместо Программист пишут «Django developer», «Zend developer» и прочее… Это из серии «Если у вас в руках молоток то все проблемы кажутся гвоздями»
                                                        • НЛО прилетело и опубликовало эту надпись здесь
                                                          • НЛО прилетело и опубликовало эту надпись здесь
                                                            • НЛО прилетело и опубликовало эту надпись здесь
                                                              • НЛО прилетело и опубликовало эту надпись здесь
                                                          • +5
                                                            regions_id = list(News.objects.all().values_list("region_id", flat=True))
                                                            print RegionSite.objects.filter(id__in=set(regions_id)))
                                                            

                                                            Зачем использовать set() когда есть .distinct()?

                                                            regions_id = list(News.objects.all().distinct().values_list("region_id", flat=True))
                                                            print RegionSite.objects.filter(id__in=regions_id))
                                                            
                                                            • –1
                                                              Лишняя штука, которая переносит выборку данных на плечи базы. Я как раз от такого пытаюсь избавляться.
                                                              • +6
                                                                На стороне базы это пройдёт быстрее, чем на стороне python'а =)
                                                                • +1
                                                                  Ну это имеет смысл есть БД сервер у нас 1, а python воркеров много и размазаны они на 3-4 сервера.
                                                                  Но в обычных ситуациях конечно лучше отдать все на съедение базе.
                                                                • +2
                                                                  .distinct() дешевая операция и есть мысли что это выгоднее чем посылать лишние байты по сети, а потом еще их обрабатывать на app машине.
                                                                  Вопрос то к чему, мне интересно, это реально помогает или просто оптимизация с потолка?
                                                                  • 0
                                                                    на неделе протестирую, у меня есть много моментов где я сетом пользуюсь чаще чем distinct() и тогда можно будет судить. вообще, поднял инфу в сети — везде все склоняются к distinct().
                                                              • НЛО прилетело и опубликовало эту надпись здесь
                                                                • 0
                                                                  Есть система с выборкой на основе множества фильтров, простых и не очень. Гораздо проще реализовать это с помощью орм, чем вручную склеивать запросы.

                                                                  Конечно в определенный момент настанет потолок возможностей орм, но у меня в этом проекте еще на настал.
                                                                  • +1
                                                                    А никто и не говорил, что нужно «склеивать запросы вручную».
                                                                    • 0
                                                                      Ну а как тогда?
                                                                      • 0
                                                                        Ну, например, различного рода DSL-и для построения SQL запросов. Для Python сходу не назову, но вот пример для Java. Или для Ruby. Или вот мой собственный велосипед для Clojure.
                                                                        • 0
                                                                          > Для Python сходу не назову
                                                                          sqlalchemy core, оно?
                                                                          • 0
                                                                            Как вариант.
                                                                            • 0
                                                                              А чем он лучше написания SQL в строках?
                                                                              • 0
                                                                                Не нужно заморачиваться с порядком склеивания. Добавляешь условия к объекту запроса в любом удобном месте по коду. При выполнении он сам все склеит как надо.
                                                                                • 0
                                                                                  С другой стороны, он также, как и ORM занимает время на генерацию запроса.
                                                                                  • +1
                                                                                    Время на генерацию запроса минимально в сравнении со временем его выполнения. Особенно если учесть, что на сервере БД его надо еще распарсить (в любом случае, даже с применением ОРМ).
                                                                          • 0
                                                                            Ну, собственно, да. При потолке орм буду использовать DSL. Но в исходном комментарии был выбор только между orm и sql:)
                                                                            • НЛО прилетело и опубликовало эту надпись здесь
                                                                              • 0
                                                                                В общем проблема в терминологии. Разобрались, вопросов нет:)
                                                                                • 0
                                                                                  Мне не приходилось использовать SQLAlchemy. Я так понимаю вы про неё говорите как замену джанго-орм?
                                                                                  Если да, то как в джанге работать с формами, админкой, обработкой данных полученых из ОРМ… и всем тем где вызывается класс модели и потом с ним работается по сути средствами ОРМ? Если мы заменяем орм на SQLAlchemy.
                                                                                  • НЛО прилетело и опубликовало эту надпись здесь
                                                                                    • 0
                                                                                      Полистаю вечерком. А в двух словах можете подсказать, если есть опыт.
                                                                                      1. Почему тогда не делать прямые запросы в бд, если основным из аспектов SQLAlchemy сложные/более_гибкие запросы(если я правильно понял её направление). То есть на простые и типовые задачи оставить джанго-орм, а на сложные запросы не реализуемые ОРМ, делать прямые запросы в БД в обход ОРМ.
                                                                                      2. Работает ли SQLAlchemy с хранимыми процедурами, триггерами, евентами и тем многообразием функционала самой базы данных, с которыми не работает джанго-орм?
                                                                                      • НЛО прилетело и опубликовало эту надпись здесь
                                                                                        • НЛО прилетело и опубликовало эту надпись здесь
                                                                                          • НЛО прилетело и опубликовало эту надпись здесь
                                                                                            • НЛО прилетело и опубликовало эту надпись здесь
                                                                                              • 0
                                                                                                Спасибо за развёрнутые ответы, буду читать и думать.
                                                                          • +3
                                                                            Начну с классической ошибки, которая меня преследовала довольно долго. Я очень сильно верил во всемогучесть Django ORM, а именно в Klass.objects.all()

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

                                                                            Попробуем поиграться с шаблонами, и любимой некоторыми функцией __unicode__().

                                                                            При чем тут эта функция. Все описанное к ней напрямую не относится.

                                                                            Нас так просто не возьмешь — всхлипнули мы и воспользовались следующей отличной возможностью ORM — .values().

                                                                            Только это уже не возможность ORM, а возможность избавится от ORM. Обычно используется, когда узкое место — именно создание инстансов. Зачем вы решили использовать её против лишних запросов — непостижимо.

                                                                            Для этого забъем на наши предрассудки по количеству запросов и в срочном порядке увеличиваем количество запросов в 2 раза! А именно, займемся подготовкой данных:

                                                                            Лишь бы свое, да с квадратными колесами. prefetch_related() же есть.

                                                                            Получившийся результат вставляем в наш новостной запрос и получаем: Запрос в запросе! Уууух, обожаю :) Вы конечно же понимаете чем это грозит? :) Если нет — то поймите — ничем, совершенно ни чем хорошим!

                                                                            Не понимаю. Нет правда, объясните. Мне-то казалось, что лучше так, чем гонять список айдишников из базы в Питон, а потом обратно.

                                                                            так он еще взял откуда-то limit 21. Про лимит все просто — так устроен print большого количества значений массива

                                                                            Так устроен repr() кверисета.
                                                                            • –1
                                                                              prefetch_related — идет с версии 1.4
                                                                              • +1
                                                                                Эта версия не подходит для хайлоада? Или почему вы считаете что её не нужно рассматривать? Ну и 1.3 уже не поддерживается.
                                                                                • 0
                                                                                  подходит, но все же я перескачу до 1.5 или 1.6… там будет (или уже есть) класный патч, который время коннекта до SQL увеличивает.
                                                                                  Сложность перехода, помимо пользователей (1.4->1.5), урлы… вот в чем жесть — такой труд… и ничего не пропустить бы:)
                                                                                  + структура файлов поменялась (к лучшему).
                                                                                  Переход запланирован. Сейчас только второй проект устаканю на версии 1.5, изучу особенности и буду переводить текущий. Нужно!
                                                                                  • 0
                                                                                    урлы… вот в чем жесть

                                                                                    Можно, оставаясь на 1.4, плавно переносить с {% load url from future %} =)
                                                                                    • 0
                                                                                      ага, но думаю что возьму себя в руки и сразу на 1.5 перейду, а может и 1.6… Тут подсказали, что в 1.5 пользователи еще не до конца допилены. Так-то, примерно дня 2-3 работы и, думаю, можно перевести проект на 1.5… Возможно, конечно, ошибаюсь — но я оптимист:)
                                                                                    • 0
                                                                                      Т.е. вы считаете что применять 1.4 не стоит потому что конкретно вы на нее еще не перешли? Или как воспринимать этот ваш рассказ в ответ на вопрос «что в 1.4 плохого?»?
                                                                                      • 0
                                                                                        Я конкретно не перевел проект на версию старше 1.3.4.
                                                                                        И считаю что это не преграда всем остальным проектам использовать более старшие версии.
                                                                                  • 0
                                                                                    Дак вы объясните, чем грозит запрос в запросу в условии where?
                                                                                    • 0
                                                                                      Ничем, если выборка небольшая…
                                                                                      Сейчас скажу абстрактную вещь, взятую из головы… когда тесты делал, у меня получалось что запрос-в-запросе как-то в разы медленней крутится, чем с подготовленными данными. Заметным отставание становится когда, например, выбираешь 200 тыс записей из первой таблицы, при этом во вложенном запросе у тебя выбирается порядка 60 тыс записей.
                                                                                      Яж практик, больше чем теоретик, делаю выводы из своего опыта.

                                                                                      select id, name from table1 where id2 in (select id from table 2)
                                                                                      


                                                                                      про такого рода запросы я говорю. Если я не прав, конечно же подправте
                                                                                      • +1
                                                                                        Как выше уже заметили, Ваши способы тестирования производительности вызывают легкое недоверие.
                                                                                        • 0
                                                                                          +1 Вам в карму

                                                                                          для меня, если честно, конечный показатель — это время генерации страницы, которое мне отдается в логах uwsgi, например вот:
                                                                                          image

                                                                                          а вот если промелькнет красная строчка — то это ппц… а они промелькивают. Это значит что время генерации страницы более 1,5 сек… и нужно что-то предпринимать, чтобы такого не было :)
                                                                                          Кстати да, 0,91 сек на главную — это тоже довольно много.
                                                                                        • 0
                                                                                          select id, name from table1 where id2 in (select id from table 2)
                                                                                          


                                                                                          Не знаю про mysql, но postgresql такой запрос «раскроет» и всё будет быстро и на бОльших базах.
                                                                                • 0
                                                                                  JOIN новостей и регионов на стороне питона доставляет :)
                                                                                  Один запрос с JOIN-ом на базе будет быстрее чем 2 запроса и JOIN на стороне питона как минимум из-за того что не надо 2 раза гонять данные.

                                                                                  Такой подход вообще не вяжется с названием топика.
                                                                                  • 0
                                                                                    Прочитайте еще раз внимательно… Все хорошо и в меру, пока у вас не появятся более одного джойна. Я рассматриваю небольшие примеры, показывая суть проблемы. Показываю их из личного опыта. А на личном опыте теория очень часто расходится с практикой, за счет небольших, но множественных особенностей
                                                                                    Как уже выше написали, я очень зря не учел еще 2 отличные команды. Спасибо за советы — будем совершенствоваться. (prefetch_related() и select_related() )
                                                                                    • 0
                                                                                      Может быть ситуация, что лучше пускай питон в 2 раза дольше работает, чем мускул на 10%. Хотя, конечно, оговаривать такое нужно.
                                                                                      • 0
                                                                                        такое тоже бывает оправдано. + порой у нас не заканчивается все на простых связях, которые могут оправдать нашу задачами одними JOIN'ами. Все зависит от ситуаций
                                                                                        • +1
                                                                                          Нет. В данном случае «база отработает один запрос за время X» vs «база отрботает 2 запроса за суммарное время X + мы по сети гоняем больше данных + нагружаем питон».
                                                                                          • +2
                                                                                            Сомнительно, что это верно на любых выборках — например, приджойненная таблица имеет две записи, а основная — миллиарды. Да и по сети может гоняться больше данных в случае запроса с джойном. FK — несколько байт, а его разворачивание в строке результата может занимать на порядки больше объема.

                                                                                            В общем данных только о схеме, по-моему, недостаточно, чтобы сказать «это будет медленно». Нужно знать реальные данные.
                                                                                        • +2
                                                                                          Один запрос с JOIN-ом на базе будет быстрее чем 2 запроса и JOIN на стороне питона как минимум из-за того что не надо 2 раза гонять данные.
                                                                                          Вот кстати совершенно не факт. Смотря что за БД используется. Если отталкиваться от MySQL, то есть ситуации когда вместо JOIN лучше сделать два запроса и склеить данные в коде
                                                                                        • 0
                                                                                          Все хорошо и в меру, пока у вас не появятся более одного джойна

                                                                                          Не совсем понял что вы этим хотите сказать: что такой подход обоснован для большого числа JOIN-ов?
                                                                                          То что я хотел донести своим комментарием — не надо пытаться делать работу базы данных.
                                                                                          • +1
                                                                                            У ORM одна проблема, его часто используют люди которые про СУБД слышали краем уха. В итоге вместо того чтобы сначала спроектировать БД, они сначала строят объектную модель, в итоге получают неудачно спроектированную БД с нарушением нормальных форм, а еще и ходят туда как в тупое хранилище. А потом появляются статьи вида «что же делать как нам быть». Хотя ответ прост, сначала ознакомится с реляционной теорией, узнать что такое нормальные формы, затем уже спроектировать БД и только после того как есть понимание как оно там начать использовать ORM. Дополнительно надо не забыть узнавать про жадную загрузку и оптимизацию запросов с указанием использования join вместо пачки запросов к СУБД. Нормальные ORM это позволяют делать. Как и позволяют ходить в СУБД нативным SQL.
                                                                                            • 0

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