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
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1An atomic cache write with unless_exist gives you a distributed debounce lock for free.
- 2Deferring the job with a wait window lets bursts of changes settle into one reindex.
- 3Deleting the debounce key inside the job reopens the window so future edits get scheduled.
Related explainers
ruby
require "shellwords" require "open3" module Backup
Building safe shell commands in Ruby
shell-out
subprocess
command-injection
Intermediate
7 steps
ruby
class UserAgentParser BROWSERS = [ [/Edg\/([\d.]+)/, "Edge"], [/OPR\/([\d.]+)/, "Opera"],
Parsing user-agent strings in Ruby
regex
pattern-matching
lookup-tables
Intermediate
8 steps
ruby
class LogAggregator BUCKET_FORMAT = "%Y-%m-%dT%H:%M" def initialize(entries)
Bucketing log entries by the minute in Ruby
aggregation
hashing
enumerable
Intermediate
5 steps
javascript
import { useState, useEffect, useCallback, useRef } from 'react'; const cache = new Map(); const inflight = new Map();
Building a stale-while-revalidate hook in React
caching
request-deduplication
custom-hooks
Advanced
10 steps
ruby
class WeeklySignupsReport DEFAULT_WEEKS = 12 def initialize(weeks: DEFAULT_WEEKS, source: User.all)
Building a weekly signups report in Rails
service object
aggregation
group by
Intermediate
7 steps
php
<?php namespace App\Services;
Building a cached daily leaderboard in Laravel
caching
aggregation
eager-loading
Intermediate
9 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/debouncing-search-reindex-jobs-in-rails-explained-ruby-5e22/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.