📋 Dispatch Log & Querying History
When dispatch logging is enabled (enable_log_dispatch_registry = true), Kaal maintains an audit trail of all cron job dispatch attempts. This allows you to query historical dispatch records, debug issues, and perform cleanup operations.
Enabling Dispatch Logging
Configure dispatch logging in your Rails initializer:
# config/initializers/kaal.rb
Kaal.configure do |config|
# Enable audit trail for all cron jobs
config.enable_log_dispatch_registry = true
# Choose your backend (determines storage and available methods)
config.backend = Kaal::Backend::PostgresAdapter.new
end
Accessing the Dispatch Log Registry
Use Kaal.dispatch_log_registry to access the underlying registry instance:
registry = Kaal.dispatch_log_registry
# Returns nil if no adapter is configured or adapter doesn't support dispatch logging
if registry.nil?
puts "Dispatch logging not configured"
else
puts "Dispatch registry is available"
end
Common API (All Backends)
These methods are available on all dispatch registry backends:
find_dispatch(key, fire_time)
Find a specific dispatch record:
registry = Kaal.dispatch_log_registry
fire_time = Time.at(1609459200)
record = registry.find_dispatch('reports:daily', fire_time)
# => { key: 'reports:daily', fire_time: Time, dispatched_at: Time, node_id: String, status: String }
# => nil if not found
dispatched?(key, fire_time)
Check if a dispatch exists (same as Kaal.dispatched? but at registry level):
registry = Kaal.dispatch_log_registry
already_dispatched = registry.dispatched?('reports:daily', Time.current)
# => true or false
Database Backend API
When using Kaal::Backend::PostgresAdapter, Kaal::Backend::MySQLAdapter, or Kaal::Backend::SQLiteAdapter adapters, the dispatch registry provides advanced querying:
find_by_key(key)
Find all dispatch records for a specific cron job:
registry = Kaal.dispatch_log_registry
# Get all historical dispatches, most recent first
records = registry.find_by_key('reports:daily')
# ActiveRecord::Relation, so you can chain more conditions
records.where(status: 'dispatched').limit(10)
records.order(fire_time: :asc)
find_by_node(node_id)
Find all dispatches originating from a specific node:
registry = Kaal.dispatch_log_registry
# Find all dispatches from a specific worker node
records = registry.find_by_node('web-worker-1')
# Most recent first
# Filter by status
failed = registry.find_by_node('web-worker-1').where(status: 'failed')
find_by_status(status)
Find all dispatch records with a specific status:
registry = Kaal.dispatch_log_registry
# Find successful dispatches
dispatched = registry.find_by_status('dispatched')
# Find failed attempts
failed = registry.find_by_status('failed')
cleanup(recovery_window: 86400)
Delete old dispatch records to prevent unbounded database growth:
registry = Kaal.dispatch_log_registry
# Delete dispatch records older than 7 days
deleted_count = registry.cleanup(recovery_window: 7 * 24 * 60 * 60)
puts "Deleted #{deleted_count} old dispatch records"
# Default is 24 hours
registry.cleanup # Deletes records older than 86400 seconds
Memory Backend API
When using Kaal::Backend::MemoryAdapter adapter, the registry provides in-memory inspection:
clear()
Clear all dispatch records (useful for testing):
registry = Kaal.dispatch_log_registry
registry.clear # Removes all dispatches
size()
Get the number of stored dispatch records:
registry = Kaal.dispatch_log_registry
count = registry.size
puts "#{count} dispatch records in memory"
Redis Backend
When using Kaal::Backend::RedisAdapter adapter, dispatch records are automatically expired based on TTL:
# config/initializers/kaal.rb
redis = Redis.new(url: ENV['REDIS_URL'])
Kaal.configure do |config|
config.backend = Kaal::Backend::RedisAdapter.new(redis, namespace: 'myapp')
config.enable_log_dispatch_registry = true
end
# Redis automatically expires records after 7 days (default TTL)
# No manual cleanup needed
Practical Examples
Example 1: Debug Why a Job Wasn’t Enqueued
registry = Kaal.dispatch_log_registry
fire_time = 1.hour.ago.beginning_of_hour
# Check if it was already dispatched
if registry.find_dispatch('sync:data', fire_time)
puts "Already dispatched, deduplication prevented job enqueue"
record = registry.find_dispatch('sync:data', fire_time)
puts "Dispatched at: #{record[:dispatched_at]} from node: #{record[:node_id]}"
else
puts "No dispatch record found for this fire time"
end
Example 2: Find Recently Failed Dispatches
registry = Kaal.dispatch_log_registry
# Get failed dispatches from the last hour
failed = registry.find_by_key('reports:daily')
.where(status: 'failed')
.where('dispatched_at >= ?', 1.hour.ago)
failed.each do |record|
puts "Job #{record.key} failed at #{record.dispatched_at} on node #{record.node_id}"
end
Example 3: Analyze Dispatch Pattern by Node
registry = Kaal.dispatch_log_registry
# Find which nodes dispatch the most jobs
nodes = ['web-1', 'web-2', 'worker-1']
nodes.each do |node_id|
dispatches = registry.find_by_node(node_id)
count = dispatches.count
puts "Node #{node_id}: #{count} dispatches"
end
Example 4: Automated Cleanup Task
Create a recurring task to clean up old dispatch records:
# lib/tasks/kaal_cleanup.rake
namespace :kaal do
desc 'Clean up old dispatch records'
task cleanup_dispatch_log: :environment do
registry = Kaal.dispatch_log_registry
if registry.nil?
puts 'Dispatch logging not configured, skipping cleanup'
next
end
# Delete records older than 30 days
recovery_window = 30 * 24 * 60 * 60
deleted = registry.cleanup(recovery_window: recovery_window)
puts "Cleaned up #{deleted} dispatch records older than 30 days"
end
end
Then schedule it in your cron system:
Kaal.register(
key: 'maintenance:cleanup-dispatch-log',
cron: '0 1 * * *', # Daily at 1 AM
enqueue: ->(_fire_time:, **) {
Rake::Task['kaal:cleanup_dispatch_log'].invoke
}
)
Direct Model Access
For advanced queries, you can also query the Kaal::CronDispatch ActiveRecord model directly:
# Find all dispatches for a job, ordered by recency
Kaal::CronDispatch.where(key: 'reports:daily').order(fire_time: :desc)
# Count total dispatches
Kaal::CronDispatch.count
# Find dispatches from today
Kaal::CronDispatch.where('fire_time >= ?', Time.current.beginning_of_day)
# Find the most recently dispatched job
Kaal::CronDispatch.order(dispatched_at: :desc).first
Note: It’s recommended to use Kaal.dispatch_log_registry for consistency, as it properly abstracts the underlying storage backend.
Troubleshooting
Registry returns nil
registry = Kaal.dispatch_log_registry
# => nil
# Check if dispatch logging is enabled
Kaal.configuration.enable_log_dispatch_registry
# => false
# Check if backend adapter is configured
Kaal.configuration.backend
# => nil
Get “method not found” error
registry = Kaal.dispatch_log_registry
# This will fail on Memory backend (doesn't support bulk queries)
registry.find_by_key('mykey') # Error: Memory backend doesn't have this method
Solution: Use portable methods available on all backends:
# Instead of find_by_key, use find_dispatch in a loop
fire_times = [1.day.ago, 2.days.ago, 3.days.ago]
fire_times.each do |ft|
record = registry.find_dispatch('mykey', ft)
# process record
end
Performance Issues with Large Tables
If your dispatch table is very large, consider:
-
Run cleanup regularly:
Kaal.dispatch_log_registry.cleanup(recovery_window: 7 * 24 * 60 * 60) -
Use indexed queries:
# Indexed by key, fire_time registry.find_dispatch('mykey', Time.current) # Slower: full table scan Kaal::CronDispatch.where(status: 'failed') -
Archive old records:
# Export records older than 90 days to archive storage old = Kaal::CronDispatch.where('fire_time < ?', 90.days.ago) # ... backup to S3, etc ... old.delete_all