📋 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:

  1. Run cleanup regularly:

    Kaal.dispatch_log_registry.cleanup(recovery_window: 7 * 24 * 60 * 60)
    
  2. Use indexed queries:

    # Indexed by key, fire_time
    registry.find_dispatch('mykey', Time.current)
    
    # Slower: full table scan
    Kaal::CronDispatch.where(status: 'failed')
    
  3. 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