Data retention and export
Railhook keeps every event, attempt and incoming event; retention is the only thing that stops those tables growing.
What is kept, and for how long
Section titled “What is kept, and for how long”Set these in .env:
| Data | Variable | Default |
|---|---|---|
| Failed attempts | DATA_RETENTION_ATTEMPTS_DAYS |
90 days |
| Successful (2xx) attempts | DATA_RETENTION_SUCCESSFUL_ATTEMPTS_DAYS |
14 days |
| Events | DATA_RETENTION_EVENTS_DAYS |
90 days |
| Incoming events | DATA_RETENTION_INCOMING_EVENTS_DAYS |
30 days |
| Attempts kept per delivery | DATA_RETENTION_MAX_ATTEMPTS_PER_DELIVERY |
10 |
| Tunnel request log | — | 7 days |
Why these defaults:
- Successful attempts go first. A 2xx attempt says little once you know it worked; a failed one is the debugging record. Dropping successes at 14 days removes most of the volume and keeps the evidence.
- Events and failed attempts share 90 days. Keeping attempts longer than their event leaves records that point at nothing.
- A cap per delivery catches what age cannot: an endpoint failing for a week keeps adding attempts to one delivery.
When cleanup runs
Section titled “When cleanup runs”| Job | Default schedule | Variable |
|---|---|---|
| Main cleanup | 02:00 daily | DATA_RETENTION_CRON |
| Attempts-per-delivery cap | Every 30 minutes | DATA_RETENTION_LIMIT_CRON |
| Burst cleanup | Every 4 hours | DATA_RETENTION_BURST_CLEANUP_CRON |
| Table size metrics | Every 15 minutes | DATA_RETENTION_TABLE_METRICS_INTERVAL_MS |
Rows are deleted in batches of DATA_RETENTION_BATCH_SIZE (default 1000), so a long-overdue first run does not lock the platform.
Delivery attempts and the tunnel request log are partitioned by time in Postgres, so an expired period is dropped as a whole partition, instantly, instead of row by row. PARTITION_MAINTENANCE_ENABLED (default true) creates partitions ahead of time and drops expired ones.
Check that cleanup is working
Section titled “Check that cleanup is working”| Metric | Meaning |
|---|---|
delivery_attempts_table_rows, events_table_rows, incoming_events_table_rows |
Estimated rows in each large table |
delivery_attempts_cleanup_total, events_cleanup_total, incoming_events_cleanup_total |
Rows removed |
partition_dropped_total |
Partitions dropped |
partition_default_rows |
Rows that landed in the default partition. Should be zero; anything else means a partition was missing. |
A table that grows while its cleanup counter stays flat means the job is not running. Check the cron values and the API logs around 02:00.
Export an organization
Section titled “Export an organization”An owner exports an organization with GET /api/v1/orgs/{orgId}/export: one JSON document with the organization, members, projects, endpoints, subscriptions, sources, destinations, API keys and audit entries.
- Payloads are not included. Events, deliveries and attempts are too large for one document. Tell anyone answering a subject-access request with this file.
- Audit entries are capped at 10,000. When the cap applies,
auditLogsTruncatedistrueandauditLogsTotalgives the real count.
Erase data
Section titled “Erase data”A whole organization. An owner deletes it with DELETE /api/v1/orgs/{orgId}, removing everything that belongs to it. The audit log is kept on purpose, as proof of the erasure.
One person’s account. The person deletes it with DELETE /api/v1/auth/me:
- Identifying data is replaced and the account can never sign in again. Every session is closed and every membership removed.
- An organization where they were the only member is deleted with them.
- The request answers
409if they are the last owner of an organization with other members. Hand over ownership first.
Both erasures and the export are recorded in the audit log.