Class: ScheduleWorkers::AvailableTimesCacheCleaner

Inherits:
ApplicationWorker show all
Includes:
ElasticAPM::SpanHelpers
Defined in:
app/workers/schedule_workers/available_times_cache_cleaner.rb

Overview

ScheduleWorkers::AvailableTimesCacheCleaner This worker is responsible for cleaning up stale available_times cache keys on a scheduled basis. It addresses the memory bloat issue where multiple cache keys accumulate for the same restaurant package or restaurant add-on due to changes in agenda_setting, start_date, end_date, or time_zone.

Problem:

The cache key format includes a hash of variables like [agenda_setting, start_date, end_date].
When these values change, a new cache key is created, but old keys are not deleted.
This leads to Redis memory bloat with multiple stale keys per restaurant package/add-on.

Example of stale keys (for restaurant_package_37267):

available_times_v2_version_v2025-03-08:{restaurant_package_37267}:variables_331924283
available_times_v2_version_v2025-03-08:{restaurant_package_37267}:variables_3977971064
available_times_v2_version_v2025-03-08:{restaurant_package_37267}:variables_2558844315
... (23 keys consuming ~8MB for ONE package)

Solution:

- Keep only ONE key per restaurant_package or restaurant_add_on (the most recently accessed one)
- Delete all other stale keys

Author:

  • GitHub Copilot

Constant Summary collapse

REDIS_SCAN_COUNT =

Configuration

1000
BATCH_SIZE =
100
CACHE_PATTERNS =

Cache key patterns to clean

[
  { pattern: 'available_times_v2_version_*:{restaurant_package_*}:*', id_regex: /:?\{restaurant_package_(\d+)\}:/,
    type: 'restaurant_package' },
  { pattern: 'restaurant-add-on-inv:{restaurant_add_on_*}:*', id_regex: /:?\{restaurant_add_on_(\d+)\}:/,
    type: 'restaurant_add_on' },
].freeze

Instance Method Summary collapse

Methods inherited from ApplicationWorker

unlimited_retry

Instance Method Details

#performObject

Main entry point for the worker



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'app/workers/schedule_workers/available_times_cache_cleaner.rb', line 41

def perform
  ElasticAPM.with_span('Available Times Cache Cleanup', 'cache', subtype: 'redis', action: 'cleanup') do |_span|
    cleanup_stats = {
      restaurant_packages_processed: 0,
      restaurant_add_ons_processed: 0,
      stale_keys_deleted: 0,
      total_memory_freed: 0,
      errors: 0,
    }

    begin
      HH_LOGGER.info('available_times_cache_cleanup_started', {
                       timestamp: Time.current.iso8601,
                     })

      CACHE_PATTERNS.each do |config|
        # Scan and group keys by entity ID
        entity_keys = scan_and_group_keys(config[:pattern], config[:id_regex])

        HH_LOGGER.info('available_times_cache_scan_completed', {
                         type: config[:type],
                         unique_entities: entity_keys.size,
                         total_keys_found: entity_keys.values.flatten.size,
                       })

        # Process each entity
        entity_keys.each do |entity_id, keys|
          entity_stats = cleanup_entity_cache(entity_id, keys, config[:type])

          if config[:type] == 'restaurant_package'
            cleanup_stats[:restaurant_packages_processed] += 1
          else
            cleanup_stats[:restaurant_add_ons_processed] += 1
          end
          cleanup_stats[:stale_keys_deleted] += entity_stats[:deleted_count]
          cleanup_stats[:total_memory_freed] += entity_stats[:memory_freed]
        rescue StandardError => e
          cleanup_stats[:errors] += 1
          APMErrorHandler.report(e, context: { entity_id: entity_id, type: config[:type] })
        end
      end

      HH_LOGGER.info('available_times_cache_cleanup_completed', cleanup_stats.merge(
                                                                  memory_freed_mb: (cleanup_stats[:total_memory_freed] / 1024.0 / 1024.0).round(2),
                                                                ))
    rescue StandardError => e
      APMErrorHandler.report(e, context: { cleanup_stats: cleanup_stats })
      raise
    end
  end
end