Class: ChronoForge::Cleanup
- Inherits:
-
Object
- Object
- ChronoForge::Cleanup
- 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
-
.run ⇒ Hash
Counts of deleted rows by category.
Instance Method Summary collapse
-
#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
constructor
A new instance of Cleanup.
- #run ⇒ Object
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
.run ⇒ Hash
Returns 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
#run ⇒ Object
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 |