Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Continuous profiling, Давид Джалаев, PythoNN

Continuous profiling, Давид Джалаев, PythoNN

Avatar for Sobolev Nikita

Sobolev Nikita

September 21, 2025

More Decks by Sobolev Nikita

Other Decks in Programming

Transcript

  1. 2 Немного о спикере github.com/davidromanovizc Давид Джалаев • Разработчик •

    Опенсурсер • Хакатонщик • Начинающий спикер
  2. 3 О чем доклад • Зачем оно надо? • Киты

    наблюдаемости • Профайлинг и eBPF • Демонстрация на Pyroscope
  3. 5 Зачем? • Точно отражает производительность в продакшене • Позволяет

    оперативно вычислить первопричину проблемы с производительностью • Позволяет выявлять редкие или специфические проблемы
  4. 6

  5. 8 База наблюдаемости • Метрики – загрузка CPU, RAM, сети

    и д.р. • Логи – события приложений и инфраструктуры • Трейсы – путь запроса через распределенную систему
  6. 9 Профиль как сигнал • Добавлены в OTLP v1.3.0 как

    новый тип сигнала • Пока статус unstable, но прогресс большой • Общая спецификация и семантические конвенции • Теперь профили часть Observability, а не отдельный тул source: The State of Profiling | OpenTelemetry
  7. 10 Добавляется еще один • Профили – где именно код

    потребляет, например, CPU • Профилирование отвечает на вопрос: почему код работает медленно или нестабильно, а не только где это заметно
  8. 12 Профайлинг • Профиль – состояние/стектрейс + метрики • Профилирование

    – процесс сбора таких профилей ◦ Режимы (когда): ▪ Ручное – запускаем профайлер ▪ Непрерывное – профайлер работает в фоне ◦ Виды (как): ▪ Ручная разметка – с помощью изменения кода ▪ Сэмплирующие – периодическое снятие стека вызова
  9. 15 Почему это важно • Без вмешательства в код •

    Минимальный overhead (~2%) • Понимает K8s и Docker • Поддержка continuous profiling • Интеграции с Collector, Grafana, Pyroscope source: What is eBPF
  10. 16 Как работает? • Снимает стеки процессов и ядра •

    Привязывается к хукам (syscalls, tracepoints) • Не требует менять приложение source: What is eBPF
  11. 17 Но есть нюансы • Требуются привилегированные поды ◦ CAP_BPF,

    BPF_PROG_TYPE_PERF_EVENT, CAP_SYS_ADMIN • Уязвимости: ◦ CVE-2021-31440 – контейнер escape через eBPF verifier bug ◦ CVE-2022-42150 – eBPF cross-container атаки • CPython-стек без user-code
  12. 19 Как их избежать • Использовать готовые операторы • Использовать

    Sidecar или Node Agent, но с наименьшими правами (Это медленнее) • Давать минимум привилегий • Использовать py313, улучшает раскрутку стека, но все еще beta
  13. 21 Как обстоят дела с тулзами? • Elastic apm •

    Datadog • Sentry • Grafana pyroscope • Yandex Perforator • Parca.dev • Coroot
  14. 22 Общие проблемы инструментов • Нет единого встроенного стандартного решения

    • Overhead чуть выше, чем у нативных и VM/JIT-языков, но остаётся незначительным • Зачастую доступен только один вид профилей (CPU)
  15. 23 source: pydantic using abc which can have memory and

    performance issues def create_standard_order_template(): class StandardOrderType(StandardOrder): pass return StandardOrderType def create_premium_order_template(): class PremiumOrderType(PremiumOrder): pass return PremiumOrderType def process(self, order) -> None: if isinstance(order, StandardOrder): self.standard_orders += 1 elif isinstance(order, PremiumOrder): self.premium_orders += 1
  16. 24 source: pydantic using abc which can have memory and

    performance issues @router.post("/v1/process_orders") async def process_orders_old(orders, processor = Depends(get_order_processor_old)): order_instances = [ create_standard_order_template()(--.) if order.type -= "standard" else create_premium_order_template()(--.) for order in orders ] for order in order_instances: processor.process(order) return {"standard_orders": processor.snt_ord, "premium_orders": processor.prem_ord}
  17. 25

  18. 26 На чем тестировали • VM на Ubuntu 22.04 •

    4 RAM • 4 CPU • 40 GB NVME • Python 3.11 • FastAPI 0.109.1 • Pydantic v2.11 • pyroscope-io/pypprof
  19. 28 Grafana Pyroscope • Обычно низкий overhead: ~50 МБ памяти

    на pod, CPU ~1-5% • Поддерживает push и pull модели • Open Source решение, можно развернуть у себя в контуре • Если упадет сам Pyroscope, наш сервис никак не пострадает
  20. 31 pyroscope-io Python SDK for Pyroscope pyroscope.configure( application_name = "backend.python.app",

    server_address = "http:-/pyroscope:4040", sample_rate = 128 * 1024, tags = { "region": f'{os.getenv("REGION")}', } )
  21. 33 async def pyroscope_middleware(request, call_next): path = request.url.path if path.startswith("/healthcheck"):

    return await call_next(request) route = request.scope.get("route") template = getattr(route, "path_format", getattr(route, "path", path)) tags = { "endpoint": f"{request.method}:{template}", "method": request.method, } with pyroscope.tag_wrapper(tags): response = await call_next(request) return response
  22. 34 pypprof • Для django есть django-pypprof • Для flask

    есть flask-pypprof • Есть базовая штука, предоставляющая интерфейс pprof – pypprof • Но для этого нам надо использовать pull модель, взять еще alloy, потому что не рекомендуют грузить pyroscope Configure the client to send profiles
  23. 35 pyroscope.scrape "python_backend" { targets = [ {"-_address-_" = "backend:8081",

    "service_name" = "backend"}, ] scrape_interval = "30s" delta_profiling_duration = "15s" profiling_config { profile.process_cpu {} profile.memory { enabled = true path = "/debug/pprof/heap" delta = false } } forward_to = [ pyroscope.write.default.receiver ] } pyroscope.write "default" { endpoint { url = "http:-/pyroscope:4040" } }
  24. 36 def main() -> None: app = create_app() with ThreadPoolExecutor(max_workers=1,

    thread_name_prefix="uvicorn") as ex: ex.submit(run_uvicorn, app) os.environ.setdefault("PYPROF_PORT", "8081") os.environ.setdefault("MEMORY_PROFILER_ENABLED", "true") import mprofile mprofile.start(sample_rate=524288) from pypprof.net_http import start_pprof_server port = int(os.environ["PYPROF_PORT"]) start_pprof_server(host="0.0.0.0", port=port)
  25. 38

  26. 39

  27. 41

  28. 42

  29. 44

  30. 45 Что имеем? • Основная точка нагрузки по CPU и

    по памяти – это __subclasscheck__ /__instancecheck__ • В профилях видно, что именно process_orders_old • Diff сравнения baseline vs нагрузка подтверждает: основная причина роста из-за pydantic
  31. 47 Что потенциально мы получаем • Сокращение MTTR, MTTD •

    Возможность сравнивать разные релизы • Экономия на инфраструктуре • Более спокойный on-call, если есть такая практика • Снижение downtime и latency
  32. 48 Подводные камни • Профили тяжелые, требуют места и продуманной

    ретенции • Есть определенный overhead хоть и незначительный • Нативно только CPU-профили. Остальное требует танцы с бубном • Есть риски связанные с уязвимостями • Pprof-профили могут содержать чувствительные данные