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

Как сократить время ответа в 2 раза, добавив одну строку кода

Уровень сложностиСредний
Время на прочтение9 мин
Количество просмотров35K
Всего голосов 78: ↑75 и ↓3+82
Комментарии41

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

Производительность и Python.

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

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

Термин производительность включает в себя не только throughput.

Не во всех языках программирования с GC существуют непредсказуемые, неконтролируемые и нерегулируемые задержки в нормальном режиме работы.

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

И только @Artyomcoolв комментариях издевается над словами "питон и производительность".

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

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

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

Техкоманда проекта с 8 млрд выручки: выбирает инструменты, которые их устраивают.

Анон с хабра: у вас инструменты неподходящие, потому что считается что питон медленный!

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

А, на других языках нельзя получить техдолг? Спасибо, буду знать, что этим только питон страдает.

Техкоманда проекта с 8 млрд выручки: выбирает инструменты, которые их устраивают.

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

А, на других языках нельзя получить техдолг?

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

Они не починили, а подогнали под конкретное место. Теперь проблема будет в других местах стрелять.

Фундаментально её в Python не решить by design.

Если бы тот же сервис написали на Elixir, то и отлаживать ничего не пришлось бы, потому что там архитектура виртуальной машины так построена, чтобы не было всплесков по latency.

Если бы тот же сервис написали на Elixir, то и отлаживать ничего не пришлось бы, потому что там архитектура виртуальной машины так построена, чтобы не было всплесков по latency.

И какое соотношение спецов по питону и спецов по Elixir?

Подмена тезиса, протестую.

Почему? Я легко соглашусь с тем, что Rust уменьшает количество ошибок по сравнению с си, а Elixir не обладает проблемами с latency в отличии от питона.

Проблема в том, что разработка не существует в вакууме, у нее есть конкретные параметры — максимальные расходы на ФОТ, обьем фичей, которые надо внедрять в месяц, легкость онбординга новых сотрудников, количество специалистов на рынке, легкость написания, легкость чтения, и так далее. И выбор инструмента должен учитывать и эти вещи, а не просто фокусироваться на лучшем техническом решении из возможных.

И утверждать, что у ТС неправильно выбран стек, потому что Elixir не допустил бы этих проблем, я не готов, потому что вполне может быть ситуация, когда на Elixir невозможно за это время реализовать столько фич, сколько было реализовано на питоне или невозможно это адекватно поддерживать в рамках процессов в компании.

Ну, типа, это аналог спора нанять интегратора vs писать самим. Зачастую с точки зрения качества продукта писать самим лучше. Может быть будет кривовато, но зато разработчики с большей вероятностью напишут то, что нужно бизнесу. Почему же существуют интеграторы? Потому что не всегда компания, которая варит заборы в промышленных масштабах готова растить у себя IT-отдел с разработкой (для начала HR-ы умеют нанимать сварщиков, а программистов не умеют), чтобы написать себе CRM для учета заборов и потом платить ему годами ФОТ и обеспечивать задачами. Поэтому проще сходить к интегратору и получить не идеальный, дорогой, но гарантировано рабочий продукт.

И вот вы смотрите на статью и понимаете что ситуация у ТС решается переходом на Elixir. Но это тоже самое, что советовать компании с заборами сделать свой IT-отдел. Команду, которая будет пилить сервис на другом языке, нельзя просто так взять и нанять, нужны процессы найма. Их нельзя перебросить на другой проект, потому что там уже свой стек. Нельзя попросить сеньора из другой команды помочь с чем-то, его сеньорность в питоне, которой он щелкает вот такие проблемы как в статье как орехи, ничего не стоит в разработке под Elixir. И так далее, и тому подобное. Да, сервис-то будет работать стабильнее. Но борьба с такими багами в питоне занимает 5 дней в месяц (условно) и стоит 100к/месяц в ФОТ, а вот геморрой с организацией отдельной команды на другом языке как бы не на порядок больше.

Я не говорю, что у ТС именно так. Может, у них разноплановые команды и никакой миграции разработчиков и так нет, и есть спецы конкретно по Elixir, которым могут построить команду и это не будет больно. Я просто накидываю аргументы "почему такая хорошая идея как миграция на другой стек, в котором нет этих проблем" может раз за разом торпедироваться бизнесом.

Вы слышали про то что в 3.12 появились саб интерпретаторы, позволяющие запускать в одном процессе несколько питонов, у каждого свой GIL, таким образом достигается параллелизм?

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

Слышал-слышал, и даже код читал этого, с позволения сказать, JIT'а. А теперь запустите любой бенчмарк и сравните с буквально любым другим ЯП.

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

Одной из проблем Python является GIL, что приводит к ограниченной производительности в многопоточных приложениях. Однако у этого языка, действительно, есть и значительное преимущество — высокая скорость разработки, что важно для продуктовых компаний, нуждающихся в быстрой реализации новой функциональности. Также из плюсов можно отметить: большое количество разработчиков, наличие популярных библиотек для DS, - всё это для нас тоже являлись важными факторами. Вдобавок ко всему была хорошая экспертиза в Python. Поэтому решили выбрать Python не для POC, а для целевой реализации сервиса. По мере развития проекта ML часть вынесли в отдельные сервисы. CPU задач у нас осталось немного (порядка 10мс из 150мс), компилируемый язык тут не поможет.

