Skip to main content

Background Jobs & Scheduler

Heimdall runs recurring background work through the heimdall-scheduler crate. It registers a small set of cron jobs on a tokio_cron_scheduler JobScheduler and starts them when the API boots.

Overview

Location: crates/heimdall-scheduler/

The scheduler is started via start_scheduler(...), which:

  1. Checks whether the scheduler is enabled (default: enabled).
  2. Reads each job's cron expression and tuning values from configuration, falling back to built-in defaults when unset.
  3. Registers the jobs on a JobScheduler.
  4. Runs an initial scheduled-deletion check on startup so it does not have to wait for the first cron interval.
  5. Starts the scheduler.

If the scheduler is disabled, start_scheduler returns an empty JobScheduler with no jobs registered.

All cron expressions use the 6-field tokio_cron_scheduler format: sec min hour day month day-of-week.

Jobs

JobDefault scheduleWhat it doesConfig keys
Scheduled deletion processing0 */15 * * * * (every 15 min)Anonymizes user accounts whose grace period has elapsedscheduler.deletion_cron
Integration token refresh0 */5 * * * * (every 5 min)Proactively refreshes OAuth tokens expiring soonscheduler.integration_refresh_cron, scheduler.integration_refresh_buffer_minutes
Channel stats refresh0 0 */6 * * * (every 6 hours)Refreshes follower/subscriber counts for stale channel integrationsscheduler.channel_stats_refresh_cron, scheduler.channel_stats_stale_minutes

The whole scheduler can be turned off with scheduler.enabled = false.

Scheduled deletion processing

Implemented in process_scheduled_deletions. It selects rows from ScheduledDeletion joined to User where delete_at <= NOW() and the user is not already deleted (u.deleted_at IS NULL), ordered by delete_at ascending. For each match it calls anonymize_user (full data cleanup, including TimescaleDB audit anonymization and Redis cache invalidation) and broadcasts an account-deletion event over WebSocket to force-logout active sessions. Failures are logged per-user and the job continues with the remaining users. The job returns the count of successfully processed deletions.

This job also runs once on startup before the cron loop begins.

See GDPR & Privacy for the account-deletion grace-period flow that creates the ScheduledDeletion rows this job consumes.

Integration token refresh

Implemented in refresh_expiring_tokens. It loads integrations whose OAuth tokens expire within the configured buffer (get_integrations_needing_refresh). For each one it decrypts the stored refresh token, calls the platform's OAuth refresh endpoint, re-encrypts the new access/refresh tokens, and updates the integration's stored tokens and expires_at.

  • Integrations without a refresh token, or with an unknown platform, are skipped.
  • Successful refreshes log a integration_token_refreshed audit event (with no user/IP, since it is an automatic background refresh).
  • Failures mark the integration with an error state and log a integration_token_error audit event.

The buffer (minutes before expiry to trigger a refresh) defaults to 15 and is controlled by scheduler.integration_refresh_buffer_minutes.

Channel stats refresh

Implemented in refresh_channel_stats. It loads channel-type integrations that have not had their stats updated within the stale threshold (get_channel_integrations_for_stats_refresh). For each one it decrypts the access token and fetches channel statistics from the platform API, then upserts the follower/subscriber counts.

  • Only Twitch and YouTube are supported; other platforms are skipped.
  • The stale threshold defaults to 360 minutes (6 hours) and is controlled by scheduler.channel_stats_stale_minutes.

Configuration

Scheduler settings live under the [scheduler] section. All cron and tuning values are optional and fall back to the defaults shown above.

[scheduler]
# Enable/disable the background job scheduler (default: true)
enabled = true

# Cron for account deletion processing (default: "0 */15 * * * *")
# Format: sec min hour day month day-of-week
deletion_cron = "0 */15 * * * *"

# Cron for integration OAuth token refresh (default: "0 */5 * * * *")
integration_refresh_cron = "0 */5 * * * *"

# Refresh tokens this many minutes before expiry (default: 15)
integration_refresh_buffer_minutes = 15

# Cron for channel stats refresh (default: "0 0 */6 * * *")
channel_stats_refresh_cron = "0 0 */6 * * *"

# Minutes after which channel stats are considered stale (default: 360 = 6h)
channel_stats_stale_minutes = 360
KeyTypeDefaultPurpose
scheduler.enabledbooltrueMaster on/off switch; when false no jobs are registered
scheduler.deletion_cronstring"0 */15 * * * *"Deletion job schedule
scheduler.integration_refresh_cronstring"0 */5 * * * *"Token refresh job schedule
scheduler.integration_refresh_buffer_minutesint15Refresh tokens expiring within this window
scheduler.channel_stats_refresh_cronstring"0 0 */6 * * *"Channel stats job schedule
scheduler.channel_stats_stale_minutesint360Treat channel stats older than this as stale

These values can also be overridden via environment variables (e.g. HEIMDALL__SCHEDULER__DELETION_CRON). See Configuration for the full configuration resolution order.