Когда Python — не тот инструмент: дерево решений и арифметика зарплат против серверов
Триаж узкого места: I/O-bound и C-library-bound — Python подходит; интерпретаторные циклы в масштабе стоят 10-50x на ядро. Free-threaded 3.13 меняет математику потоков, не скорость потока. Выносите горячий путь стренглером; Python — там, где решает скорость разработки.
Эндпоинт скоринга фич сжигал 35 мс чисто-питоновского CPU на запрос. На 4 000 RPS это означало двенадцать 8-ядерных подов, занятых только интерпретацией одного и того же цикла. Один инженер переписал этот единственный эндпоинт на Go — тот же алгоритм, строка в строку: 1.1 мс на запрос, и флот сжался с двенадцати подов до двух. Счёт за вычисления упал на $14k в месяц. Следующий ход руководства был предсказуем: RFC «Мигрировать все сервисы на Go». Аудит убил его одним слайдом — остальные тридцать Python-сервисов вместе использовали меньше ядер, чем один старый скорер; это были CRUD-клей, оркестрация и батчи, простаивающие на I/O, где Go не купил бы ничего, кроме года переписывания. Скорер был счётом. Остальные счётом не были никогда. Навык сениора — не выбрать язык, а найти счёт до того, как выставлять смету.
Классифицируйте узкое место, прежде чем судить язык
«Python медленный» — не диагноз. Диагноз приходит из урока 1 — py-spy на реальной нагрузке — и ложится в одну из трёх корзин:
- I/O-bound — сэмплы сидят в ожиданиях сокетов, CPU в основном простаивает. Python годится: простаивающее ядро простаивает на любом языке. Рычаги — asyncio, пулы соединений, конкурентность, а не переписывание. Go-порт сервиса, ждущего Postgres, всё так же ждёт Postgres.
- C-library-bound — фреймы живут внутри numpy, torch, драйвера базы. Python годится: он уже тонкий оркестратор над скомпилированными ядрами (вся суть урока 3). Рычаги — батчинг и лучшее кормление C; переписывание реализует заново клей вокруг тех же ядер.
- Interpreter-bound — горячи чисто-питоновские фреймы, и это в масштабе. Вот где разрыв 10-50x на ядро — реальные деньги. Эскалируйте лестницу: сначала алгоритм, потом векторизация или расширение (урок 3), и лишь когда путь остаётся горячим и остаётся счётом — переписывайте этот путь.
Free-threaded 3.13 меняет математику потоков, не скорость
Free-threaded сборка (PEP 703) делает GIL необязательным: чисто-питоновские потоки наконец могут работать параллельно. Чего она не меняет — скорость одного потока: ранние free-threaded сборки даже платят оверхед в единицы процентов, и специализирующему интерпретатору потребовалось время, чтобы вернуться. Сдвигается одна граница: «для CPU-параллелизма — процессы или C с отпущенным GIL» смягчается в сторону потоков. Межъядерный интерпретаторный разрыв, питающий случаи ниже, выживает нетронутым: сорок медленных ядер параллельно — всё ещё сорок медленных ядер.
Где Python — не тот инструмент
Три честных случая, каждый держится на числе из этого юнита:
- Постоянные CPU-bound сервисы в масштабе. 10-50x медленнее на ядро — значит 10-50x флота за ту же пропускную способность. На 2 ядрах всем всё равно; на двенадцати подах хука — или на сотнях ядер — интерпретатор и есть счёт за инфраструктуру.
- Пути с жёсткой латентностью. Паузы поколенческого GC (gen-2 проход по большой куче — миллисекунды), джиттер аллокатора и разброс интерпретатора воюют с p99 SLO в единицы миллисекунд. Смягчить можно —
gc.freeze, настройка порогов, — но вы торгуетесь с платформой, а SLO не торгуется. - Высококардинальные данные при дефиците памяти. Арифметика урока 2: 28-байтные int и 250-байтные объекты превращают 10 млн мелких записей в гигабайты, которые Go или Rust держат в сотнях мегабайт. Если рабочее множество обязано влезать в RAM на узел, язык выбирает пообъектный оверхед.
Все три случая имеют одну форму: оверхед Python реален, измерим и уже является счётом — не теоретической угрозой. Без устойчивого числа ядер из случая 1, жёсткого latency-таргета из случая 2 или RAM-скалы из случая 3 арифметика почти наверняка отклонит переписывание до того, как вы допишете RFC.
Для какого из этих сервисов Python действительно не тот инструмент?
Где Python остаётся прав: арифметика зарплат против серверов
Клей, оркестрация, внутренние инструменты, data science, управляющие планы ETL — домены, где скорость итераций доминирует над вычислениями. Проговорите арифметику явно, потому что она решает реальные RFC. Полная стоимость сениора — $150-300k в год. Облачный vCPU-год — примерно $200-600. Python-сервис, тратящий 10x на 4 ядрах, сжигает $10-20k в год — меньше одного инженеро-месяца. Переписывание должно вытеснять больше ядро-долларов, чем стоит в инженеро-долларах, — навсегда, включая сопровождение. Большинство сервисов эту планку не берут никогда. Скорер из хука взял её десятикратно — 240 потраченных ядер, — именно поэтому двигать стоило один сервис.
Python-сервис живёт на 4 vCPU (~$1.6k/год при $400/vCPU-год). RFC предлагает Go-переписывание за 2 инженеро-месяца, обещая 10x эффективности на ядро. Вердикт?
Паттерн миграции: стренглер, а не большой взрыв
Когда путь действительно берёт планку — выносите его, а не переписывайте мир. Поставьте измеренный горячий путь за контракт (protobuf или OpenAPI), реализуйте этот один сервис на Go или Rust, прогоните теневой трафик, сдвигайте проценты, сохраните Python-оркестрацию вокруг. Большие взрывы вязнут, потому что 90% кода, никогда не бывшие счётом, всё равно надо переписать баг-в-баг, тогда как стренглер отгружает те 10%, которые платят. Каждый следующий вынос оправдывается собственной ядерной арифметикой — а в системе из хука после переезда скорера не квалифицировался больше ни один сервис.
- 01Пройдите дерево решений для медленного Python-сервиса: три корзины, улики каждой и предписанный ход.
- 02Сформулируйте арифметику зарплат против серверов и примените её к интерпретаторным сервисам на 4 и на 300 ядрах.
Этот урок — вердиктный шаг юнита. Уроки 1-3 производят улики — куда падают сэмплы, сколько стоят объекты, что расширение может и не может купить, — а дерево решений превращает улики в ход. I/O-bound и C-library-bound сервисы остаются на Python не из лояльности, а потому, что их ядра простаивают или уже исполняют скомпилированные ядра, и переписывание покупает синтаксис. Интерпретаторные пути под постоянной нагрузкой — место, где структурный разрыв 10-50x на ядро конвертируется в размер флота, и даже там эскалация идёт: алгоритм, векторизация, расширение — и лишь потом переписывание. Free-threaded 3.13 (сборка Python без обязательного GIL) смягчает правило параллелизма, не трогая скорость на ядро. Каждый ход запирает экономика: инженеро-годы стоят шестизначно, vCPU-годы — сотни, поэтому переписывание обязано вытеснять больше ядро-долларов, чем потребляет инженеро-долларов навсегда, — арифметика, которая на одном дыхании отклоняет тщеславное переписывание 4-ядерного сервиса и одобряет вынос 300-ядерного скорера. Паттерн исполнения — стренглер: один измеренный горячий путь за контрактом, трафик сначала теневой, потом сдвигаемый, а Python сохраняет оркестрацию, в которой он лучший. Теперь, когда увидишь RFC с предложением мигрировать сервис на Go или Rust, — знаешь первый вопрос: запусти py-spy на реальной нагрузке, классифицируй узкое место и положи ядро-долларную арифметику на стол до начала дискуссии о языке.
Практика
Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.
Что-то непонятно?
Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.