Как стать автором
Обновить

Комментарии 25

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

Если обратиться к лидирующему на данный момент ответ с str.format, чисто вскидку, если это возможно прикинуть, эффективность была бы выше, как вы думаете?

Это вообще похоже наиболее эффективный вариант, круче только прямое обращение к MySQL по HTTP за JSON-ами. Вот только в наших условиях переписать на str.format около тысячи (на вскидку) возможных вариантов SQL-текстов будет затруднительно.

Приведу еще аргумент против str.format.


как это вообще переписать возможно?
SELECT DISTINCT
  "paywall_alternativesalesrule"."id",
  "paywall_alternativesalesrule"."priority",
  "paywall_alternativesalesrule"."is_active",
  "paywall_alternativesalesrule"."name",
  "paywall_alternativesalesrule"."player_stub_id",
  "paywall_alternativesalesrule"."type",
  "paywall_alternativesalesrule"."watch_everywhere",
  "paywall_alternativesalesrule"."provider_id",
  "paywall_alternativesalesrule"."licensed",
  CASE WHEN ("paywall_alternativesalesrule_os"."os_id" IS NULL AND
             "paywall_alternativesalesrule_subnet"."subnetgroup_id" IS NULL AND
             ("acl_domain"."name" IN (%s, %s, %s) OR
              "paywall_alternativesalesrule_allow_domains"."domain_id" IS NULL)
             AND (NOT (T11."name" IN (%s, %s, %s) AND T11."name" IS NOT NULL) OR
                  "paywall_alternativesalesrule_deny_domains"."domain_id" IS
                  NULL))
    THEN %s
  ELSE %s END AS "is_targeted"
FROM "paywall_alternativesalesrule"
  LEFT OUTER JOIN "paywall_alternativesalesrule_videos"
    ON ("paywall_alternativesalesrule"."id" =
        "paywall_alternativesalesrule_videos"."alternativesalesrule_id")
  LEFT OUTER JOIN "paywall_alternativesalesrule_os"
    ON ("paywall_alternativesalesrule"."id" =
        "paywall_alternativesalesrule_os"."alternativesalesrule_id")
  LEFT OUTER JOIN "paywall_alternativesalesrule_subnet"
    ON ("paywall_alternativesalesrule"."id" =
        "paywall_alternativesalesrule_subnet"."alternativesalesrule_id")
  LEFT OUTER JOIN "paywall_alternativesalesrule_allow_domains"
    ON ("paywall_alternativesalesrule"."id" =
        "paywall_alternativesalesrule_allow_domains"."alternativesalesrule_id")
  LEFT OUTER JOIN "acl_domain"
    ON ("paywall_alternativesalesrule_allow_domains"."domain_id" =
        "acl_domain"."id")
  LEFT OUTER JOIN "paywall_alternativesalesrule_deny_domains"
    ON ("paywall_alternativesalesrule"."id" =
        "paywall_alternativesalesrule_deny_domains"."alternativesalesrule_id")
  LEFT OUTER JOIN "acl_domain" T11
    ON ("paywall_alternativesalesrule_deny_domains"."domain_id" = T11."id")
WHERE ("paywall_alternativesalesrule"."is_active" = % s AND
       ("paywall_alternativesalesrule"."licensed" IS NULL OR
        "paywall_alternativesalesrule"."licensed" = % s) AND
       ("paywall_alternativesalesrule_videos"."video_id" IS NULL OR
        "paywall_alternativesalesrule_videos"."video_id" = % s) AND
       ("paywall_alternativesalesrule"."watch_everywhere" = % s OR (
         "paywall_alternativesalesrule_os"."os_id" IS NULL AND
         "paywall_alternativesalesrule_subnet"."subnetgroup_id" IS NULL AND
         ("acl_domain"."name" IN (%s, %s, %s) OR
          "paywall_alternativesalesrule_allow_domains"."domain_id" IS NULL) AND
         (NOT ("paywall_alternativesalesrule"."id" IN
               (SELECT U1."alternativesalesrule_id" AS Col1
                FROM "paywall_alternativesalesrule_deny_domains" U1 INNER JOIN
                  "acl_domain" U2 ON (U1."domain_id" = U2."id")
                WHERE U2."name" IN (%s, %s, %s))) OR
          "paywall_alternativesalesrule_deny_domains"."domain_id" IS NULL))))
