Привет, Хабр!
Сегодня рассмотрим как ускорить стартап 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, на которые могут зарегистрироваться все желающие: