Class: FirebaseCleanupService

Inherits:
Object
  • Object
show all
Includes:
ElasticAPM::SpanHelpers
Defined in:
app/services/firebase_cleanup_service.rb

Overview

Service to cleanup Firebase Realtime Database reservation entries by deleting specific reservation IDs or IDs up to a threshold.

Phase 1: Safety-first approach - supports deletion by specific IDs or threshold to prevent accidental mass deletion of data.

This service uses FirebaseCleanupWorker which processes deletions in batches by accepting explicit reservation IDs and deleting them from Firebase.

Key features:

  • Supports specific reservation IDs or threshold-based cleanup

  • Batch deletion using Firebase update with nil values (1000 IDs per batch)

  • Validates and normalizes input (compact, unique, remove zeros/nils)

  • Comprehensive logging and error handling

  • APM instrumentation for monitoring

Usage:

service = FirebaseCleanupService.new

# Cleanup specific reservation IDs
service.cleanup_by_ids([12345, 67890, 11111])
service.cleanup_by_ids(12345) # Single ID

# Cleanup all reservation IDs <= threshold (up to max id)
service.cleanup_by_ids(1000) # Deletes all reservation_id <= 1000

Constant Summary collapse

BATCH_SIZE =
1000
MAX_RANGE_SIZE =
1_000_000

Instance Method Summary collapse

Instance Method Details

#cleanup_by_ids(reservation_ids, threshold_mode: nil) ⇒ Hash

Delete specific reservation IDs from Firebase, all IDs <= threshold, or a range of IDs This method accepts:

  • A single ID (Integer): Treated as threshold, deletes all IDs <= this value

  • An array of two integers: Treated as a range, deletes all IDs between from_id and to_id (inclusive) Example: [100, 200] will delete IDs: 100, 101, 102, …, 199, 200

  • An array of IDs: Deletes only the specified IDs

Parameters:

  • reservation_ids (Integer, Array<Integer>)

    Single threshold, range, or array of specific IDs to delete

  • threshold_mode (Boolean) (defaults to: nil)

    Force threshold mode even for arrays (default: auto-detect)

Returns:

  • (Hash)

    Summary with job IDs and stats



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
# File 'app/services/firebase_cleanup_service.rb', line 46

def cleanup_by_ids(reservation_ids, threshold_mode: nil)
  # Auto-detect threshold mode: single integer input is threshold mode
  is_threshold_mode = threshold_mode.nil? ? !reservation_ids.is_a?(Array) : threshold_mode

  if is_threshold_mode
    # Threshold mode: delete all reservation IDs <= threshold
    threshold = reservation_ids.to_i

    if threshold <= 0
      return error_response(
        'Threshold must be a positive integer',
        threshold: threshold,
      )
    end

    cleanup_by_threshold(threshold)
  elsif reservation_ids.is_a?(Array) && reservation_ids.size == 2 && reservation_ids.all? { |id| id.is_a?(Integer) }
    # Range mode: delete all reservation IDs between from_id and to_id (inclusive)
    # Validate before sorting
    if reservation_ids.any? { |id| id <= 0 }
      return error_response(
        'Invalid range for reservation IDs. Both IDs must be positive integers.',
        from_id: reservation_ids[0],
        to_id: reservation_ids[1],
      )
    end

    from_id, to_id = reservation_ids.sort
    range_size = to_id - from_id + 1

    if range_size > MAX_RANGE_SIZE
      return error_response(
        "Range too large (#{range_size} IDs). Maximum allowed range: #{MAX_RANGE_SIZE}",
        from_id: from_id,
        to_id: to_id,
        range_size: range_size,
      )
    end

    cleanup_by_range(from_id, to_id)
  else
    # Specific IDs mode: delete only provided IDs
    cleanup_specific_ids(reservation_ids)
  end
end

#cleanup_by_range(from_id, to_id) ⇒ Hash

Delete all reservation IDs between from_id and to_id (inclusive) from Firebase Fetches IDs from database where id >= from_id and id <= to_id and processes in batches

Parameters:

  • from_id (Integer)

    Minimum reservation ID to delete

  • to_id (Integer)

    Maximum reservation ID to delete

Returns:

  • (Hash)

    Summary with job IDs and stats



98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'app/services/firebase_cleanup_service.rb', line 98

def cleanup_by_range(from_id, to_id)
  BUSINESS_LOGGER.set_business_context(
    service: 'FirebaseCleanupService',
    method: 'cleanup_by_range',
    from_id: from_id,
    to_id: to_id,
  )

  BUSINESS_LOGGER.info(
    'Starting Firebase cleanup by range',
    {
      from_id: from_id,
      to_id: to_id,
      mode: 'range',
    },
  )

  # Fetch all reservation IDs in the range from database
  ids_array = Reservation.where('id >= ? AND id <= ?', from_id, to_id).order(:id).pluck(:id)

  if ids_array.empty?
    BUSINESS_LOGGER.info(
      'No reservations found in database for range cleanup',
      {
        from_id: from_id,
        to_id: to_id,
        mode: 'range',
      },
    )
    return {
      success: true,
      job_ids: [],
      total_ids: 0,
      batches_enqueued: 0,
      from_id: from_id,
      to_id: to_id,
      mode: 'range',
      message: 'No reservations found to delete in range',
    }
  end

  BUSINESS_LOGGER.info(
    'Fetched reservation IDs from database for range cleanup',
    {
      from_id: from_id,
      to_id: to_id,
      total_ids: ids_array.count,
      mode: 'range',
    },
  )

  # Split IDs into batches and enqueue workers
  job_ids = []
  ids_array.each_slice(BATCH_SIZE) do |batch|
    job_id = FirebaseCleanupWorker.perform_async(batch)
    job_ids << job_id

    BUSINESS_LOGGER.info(
      'Enqueued Firebase cleanup batch for range mode',
      {
        job_id: job_id,
        batch_size: batch.size,
        from_id: from_id,
        to_id: to_id,
        mode: 'range',
      },
    )
  end

  result = build_success_result(
    job_ids: job_ids,
    total_ids: ids_array.size,
    mode: 'range',
    message: "Enqueued #{job_ids.size} worker(s) to delete #{ids_array.size} reservation(s) from ID #{from_id} to #{to_id} from Firebase",
    from_id: from_id,
    to_id: to_id,
  )

  BUSINESS_LOGGER.info('Firebase cleanup by range - workers enqueued', result)

  result
end