ORDER BY "paywall_alternativesalesrule"."priority" ASC, "is_targeted" DESC
Написать хранимую процедуру?

Кстати, вот такие IN (%s, %s, %s) с переменным количеством %s бьют уже по самой СУБД. Потому что СУБД тоже пытается кешировать планы запросов. А каждое изменение количества параметров в запросе приводит к созданию нового плана запросов и к тому же самом комбинаторному взрыву. Правильнее было бы передавать один параметр — массив значений.

Не кажется ли вам, что трансляция существующей бизнес-логики из Django в хранимые процедуры это, мягко скажем, безумие? Даже если не брать в расчет декодирование idna-доменов и прочие штуки, для которых нет SQL-функций MySQL, получается перенос нагрузки на CPU с бекенда на базу данных.

«не передавать объекты моделей (только ID)» — вообще в Python объекты передаются по ссылке поэтому нет смысла передавать айдишники.

Вообще не про то разговор) Передав в фунцию объект модели Django, можно повытаскивать у этого объекта связанные ForeignKey-объекты, повызывать методы разные и т.п. — и навертеть в генерируемом SQL-запросе каких угодно новых условий так, что кэширующий декоратор этого не заметит.
Получается очень подверженный ошибкам подход, поэтому речь идет о прокидывании значений первичных ключей БД, которые к передаче по ссылке не имеют никакого отношения.

Не думаю, что стоимость получившейся разработки вышла намного дешевле стоимости апгрейда серверов… кроме того, увеличились вероятности ошибок в связи с нестандартным поведением (грубо говоря, где-то забыл передать id, а передал по-старинке объект, тест прошел, а аукнулось это намного позже). Я бы тут больше подумал об оптимизации бизнес логики, возможно там слишком наверчено, что это приводит к таким вот зубодробительнм запросам…

Хотя тут может быть все решает политика руководства — типа платить за новые сервера дорого и с чего бы это. А программистам вроде как и так зарплата платится, вот пусть чинят свои косяки…

1 человеко-месяц понадобился в данном случае. Это примерно четверть от стоимости одного нового сервера.

В вашем случае этот метод не приемлемый, но вдруг пробовали…
Что будет если оптимизнуть сам Python в этом месте, например на PyPy запустить?

Не знаю, поддерживает ли PyPy Django в полной мере сейчас, давно не смотрел в эту сторону. Но перетянуть с полсотни зависимостей на другой интерпретатор будет довольно сложно.

Да, на таком проекте я бы не рискнул так делать для боевых задач,
но в познавательных целях было-бы интересно посмотреть на результат.
На моих задачах подобного рода прирост скорости был примерно в 5 раз. При этом, что удивительно, потребление памяти оказалось в 2 раза меньше, чем из под CPython.
В принципе, решение вполне нормальное. Для каждого достаточно большого проекта подход для оптимизации может быть своим.
Кстати, если бы использовались CBV, то просто так декоратор для кеширования не всегда бы мог решить проблему. Если внутри кроме аргументов есть что-то типа self.object или другие свойства, то они могут быть не учтены при таком кэшировании.

выражение IN (%s) заменяется на IN (%s, %s, %s) с числом плейсхолдеров, соответствующих реальному числу значений в списке, а кортеж params делается плоским.

Тут еще вопрос что будет быстрее… Я бы лучше оставил один плейсхолдер, а параметр передавал как
",".join([str(i) for i in ids])
. Быстрее чем подставлять плейсхолдеры в строке. Минус — вы теряете проверку на безопасность аргументов, но если они не напрямую переданы с веба, то почему бы и нет?

однако узким местом является формирование таких запросов с использованием Django ORM

После беглого взгляда на реализацию возникает вопрос — а сильно ли меньше кода получилось со всеми этими Lazy классами? Метрикам верю, но не сложнее ли вышло?

Насчет Class-Based Views. Декоратор навешивается на метод менеджера модели, хотя это не обязательно. Получается, что всё кэширование остается на уровне работы с моделями, не задевая вьюшки и остальной код. Таким образом почти всё, касающееся Manager.self, можно объявить безопасным в плане учета в ключе кэширования. Понятно, что накосячить можно везде, от всего не защитишься.


Насчет замены в выражении IN проще показать код (и да, мы передаем в запрос разобранное значение заголовка реферер — так что str не катит).


