Class: ChronoForge::Cleanup

Inherits:
Object
  • Object
show all
Defined in:
lib/chrono_forge/cleanup.rb

Overview

Reclaims storage from finished workflows and the unbounded execution-log rows that periodic tasks (durably_repeat) accumulate.

ChronoForge keeps every workflow and execution-log row indefinitely so that replays stay idempotent. Two things grow without bound over time:

1. Terminal workflows (completed/failed) that are no longer needed.
2. durably_repeat repetition logs — one row per scheduled execution. A
   long-lived periodic workflow never reaches a terminal state, so its
   repetition logs accumulate forever.

This is not run automatically — schedule it from your own scheduler (cron, Solid Queue recurring tasks, sidekiq-cron, GoodJob cron, the ‘whenever` gem, …). See ChronoForge::CleanupJob for a ready-made job, e.g.:

ChronoForge::Cleanup.run(
  older_than: 90.days,                       # default retention for terminal workflows
  failed_older_than: 180.days,               # keep failures longer for debugging
  prune_repetition_logs_older_than: 30.days  # opt in to periodic-log pruning
)

Workflow retention is measured from when a workflow became terminal

Retention is measured from the terminal transition, not created_at: a long-running workflow may have been created long ago but only just finished. Completed workflows use the immutable completed_at; failed workflows have no completed_at, so they use updated_at (the failed! transition, after which nothing touches the row — release_lock/context use update_columns/ update_column, which do not bump it).

Repetition-log pruning safety

Pruning periodic logs is opt-in and deliberately conservative. A repetition log is removed only when its scheduled time is BOTH older than the retention window AND strictly before the periodic task’s current frontier (the coordination log’s last_execution_at). Everything at or after the frontier is kept, because durably_repeat’s catch-up mechanism may still need it: the next execution is computed as last_execution_at + every, so anything at/after the frontier can still be revisited, while anything strictly before it never is. Both checks use the scheduled time embedded in the step name rather than created_at, which is misleading for catch-up rows created long after the occurrence they represent. A task that has not executed yet (no frontier) is never pruned.

Constant Summary collapse

DEFAULT_RETENTION =
90.days
DEFAULT_BATCH_SIZE =
1_000
TERMINAL_LOG_STATES =
%i[completed failed].freeze

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(older_than: DEFAULT_RETENTION, completed_older_than: nil, failed_older_than: nil, prune_repetition_logs_older_than: nil, batch_size: DEFAULT_BATCH_SIZE) ⇒ Cleanup

Returns a new instance of Cleanup.



65
66
67
68
69
70
71
# File 'lib/chrono_forge/cleanup.rb', line 65

def initialize(older_than: DEFAULT_RETENTION, completed_older_than: nil, failed_older_than: nil,
  prune_repetition_logs_older_than: nil, batch_size: DEFAULT_BATCH_SIZE)
  @completed_older_than = completed_older_than || older_than
  @failed_older_than = failed_older_than || older_than
  @prune_repetition_logs_older_than = prune_repetition_logs_older_than
  @batch_size = batch_size
end

Class Method Details

.runHash

Returns counts of deleted rows by category.

Parameters:

  • older_than (ActiveSupport::Duration)

    default retention for terminal workflows; used for any state without a specific override.

  • completed_older_than (ActiveSupport::Duration, nil)

    retention for completed workflows. Defaults to older_than.

  • failed_older_than (ActiveSupport::Duration, nil)

    retention for failed workflows. Defaults to older_than.

  • prune_repetition_logs_older_than (ActiveSupport::Duration, nil)

    when set, also prune old terminal durably_repeat repetition logs from still-active workflows (see safety notes above). nil disables it.

  • batch_size (Integer)

    rows per delete batch.

Returns:

  • (Hash)

    counts of deleted rows by category.



61
62
63
# File 'lib/chrono_forge/cleanup.rb', line 61

def self.run(**)
  new(**).run
end

Instance Method Details

#runObject



73
74
75
76
77
78
79
80
81
82
83
# File 'lib/chrono_forge/cleanup.rb', line 73

def run
  result = {workflows: 0, execution_logs: 0, error_logs: 0, repetition_logs: 0}

  # Completed workflows use the immutable completed_at; failed workflows
  # have no completed_at, so they fall back to updated_at.
  delete_terminal_workflows(:completed, :completed_at, @completed_older_than, result)
  delete_terminal_workflows(:failed, :updated_at, @failed_older_than, result)
  prune_repetition_logs(result) if @prune_repetition_logs_older_than

  result
end