Class: Flightdeck::BulkAction

Inherits:
Object
  • Object
show all
Defined in:
app/models/flightdeck/bulk_action.rb

Overview

Applies a Solid Queue operation across a filtered relation in bounded, individually-committed batches.

Three things keep this safe to run against a live queue:

* a hard record limit (`Flightdeck.config.bulk_action_limit`),
* a wall-clock deadline measured on the monotonic clock, and
* a commit per batch, so stopping early still leaves completed work done.

Stopping early is a normal outcome, not an error: the caller reports "Retried 1,000 of ~14,200" and the same request can simply be submitted again to continue.

Defined Under Namespace

Classes: Result

Constant Summary collapse

BATCH_SIZE =
100
DEADLINE =
10.seconds

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(relation:, limit: nil, deadline: DEADLINE, count_cap: nil) ⇒ BulkAction

Returns a new instance of BulkAction.



28
29
30
31
32
33
# File 'app/models/flightdeck/bulk_action.rb', line 28

def initialize(relation:, limit: nil, deadline: DEADLINE, count_cap: nil)
  @relation = relation
  @limit = (limit || Flightdeck.config.bulk_action_limit).to_i
  @deadline = deadline
  @count_cap = (count_cap || Flightdeck.config.count_cap).to_i
end

Instance Attribute Details

#count_capObject (readonly)

Returns the value of attribute count_cap.



26
27
28
# File 'app/models/flightdeck/bulk_action.rb', line 26

def count_cap
  @count_cap
end

#deadlineObject (readonly)

Returns the value of attribute deadline.



26
27
28
# File 'app/models/flightdeck/bulk_action.rb', line 26

def deadline
  @deadline
end

#limitObject (readonly)

Returns the value of attribute limit.



26
27
28
# File 'app/models/flightdeck/bulk_action.rb', line 26

def limit
  @limit
end

#relationObject (readonly)

Returns the value of attribute relation.



26
27
28
# File 'app/models/flightdeck/bulk_action.rb', line 26

def relation
  @relation
end

Instance Method Details

#call(&operation) ⇒ Object



35
36
37
38
39
40
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
# File 'app/models/flightdeck/bulk_action.rb', line 35

def call(&operation)
  total = relation.limit(count_cap).count
  started_at = monotonic_now
  processed = 0
  failed = 0
  stopped = nil

  relation.in_batches(of: BATCH_SIZE, order: :desc) do |batch|
    SolidQueue::Record.transaction do
      batch.each do |record|
        if processed + failed >= limit
          stopped = :limit
          break
        end

        begin
          operation.call(record)
          processed += 1
        rescue ActiveRecord::RecordNotFound, ActiveRecord::Deadlocked
          # The queue moved under us — a worker finished or retried this job
          # first. Skip it; the caller's counts stay honest.
          failed += 1
        end
      end
    end

    break if stopped

    if monotonic_now - started_at >= deadline
      stopped = :deadline
      break
    end
  end

  Result.new(
    processed: processed,
    failed: failed,
    total: total,
    total_capped: total >= count_cap,
    stopped: stopped
  )
end