# Data retention

A scheduled cleanup deletes rows that have expired, so the database does not keep sign-in data longer than it
needs to. It is on by default (added in 1.0.0-rc.13). It runs every 15 minutes in batches of 500 rows with
`FOR UPDATE SKIP LOCKED`, so it is safe with several replicas.

## What is deleted

| Data | Deleted |
| --- | --- |
| OAuth2 authorizations | one hour after their **last** token expired (the refresh token included); abandoned ones (no token yet, e.g. a consent or PAR request never finished) after a day |
| Browser sessions kept in PostgreSQL | after they expire (5-minute grace) |
| One-time codes | when older than two days |
| Magic-link, e-mail-verification and e-mail-change tokens | a day after they expired |
| Audit log | **never by default**; only when `helix.retention.audit-log-max-age` is set |

Flow state, rate limits and wallet transactions have their own sweepers (see
[High availability](high-availability.md)).

## The audit log is a policy decision

How long to keep the audit log depends on your logging, archiving and supervisory obligations, so HelixIAM keeps it
until you set a maximum age. Decide that first, and ship the audit log to your SIEM or archive if it must outlive
the database rows (see [Events & audit](../manage/events.md)).

## Settings

| Property | Environment variable | Default |
| --- | --- | --- |
| `helix.retention.enabled` | `HELIX_RETENTION_ENABLED` | `true` |
| `helix.retention.interval` | `HELIX_RETENTION_INTERVAL` | `15m` |
| `helix.retention.batch-size` | `HELIX_RETENTION_BATCH_SIZE` | `500` |
| `helix.retention.max-batches-per-run` | `HELIX_RETENTION_MAX_BATCHES_PER_RUN` | `200` |
| `helix.retention.authorization-grace` | `HELIX_RETENTION_AUTHORIZATION_GRACE` | `1h` |
| `helix.retention.authorization-unfinished` | `HELIX_RETENTION_AUTHORIZATION_UNFINISHED` | `1d` |
| `helix.retention.session-grace` | `HELIX_RETENTION_SESSION_GRACE` | `5m` |
| `helix.retention.code-max-age` | `HELIX_RETENTION_CODE_MAX_AGE` | `2d` |
| `helix.retention.token-grace` | `HELIX_RETENTION_TOKEN_GRACE` | `1d` |
| `helix.retention.audit-log-max-age` | `HELIX_RETENTION_AUDIT_LOG_MAX_AGE` | empty: keep the audit log |

Durations use Spring's format (`30m`, `12h`, `400d`). In the Helm chart: `retention.enabled`, `retention.interval`
and `retention.auditLogMaxAge`.

```bash
helm upgrade --install helixiam oci://ghcr.io/melmarzgioui/helixiam/charts/helixiam --version 1.1.0-rc.4 ... --set retention.auditLogMaxAge=400d
```

## Metrics

`helix_retention_deleted_rows_total{table}` counts deleted rows per table and `helix_retention_run_seconds` times
each run; both are on `/actuator/prometheus` (see [Observability & health](observability.md)).

## See also

- [GDPR & privacy](gdpr.md): exporting and erasing one user's data
- [Backup & disaster recovery](backup.md): deleted rows stay in older backups until those expire
