ruby 43 lines · 7 steps

Debouncing search reindex jobs in Rails

A cache-backed lock collapses rapid record changes into a single delayed reindex job.

Explained by highlit
1class SearchIndexReconciler
2 DEBOUNCE_WINDOW = 5.seconds
3 
4 def self.schedule(record)
5 new(record).schedule
6 end
7 
8 def initialize(record)
9 @record = record
10 @key = "reconcile:#{record.class.name.underscore}:#{record.id}"
11 end
12 
13 def schedule
14 return if debounced?
15 
16 ReconcileSearchIndexJob
17 .set(wait: DEBOUNCE_WINDOW)
18 .perform_later(@record.class.name, @record.id)
19 
20 mark_scheduled
21 end
22 
23 private
24 
25 def debounced?
26 !Rails.cache.write(@key, true, unless_exist: true, expires_in: DEBOUNCE_WINDOW)
27 end
28 
29 def mark_scheduled
30 Rails.logger.info("[#{self.class}] enqueued reconciliation for #{@key}")
31 end
32end
33 
34class ReconcileSearchIndexJob < ApplicationJob
35 queue_as :indexing
36 discard_on ActiveRecord::RecordNotFound
37 
38 def perform(class_name, id)
39 record = class_name.constantize.find(id)
40 Rails.cache.delete("reconcile:#{class_name.underscore}:#{id}")
41 Search::Indexer.new(record).reindex!
42 end
43end
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1An atomic cache write with unless_exist gives you a distributed debounce lock for free.
  2. 2Deferring the job with a wait window lets bursts of changes settle into one reindex.
  3. 3Deleting the debounce key inside the job reopens the window so future edits get scheduled.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Debouncing search reindex jobs in Rails — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code