Skip to content

Storing telemetry in a secondary database

Performance telemetry can be high-volume. Keeping it out of your primary database isolates that write load and lets you size, back up and retain the two independently. django-perfy makes this a one-setting change.

How it works

Every model in the package is registered under the performance app label. The bundled django_perfy.router.PerformanceRouter reads PERFORMANCE_MONITOR["DATABASE"] and pins reads, writes and migrations for that app label to the chosen alias. The router is appended to DATABASE_ROUTERS automatically when the app starts, so you don't have to register it yourself.

When DATABASE is "default" (the out-of-the-box value) the router is a no-op and nothing about a single-database project changes.

Setup

1. Declare the alias

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql",
        "NAME": "app",
        # ...
    },
    "performance": {
        "ENGINE": "django.db.backends.postgresql",
        "NAME": "perf_metrics",
        "USER": "perf",
        "PASSWORD": "...",
        "HOST": "metrics-db.internal",
        "PORT": "5432",
    },
}

2. Point the app at it

PERFORMANCE_MONITOR = {
    # ...
    "DATABASE": "performance",
}

3. Migrate the alias

Because the router confines the performance tables to their alias, run migrate against that database explicitly:

python manage.py migrate                          # your app tables on default
python manage.py migrate --database=performance   # performance tables here

Easy to forget

This second command is the step people miss. Without it, the performance app's tables simply don't exist on the secondary alias yet — the middleware will fail to write telemetry until you run it.

The allow_migrate rule guarantees the performance tables are only created on the configured alias — they will not appear on default, and other apps' tables will not be created on the performance alias by this router.

Registering the router manually (optional)

Auto-registration covers the common case. If you prefer to be explicit — or you manage DATABASE_ROUTERS yourself — add it directly; the auto-registration step detects it and won't add a duplicate:

DATABASE_ROUTERS = ["django_perfy.router.PerformanceRouter"]

Operational notes

  • Backups / retention can now differ from your primary database.
  • Cross-database relations: performance models only relate to each other, so there are no cross-database foreign keys to worry about.
  • Connection pooling: the secondary alias uses its own connection settings; tune CONN_MAX_AGE etc. independently.
  • Switching back: set DATABASE to "default" and the router stops routing. (Existing rows stay in whichever database they were written to.)