Всё крайне просто
def normalize(sql, params):
    placeholders = []
    real_params = []
    for p in map(reveal, params):
        if isinstance(p, (list, tuple)):
            # IN(%s) -> IN(%s,%s,%s)
            placeholders.append(', '.join(['%s'] * len(p)))
            real_params.extend(p)
        else:
            placeholders.append('%s')
            real_params.append(p)
    return sql % tuple(placeholders), tuple(real_params)

Насчет объема кода. В каждом таком месте, где вызывался метод построения СЛОЖНОГО queryset-а, пришлось добавить контекст-менеджер и разнести построение queryset и итерацию по нему в разные методы. В среднем получилось плюс пять строк на каждый метод, и куча рефакторинга по приведению передаваемых параметров в "безопасный" для кэширования вид.

Может и не уместно, но не могу промолчать. У джанги не только ОРМ медленный, и шаблонизатор, и даже простой json другие фреймворки отдают быстрее. Модульная архитектура быстрее, функциональнее, гибче. Flask, bottle, Jinja2, Babel, Beaker, WTForms и т.д. и т.п. А если без ОРМ совсем никак, то SQLAlchemy. Печально, что в вебе такой язык как питон в первую очередь ассоциируется с джангой. Сугубо ИМХО.

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

Лопата или экскаватор, результат тот же — яма. В случае с джангой результат разный. Да и сравнение не уместно. Собрать full-stack фреймворк из различных компонентов, реализующих определенный функционал, не намного дольше и сложнее, чем установить, настроить джангу. Но в итоге получаем производительность и возможности.

Опять какой-то странный подход: взял, собрал из отдельных компонентов и сразу все производительно стало. Не бывает так. Django надо уметь готовить и уметь проектировать приложения для того чтобы достичь максимальной производительности. Она не хуже будет работать чем скопище отдельных компонентов.

Сколько раз не сталкивался с проектами основанными на множестве отдельных компонентов с Flask внутри, все болеют одной и той же проблемой
— велосипедостроительством. Как-только проект набирает массу кода, то он начинает превращаться в набор костылей с сложно отлаживаемой логикой. Переписывание проекта на Django спасает проект, и код, в этом случае, получается стандартным, в котором сможет разобраться любой нормальный программист Python.

Flask и ему подобные хороши для маленьких проектов с простой бизнес логикой, который разрабатывается командой в 2-3 человека. Где больше трех уже стоит использовать Django.

Все это испытано на своей шкуре и не на одном проекте.

Не сказал бы что Django прям вот любой костылепроект спасает, но порядка добавляет — это точно.

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

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

Любой проект нужно уметь проектировать правильно. Только вот как ты не крути, компоненты джанги от этого быстрее не станут, например шаблонизатор, орм… Они тормознутые своей реализацией.


Вот и автор статьи пишет, что отличным вариантом было бы отказаться от ОРМ джанги в пользу иного. Но пришлось решать проблему костылями. А по уму так большинство компонентов джанги следует заменить, ибо уступают аналогичным решениям. И в итоге от неё останется микро-фреймворк с кучей мусора.


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

Что конкретно приходилось велосипедостроить вместе с фласком, но не нужно было c джангой? Если речь идёт не о 5ти строчек кода?


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


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

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


Flask и ему подобные хороши для маленьких проектов с простой бизнес логикой, который разрабатывается командой в 2-3 человека. Где больше трех уже стоит использовать Django.

Чем больше проект, тем больше будет требований по гибкости и нагрузкам. То есть не джанго. CMS -> Django -> Flask и др. -> python


А вообще так спорить можно до бесконечности. Давайте остановимся на этом. :) Каждому своё.

А вообще так спорить можно до бесконечности. Давайте остановимся на этом. :) Каждому своё.


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

Прекомпиляцию запросов использовал часто, но django queryset для этого обычно не использовал. Не потому что его трудно кэшировать, а просто потому, что с ним хватает проблем и помимо этого. Django прекрасно понимает Raw-SQL, а это значит, что ответственность за создание SQL-запроса вовсе не обязательно возлагать на Django.

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

Если автору статьи больше импонирует билдер запросов алхимии, советую обратить внимание на sqlalchemy-django-query и на другие интеграционные библиотеки которые перечислены в указанной статье.

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

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

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Изменить настройки темы

Истории