Class: SourceMonitor::Fetching::FeedFetcher::AdaptiveInterval

Inherits:
Object
  • Object
show all
Defined in:
lib/source_monitor/fetching/feed_fetcher/adaptive_interval.rb

Constant Summary collapse

MIN_FETCH_INTERVAL =
5.minutes.to_f
MAX_FETCH_INTERVAL =
24.hours.to_f
INCREASE_FACTOR =
1.25
DECREASE_FACTOR =
0.75
FAILURE_INCREASE_FACTOR =
1.5
JITTER_PERCENT =
0.1

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(source:, jitter_proc: nil) ⇒ AdaptiveInterval

Returns a new instance of AdaptiveInterval.



16
17
18
19
# File 'lib/source_monitor/fetching/feed_fetcher/adaptive_interval.rb', line 16

def initialize(source:, jitter_proc: nil)
  @source = source
  @jitter_proc = jitter_proc
end

Instance Attribute Details

#jitter_procObject (readonly)

Returns the value of attribute jitter_proc.



14
15
16
# File 'lib/source_monitor/fetching/feed_fetcher/adaptive_interval.rb', line 14

def jitter_proc
  @jitter_proc
end

#sourceObject (readonly)

Returns the value of attribute source.



14
15
16
# File 'lib/source_monitor/fetching/feed_fetcher/adaptive_interval.rb', line 14

def source
  @source
end

Instance Method Details

#adjusted_interval_with_jitter(interval_seconds) ⇒ Object



54
55
56
57
58
59
# File 'lib/source_monitor/fetching/feed_fetcher/adaptive_interval.rb', line 54

def adjusted_interval_with_jitter(interval_seconds)
  jitter = jitter_offset(interval_seconds)
  adjusted = interval_seconds + jitter
  adjusted = min_fetch_interval_seconds if adjusted < min_fetch_interval_seconds
  adjusted
end

#apply_adaptive_interval!(attributes, content_changed:, failure: false) ⇒ Object



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/source_monitor/fetching/feed_fetcher/adaptive_interval.rb', line 21

def apply_adaptive_interval!(attributes, content_changed:, failure: false)
  if source.adaptive_fetching_enabled?
    interval_seconds = compute_next_interval_seconds(content_changed:, failure:)
    scheduled_time = Time.current + adjusted_interval_with_jitter(interval_seconds)
    scheduled_time = [ scheduled_time, source.backoff_until ].compact.max if source.backoff_until.present?

    attributes[:fetch_interval_minutes] = interval_minutes_for(interval_seconds)
    attributes[:next_fetch_at] = scheduled_time
    attributes[:backoff_until] = failure ? scheduled_time : nil
  else
    fixed_minutes = [ source.fetch_interval_minutes.to_i, 1 ].max
    fixed_seconds = fixed_minutes * 60.0
    attributes[:next_fetch_at] = Time.current + adjusted_interval_with_jitter(fixed_seconds)
    attributes[:backoff_until] = nil
  end
end

#compute_next_interval_seconds(content_changed:, failure:) ⇒ Object



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/source_monitor/fetching/feed_fetcher/adaptive_interval.rb', line 38

def compute_next_interval_seconds(content_changed:, failure:)
  current = [ current_interval_seconds, min_fetch_interval_seconds ].max

  next_interval = if failure
                    current * failure_increase_factor_value
  elsif content_changed
                    current * decrease_factor_value
  else
                    current * increase_factor_value
  end

  next_interval = min_fetch_interval_seconds if next_interval < min_fetch_interval_seconds
  next_interval = max_fetch_interval_seconds if next_interval > max_fetch_interval_seconds
  next_interval.to_f
end

#configured_non_negative(value, default) ⇒ Object



90
91
92
93
94
95
# File 'lib/source_monitor/fetching/feed_fetcher/adaptive_interval.rb', line 90

def configured_non_negative(value, default)
  number = extract_numeric(value)
  return default if number.nil?

  number.negative? ? 0.0 : number
end

#configured_positive(value, default) ⇒ Object



83
84
85
86
87
88
# File 'lib/source_monitor/fetching/feed_fetcher/adaptive_interval.rb', line 83

def configured_positive(value, default)
  number = extract_numeric(value)
  return default unless number && number.positive?

  number
end

#configured_seconds(minutes_value, default) ⇒ Object



76
77
78
79
80
81
# File 'lib/source_monitor/fetching/feed_fetcher/adaptive_interval.rb', line 76

def configured_seconds(minutes_value, default)
  minutes = extract_numeric(minutes_value)
  return default unless minutes && minutes.positive?

  minutes * 60.0
end

#extract_numeric(value) ⇒ Object



97
98
99
100
101
102
103
104
# File 'lib/source_monitor/fetching/feed_fetcher/adaptive_interval.rb', line 97

def extract_numeric(value)
  return value if value.is_a?(Numeric)
  return value.to_f if value.respond_to?(:to_f)

  nil
rescue StandardError
  nil
end

#interval_minutes_for(interval_seconds) ⇒ Object



71
72
73
74
# File 'lib/source_monitor/fetching/feed_fetcher/adaptive_interval.rb', line 71

def interval_minutes_for(interval_seconds)
  minutes = (interval_seconds / 60.0).round
  [ minutes, 1 ].max
end

#jitter_offset(interval_seconds) ⇒ Object



61
62
63
64
65
66
67
68
69
# File 'lib/source_monitor/fetching/feed_fetcher/adaptive_interval.rb', line 61

def jitter_offset(interval_seconds)
  return 0 if interval_seconds <= 0
  return jitter_proc.call(interval_seconds) if jitter_proc.respond_to?(:call)

  jitter_range = interval_seconds * jitter_percent_value
  return 0 if jitter_range <= 0

  ((rand * 2) - 1) * jitter_range
end