Ctrl K
ComunidadGuías…

documentation/profiling.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Profiling the backend with django-silk

The backend ships an **opt-in** profiler, [django-silk](https://github.com/jazzband/django-silk),
to answer questions like *"where does `media-auth` spend its time — Postgres, S3,
or Python?"* under a realistic data volume. It records, per HTTP request:

- the **SQL** it ran, each query with its own duration and the Python stack that
  issued it (so a slow `readable_per_se` `EXISTS` points straight at the line in
  `viewsets.py`);
- a **cProfile** call graph of the request, downloadable as a binary `.prof`;
- silk's own overhead, so you can subtract it.

It is **disabled by default and absent from every environment that does not opt
in, production included** — when `SILK_ENABLED` is unset, `silk` is not in
`INSTALLED_APPS`, its middleware is not installed, and `/silk/` is not routed.

> ⚠️ **Never enable silk against production with real users.** It persists
> request metadata to the database. Enable it only on an isolated environment —
> local dev, or a throwaway staging database populated with synthetic volumetry
> (see [Reproducing production volumetry](#reproducing-production-volumetry)).
> Request and response **bodies are never stored** (`SILKY_MAX_*_BODY_SIZE = 0`),
> so document content, titles and emails cannot leak into the silk tables.

## Enabling it

Set `SILK_ENABLED=1` in the backend environment and run silk's migrations once:

```bash
# in the backend container / environment
export SILK_ENABLED=1
python manage.py migrate silk
```

Then restart the backend so the middleware is picked up. The profiling UI is
served at **`/silk/`**, gated behind an authenticated **staff** session
(`SILKY_AUTHENTICATION` / `SILKY_AUTHORISATION`). Create one if needed:

```bash
python manage.py createsuperuser
```

All knobs are environment variables (defaults in `documentation/env.md`):

| Variable | Purpose | Default |
| --- | --- | --- |
| `SILK_ENABLED` | Master switch | `False` |
| `SILK_PYTHON_PROFILER` | cProfile each request | `True` |
| `SILK_PYTHON_PROFILER_BINARY` | Also emit a downloadable `.prof` | `True` |
| `SILK_INTERCEPT_PERCENT` | Sample only N % of requests | `100` |
| `SILK_MAX_RECORDED_REQUESTS` | Ring-buffer cap on stored requests | `10000` |

Under load (a thundering-herd reproduction), lower `SILK_INTERCEPT_PERCENT`
(e.g. `5`) so silk records a representative sample without becoming the
bottleneck itself, and keep `SILK_MAX_RECORDED_REQUESTS` bounded so a long run
cannot fill the disk.

## Analysing a request in the UI

1. Reproduce the traffic (open a document, or run a load test — see below).
2. Open `/silk/`, sort **Requests** by *Time* or *Overall time*.
3. Click the slow request:
   - **SQL** tab — every query with its duration and the *join / N+1* summary.
     Click a query to see its `EXPLAIN`-free timing and the **stack trace** back
     into our code.
   - **Profile** tab — the cProfile breakdown (cumulative/total time per
     function), the fastest way to see the Python↔DB↔S3 split.

## Analysing the binary `.prof` offline

With `SILK_PYTHON_PROFILER_BINARY=True`, each request's cProfile is also written
as a binary `.prof`, **through the Django storage framework** rather than the
local disk. It is stored via the `SILKY_STORAGE` entry of the `STORAGES` setting,
which points at the S3 backend under the `silk/` prefix — so profiling works on
read-only / ephemeral Kubernetes pods where there is no writable filesystem.

Download the `.prof` from the request's **Profile** tab in the UI (silk serves it
back out of object storage), then open it with any standard tool:

```bash
# interactive flame-ish call graph in the browser
pip install snakeviz && snakeviz <request>.prof

# or the stdlib, sorted by cumulative time
python -m pstats <request>.prof <<< "sort cumulative
stats 30"
```

To point the binaries at a different bucket/backend, override the `SILKY_STORAGE`
entry in `STORAGES` (any Django storage backend works).

## Cleaning up stored profiles

Silk's ring-buffer (`SILK_MAX_RECORDED_REQUESTS`) prunes the request rows in the
database, but it does **not** delete the corresponding `.prof` objects, so they
accumulate in storage. Reclaim the space with:

```bash
python manage.py purge_silk_profiles --dry-run    # preview orphans
python manage.py purge_silk_profiles              # delete orphaned profiles
python manage.py purge_silk_profiles --all        # wipe every profile
python manage.py purge_silk_profiles --older-than 7   # only >7 days old
```

By default it deletes only **orphans** (profiles no longer referenced by any
`silk.Request` row), which is always safe. `--all` is required — and is the only
mode available — when silk is disabled, since there are then no rows to compare
against. Everything goes through the `SILKY_STORAGE` backend, so it works on
filesystem-less pods.

## Profiling a code block directly (no HTTP)

To profile a specific path without going through nginx/DRF — handy for the
`media-auth` authorization logic, which normally runs as an nginx subrequest —
wrap it with silk's context manager or decorator; it appears under **Profiling**
in the UI:

```python
from silk.profiling.profiler import silk_profile

with silk_profile(name="media_auth authorization"):
    ...  # the code under test
```

## Reproducing production volumetry

Silk shows *where* time goes; it only tells the truth if the database has a
production-shaped volume. The incident class we chase (`media-auth` under a large
readable set) does not reproduce on an empty staging DB. Rebuild the shape from a
value-free profile — **without copying any production data** — then profile
against it:

```bash
# 1. On a read replica: capture the shape (counts + distributions only, no PII)
python manage.py profile_volumetry --output volumetry/prod.json

# 2. On the throwaway profiling DB: synthesize a same-shaped dataset
python manage.py generate_volumetry --profile volumetry/prod.json --scale 1.0

# 3. Enable silk, drive traffic (or the media-auth load tool), read /silk/
```

Alternatively, anonymize a real production dump in an isolated database with
`anonymize_database` and profile against that. See those commands' `--help` for
details.