Так как у нас появилось больше io задач, мы думаем о переходе на FastAPI с асинхронными эндпоинтами, почему и как мы это сделали мы расскажем в другой нашей статье.

Выбор языка программирования — всегда интересная тема, которая неизбежно порождает споры и обсуждения. Учитывая, что мы соответствуем нашим внутренним SLO и многие преимущества Python важны для нас, мы решили остаться на нём. Любой язык в сложных ситуациях требует "донастройки".

самым быстрым темпом разработки

Крайне сомнительное утверждение по отношению к Python. Он конечно не самый медленный по темпу разработки, но уж точно не самый быстрый.

В выводах не вижу пункта 0.

  1. Выбирайте, подходящий под задачу, язык программирования.

Судя по тому, что латенси ручки вполне укладывается в SLO, язык подходит под задачу.

Если что, я не коллега и не адвокат автора :)

Ага, забыли добавить: "желательно, на котором никто больше в компании не пишет"

Интересная статья, но есть один момент, который хочется узнать. Изменение этих настроек сказалось ли на других параметрах: потребление памяти, потребление GPU и т.д? Хочется понять реальную стоимость решения, а не только время потраченное разработчиками.

P.S. Разработчикам уважение, проделана классная и интересная работа

Если объектов с циклическими ссылками немного, то по сути GC просто вхолостую пробегался по тем объектам, которые позднее бы удалились по счетчику ссылок. Так что негативных последствий быть не должно

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

Я бы в рекомендации еще добавил - не используйте Python :)

Сборка мусора НА КАЖДЫЙ ЗАПРОС??? А сколько миллионов объектов вы аллоцируете?

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


Также сборка мусора по поколениям по умолчанию запускается, когда разница аллокаций и деаллокаций достигает 700. Но так как счётчик сборки мусора не зависит от запроса, то выбросы в виде сбора старших поколений могут приходиться на конкретный запрос, что и оказывало влияние на 99-й персентиль.Большое количество новых объектов влияет на частый сбор мусор 0-го поколения, что провоцирует долгий сбор в старших поколениях

Обычно раньше пул использовали, раз - и нет мусора

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

Не во всех.

Просто не используйте такие языки и не столкнетесь с проблемой сборки мусора.

Интересно и познавательно, спасибо.

Скажите, пожалуйста, как (и кем) был определён таймаут на ответ сервиса? Задали со стороны бизнеса?

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

Извините за офтоп, но сразу вспоминаю: "Чисто не там, где убираются, а там где не мусорят"

Я бы остановился на первом выводе, т.к. обработку большого количества объектов лучше отдать заточенным для этого средствам (например - БД)

К сожалению, не всё так просто. У нас есть несколько обращений к сторонним API: модели машинного обучения и горячему хранилищу с данными о взаимодействиях пользователей. Кроме того, реализовать и поддерживать всю бизнес-логику в базе данных зачастую довольно сложно.

А weakref в вашем случае не помогли бы?

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

import gc
import weakref


class MyTestClass:
    ...


print(f'Счётчик 0-го поколения = {gc.get_count()[0]}')
obj = MyTestClass()
print(f'Счётчик 0-го поколения = {gc.get_count()[0]}')
r = weakref.ref(obj)
print(f'Счётчик 0-го поколения = {gc.get_count()[0]}')

Счётчик 0-го поколения = 98
Счётчик 0-го поколения = 99
Счётчик 0-го поколения = 100

Нормально так одну строку добавить - предварительно перелопатить кучу логов, написать свой анализатор этих логов, воспроизвести работу GC, выдвинуть и проверить гипотезы...

Решение проблемы, действительно, состоит всего лишь из одной строки. Однако, чтобы найти причину, нам понадобился большой анализ. Теперь, когда мы узнали этот нюанс в Python, в будущем исправление подобных случаев займет гораздо меньше времени.

Было бы неплохо убрать это большое количество англицизмов. Стэп это шаг. и т.д. Читать это в контексте самой задачи неудобно

пайплайн — последовательность степов

ну тогда уж сиквенс степов

Тихо, тихо! Уже никто никуда не идет ))) На самом деле если бы в статье было бы написано сиквенс, то не обнаружилась бы дырка, можно было обнаружить только hole, не факт что из нее можно было бы вынырнуть, потому-что это не hole, а gap какой-то. А вот если дырку обозвать разрывом, то сразу понятно, что garbage collector постарался.

У кого-то (минусатора) с юмором беда

Спасибо, познавательно. Тоже замечал такие дырки в трейсе, но пока грешу на pydantic на жирных моделях из монги (при намеренном пропуске некоторых полей которые гарантированно не будут использоваться в конкретном запросе картина прям в разы лучше). Но гц тоже стоит проверить

А ещё 1 строкой кода не хотите ускорить с 0.6 до 0.5?

@dataclass(slots=True)
class Film:    
    film_id: int
    weight: float
    actors: list[str]    

Плюс сам по себе декоратор занимает чуть больше времени, чем явно определенные функции (в данном случае методы __init__, __eq__, и __repr__и там еще могут быть какие-то), это знакомо еще с уровня Hello World. Но на 1 млн записей эта разница настолько мала, что практически не ощущается, там какие-то доли миллисекунд.
Но раз у Вас каждая миллисекунда дорога - может лучше использовать методы?
Да, кода будет чуть больше, но операций, и соответственно времени меньше.

Увы, тут либо скорость, либо красота, по-другому в Python никак)

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