Привет, Хабр!
Сегодня рассмотрим как ускорить стартап Python-приложений. Холодный старт — это прямые потери: в деньгах, в SEO, в отклике для пользователя. Serverless считает миллисекунды, edge-инфраструктура не ждёт, а тяжёлые импорты и неподготовленное окружение легко съедают полсекунды. Будем это чинить.
Минимизируем импорты на старте
Чтобы приложение взлетало быстро, первым делом убираем всё лишнее из зоны старта.
Сначала замеряем импорты
python -X importtime -m myapp 2> import.log python -m snakeviz import.log
В проектах бывает типичный разнос:
Модуль | Импорт за, мс |
|---|---|
pandas | 320 |
numpy | 180 |
scipy | 540 |
Делаем импорты локальными там, где можно
def enrich(df_like): import pandas as pd # noqa df = pd.DataFrame(df_like) df["ts"] = pd.Timestamp.utcnow() return df.to_dict(orient="records")
Импорт pandas будет происходить только, если реально вызовется эта функция.
Если глобальный импорт всё-таки нужен — используем LazyLoader
import importlib, sys _spec = importlib.util.find_spec("tensorflow") _loader = importlib.util.LazyLoader(_spec.loader) module = importlib.util.module_from_spec(_spec) _loader.exec_module(module) sys.modules["tensorflow"] = module
Заводим лёгкий entrypoint
# main.py def run(): from myapp.server import app app.run() if __name__ == "__main__": run()
Файлик main.py стартует за миллисекунды, потому что не тянет за собой кучу импортов сразу.
Теперь логичный шаг — прогреть окружение, чтобы сам контейнер быстро загружался.
Подготавливаем окружение заранее
Уб��раем тормоза уже на уровне контейнера и зависимостей.
Используем provisioned concurrency
На AWS Lambda можно держать контейнеры в прогретом состоянии. Платишь чуть больше, зато старта 0 мс.
Строим slim-образы
FROM python:3.12-slim-bookworm as builder RUN pip install poetry WORKDIR /src COPY pyproject.toml poetry.lock ./ RUN poetry install --no-dev FROM python:3.12-slim-bookworm COPY --from=builder /usr/local /usr/local COPY ./app /app CMD ["gunicorn", "-k", "uvicorn.workers.UvicornWorker", "myapp.api:app"]
Slim-образ размером ≈ 120 МБ против обычных 800 МБ — скачивание слоёв и раскрутка сети сокращаются в разы.
Убираем pip install из старта
poetry export -f requirements.txt > requirements.txt pip wheel -r requirements.txt -w vendor pip install --no-index --find-links=vendor -r requirements.txt
Или ещё лучше — собираем один огромный fat wheel.
Теперь ускорим сами тяжёлые места в коде.
Компиляция hotspot-ов + асинхронная подготовка данных
Сами по себе импорты уже оптимальны, окружение лёгкое — значит, работаем с тяжёлыми вычислениями и кэшами.
Выносим тяжёлые функции в Cython
# fastmath.pyx cimport cython @cython.cfunc def norm(double[:] v): cdef double s = 0 for i in range(v.shape[0]): s += v[i] * v[i] return s ** 0.5
Компилируем:
python setup.py build_ext --inplace
Импорт .so модуля занимает < 1 мс.
Асинхронно загружаем тяжёлые данные через PEP 684
import interpreters, asyncio, json, gzip CACHE = {} def load_big_cache(path): with gzip.open(path, "rb") as f: CACHE.update(json.load(f)) async def bootstrap(): interp = interpreters.create() interpreters.run_async(interp, load_big_cache, "/data/cache.json.gz") from myapp import app return app app = asyncio.run(bootstrap())
Пока кэш греется в фоне, приложение уже обрабатывает первые запросы.
Финальный бенчмаркинг
Конфиг | p95 cold, мс | p95 warm, мс |
|---|---|---|
Обычный образ + глобальные импорты | 920 | 24 |
+ Slim + Lazy imports | 620 | 23 |
+ Fat wheel | 410 | 22 |
+ Cython + PEP 684 | 310 | 22 |
Итог
Минимизируем импорты, используем slim-образы с готовыми зависимостями, компилируем узкие места и подгружаем тяжёлые данные асинхронно — результат: cold start ускоряется в 2–3 раза без ущерба коду и стабильности.
В завершение хочу порекомендовать бесплатные вебинары курса Python Developer. Professional, на которые могут зарегистрироваться все желающие:
