Honest AnalyticsHonest Analytics documentation
Honest Analytics
Support the plugin
Help
Honest Analytics
Support the plugin
Help
  • Documentation
  • Installation
  • Bringing your history with you
  • Scheduling
  • Page caches
  • Privacy
  • Retention
  • Uninstalling

Retention

Data ages in three stages: hourly detail, daily rollups, then deletion. Every stage is a setting, every setting has a cap, and the caps are enforced in code.

The stages

AgeState
0 to hourlyWindowDays (default 7)One row per hour per dimension
Up to rollupRetentionMonths (default 26, max 26)Folded to one row per day (hour = -1)
Beyond thatDeleted

Compaction is lossless for counts and merge-correct for uniqueness sketches. It reads the existing daily row back before merging, so running it twice changes nothing. Measured on a seeded dataset: 21,156 hourly rows folded to 3,467 daily rows with views and unique visitors identical before and after.

Everything with a retention setting

DataSettingDefaultCap
Hourly detailhourlyWindowDays7 days-
All rollupsrollupRetentionMonths26 months26 months
Journeys (Pro, consented)journeyRetentionDays90 days790 days
Consent recordsconsentLogRetentionDays0 = keep-
SessionssessionWindow30 minutes idle-
Dedupe noncesnonceTtl30 minutes-
Rate-limit counters-1 minute-
Identity saltsaltRotationInterval24 hours-
Report cache-1 hour, never today-

Consent records default to being kept indefinitely on purpose: a consent record is the evidence that consent was given, and deleting it destroys the ability to demonstrate compliance. Set a number of days if your policy requires one.

Twenty-six months is the cap because it is the outer limit most data protection guidance treats as proportionate for analytics, and because a hard limit that cannot be edited away is worth more than a default that can.

Growth

Storage grows with dimensions × time, not with pageviews × time. A site with a hundred thousand views a day and a site with a hundred use approximately the same amount of disk, because both write one row per hour per dimension value.

Measured on a seeded install: about 110,000 events, 60 distinct dimension values, 26 tables - roughly 15 MB, with no journey rows at all.

The bound is dimensionCap (default 1000). Each dimension accepts the first 1000 distinct values it sees in a period; everything after that is bucketed as __other__. First-N rather than top-N, because top-N would require storing everything first (ADR 27). Screens affected by the cap say so in a footnote and render "Other" as a muted, non-clickable row.

Garbage collection

Runs daily on honest_analytics_gc, or on demand:

wp honest-analytics gc
wp honest-analytics gc --dry-run

In order: compact hourly rows past the window; delete rollups past retention; delete journeys past retention; delete consent records past retention if a retention is set; close sessions idle beyond the window; delete expired key-value rows; remove dimension values no longer referenced by any rollup.

The last step is why Tables::dimensionReferences() exists, and why an integration test asserts that every table holding a dimension reference is in that list. A new rollup table added without registering it there would leave orphaned dimension rows accumulating forever, and the test fails rather than letting that happen quietly.

Tables::expiringRollups() and Tables::retainedElsewhere() have the same role for the retention sweep, with the same style of test: every table is in exactly one of the three registries, and the suite fails if a new one is in none.

Changing retention

Shortening it deletes data at the next garbage collection. That is immediate and irreversible - export first if you want it:

wp honest-analytics gc --dry-run   # says what would go

Lengthening it does not bring anything back.

Prev
Privacy
Next
Uninstalling