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
Tweet

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-профили могут содержать чувствительные данные