# Retention and scheduling

> The two scheduled commands that build the rollup and bound the tables, and how to run them yourself.

Source: https://requizon.boring-observability.dev/docs/retention
Section: Configuration — Requizon documentation (version 0.2)
Updated: 2026-09-16

---

Recording writes one row per transfer into `http_requests`. The dashboard does not read that table for its overview and charts: two commands turn it into hourly buckets, and later delete what is past its retention. Requizon puts both on Laravel's scheduler for you.

| Command | Scheduled | What it does |
| --- | --- | --- |
| `requizon:aggregate` | Every five minutes, in the background, never overlapping | Rebuilds the last two complete hours of `http_request_stats` and `http_request_outcome_stats` from the detail rows |
| `requizon:prune` | Daily at 03:00, in the background | Deletes detail rows older than 14 days and rollup rows older than 365 |

Both need `php artisan schedule:run` running every minute on one server. Without it, calls are still recorded, but the overview stays empty and the tables grow without limit.

## requizon:aggregate

```bash
php artisan requizon:aggregate            # the last 2 complete hours
php artisan requizon:aggregate --hours=48 # the last 48
```

The window always ends at the start of the current hour, so the hour in progress is never aggregated. Charts gain their newest point a few minutes after each hour turns.

Each run recomputes the hours in its window from scratch and overwrites their rollup rows, which is what makes re-running it safe. Two runs over the same hours produce the same numbers, and a run that was missed is caught up by the next one, as long as the missed hours are within its `--hours`. A row is stamped when its transfer finishes, so a slow call that started at 13:59 and ended at 14:01 counts towards 14:00.

> **Do not aggregate past your detail retention**
>
> Because it overwrites, aggregating an hour whose detail rows have been partly pruned replaces that hour's totals with the smaller number that is left. Keep `--hours` inside `retention.detail_days`.

The aggregate uses `INSERT ... ON DUPLICATE KEY UPDATE` with row aliases, so it needs MySQL 8.0.20 or newer, or a compatible MariaDB, on the connection named by `requizon.connection`. On any other driver it exits with an error saying so. It deliberately does not wrap its two statements in a transaction: the scan would lock the newest detail rows and hold up the inserts your application is making while it runs.

## requizon:prune

```bash
php artisan requizon:prune
php artisan requizon:prune --detail-days=3 --aggregate-days=90
```

Detail rows are deleted in chunks of 5,000 so a large first prune does not hold one long lock. The options override the configured windows for that run only.

## Choosing retention

```php
// config/requizon.php
'retention' => [
    'detail_days' => 14,
    'aggregate_days' => 365,
],
```

The two tables grow for different reasons. `http_requests` grows with your traffic: every outbound call is a row, and failed calls carry their response bodies. `http_request_stats` grows with the number of distinct API, host and path combinations per hour, however many calls each one had, which is why a year of it is cheap as long as [paths stay bounded](https://requizon.boring-observability.dev/docs/paths).

- **Shorten `detail_days`** when outbound volume is high or failed response bodies may hold sensitive data. It limits how far back the requests list and its details go. The overview and charts are unaffected.
- **Keep `detail_days` at least as long as your longest dashboard window** if you want the requests list to reach as far back as the charts do. The largest default window is 336 hours, which is 14 days.
- **Shorten `aggregate_days`** if you never look further back than a quarter. Nothing on the dashboard offers a window longer than `dashboard.windows` allows.

## Scheduling the commands yourself

To run them on a different cadence, on a specific server, or with your own monitoring hooks, switch the built-in schedule off and register them in `routes/console.php`:

```bash
REQUIZON_SCHEDULE=false
```

```php
use Illuminate\Support\Facades\Schedule;

Schedule::command('requizon:aggregate --hours=2')
    ->everyFiveMinutes()
    ->withoutOverlapping(10)
    ->onOneServer();

Schedule::command('requizon:prune')
    ->dailyAt('03:00')
    ->onOneServer();
```

If you only want to move the prune or widen the aggregate window, `schedule.prune_at` and `schedule.aggregate_hours` do that without taking over the schedule.


## Common questions

### How long does Requizon keep data?

Individual request rows for 14 days and hourly aggregates for 365, by default. requizon:prune deletes anything older every day at 03:00. Both windows are set under retention in config/requizon.php.
