super_spreader 0.1.0.beta2 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- metadata +8 -306
- data/.circleci/config.yml +0 -145
- data/.github/CODEOWNERS +0 -3
- data/.gitignore +0 -15
- data/.rspec +0 -3
- data/.ruby-version +0 -1
- data/.travis.yml +0 -7
- data/CHANGELOG.md +0 -6
- data/CONTRIBUTING.md +0 -30
- data/Gemfile +0 -6
- data/Gemfile.lock +0 -196
- data/Guardfile +0 -18
- data/LICENSE.txt +0 -13
- data/README.md +0 -240
- data/Rakefile +0 -35
- data/bin/console +0 -14
- data/bin/setup +0 -7
- data/lib/super_spreader/batch_helper.rb +0 -40
- data/lib/super_spreader/peak_schedule.rb +0 -22
- data/lib/super_spreader/redis_model.rb +0 -51
- data/lib/super_spreader/scheduler_config.rb +0 -79
- data/lib/super_spreader/scheduler_job.rb +0 -43
- data/lib/super_spreader/spread_tracker.rb +0 -39
- data/lib/super_spreader/spreader.rb +0 -61
- data/lib/super_spreader/stop_signal.rb +0 -29
- data/lib/super_spreader/version.rb +0 -5
- data/lib/super_spreader.rb +0 -20
- data/super_spreader.gemspec +0 -46
@@ -1,79 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "super_spreader/peak_schedule"
|
4
|
-
require "super_spreader/redis_model"
|
5
|
-
|
6
|
-
module SuperSpreader
|
7
|
-
class SchedulerConfig < RedisModel
|
8
|
-
# The job class to enqueue on each run of the scheduler.
|
9
|
-
attribute :job_class_name, :string
|
10
|
-
# The number of records to process in each invocation of the job class.
|
11
|
-
attribute :batch_size, :integer
|
12
|
-
# The amount of work to enqueue, in seconds.
|
13
|
-
attribute :duration, :integer
|
14
|
-
|
15
|
-
# The number of jobs to enqueue per second, allowing for fractional amounts
|
16
|
-
# such as 1 job every other second using `0.5`.
|
17
|
-
attribute :per_second_on_peak, :float
|
18
|
-
# The same as per_second_on_peak, but for times that are not identified as
|
19
|
-
# on-peak.
|
20
|
-
attribute :per_second_off_peak, :float
|
21
|
-
|
22
|
-
# This section manages the definition "on peak." Compare this terminology
|
23
|
-
# to bus or train schedules.
|
24
|
-
|
25
|
-
# The timezone to use for time calculations.
|
26
|
-
#
|
27
|
-
# Example: "America/Los_Angeles" for Pacific time
|
28
|
-
attribute :on_peak_timezone, :string
|
29
|
-
# The 24-hour hour on which on-peak application usage starts.
|
30
|
-
#
|
31
|
-
# Example: 5 for 5 AM
|
32
|
-
attribute :on_peak_hour_begin, :integer
|
33
|
-
# The 24-hour hour on which on-peak application usage ends.
|
34
|
-
#
|
35
|
-
# Example: 17 for 5 PM
|
36
|
-
attribute :on_peak_hour_end, :integer
|
37
|
-
# The wday value on which on-peak application usage starts.
|
38
|
-
#
|
39
|
-
# Example: 1 for Monday
|
40
|
-
attribute :on_peak_wday_begin, :integer
|
41
|
-
# The wday value on which on-peak application usage ends.
|
42
|
-
#
|
43
|
-
# Example: 5 for Friday
|
44
|
-
attribute :on_peak_wday_end, :integer
|
45
|
-
|
46
|
-
attr_writer :schedule
|
47
|
-
|
48
|
-
def job_class
|
49
|
-
job_class_name.constantize
|
50
|
-
end
|
51
|
-
|
52
|
-
def super_spreader_config
|
53
|
-
[job_class, job_class.super_spreader_model_class]
|
54
|
-
end
|
55
|
-
|
56
|
-
def spread_options
|
57
|
-
{
|
58
|
-
batch_size: batch_size,
|
59
|
-
duration: duration,
|
60
|
-
per_second: per_second
|
61
|
-
}
|
62
|
-
end
|
63
|
-
|
64
|
-
def per_second
|
65
|
-
schedule.on_peak? ? per_second_on_peak : per_second_off_peak
|
66
|
-
end
|
67
|
-
|
68
|
-
private
|
69
|
-
|
70
|
-
def schedule
|
71
|
-
@schedule ||=
|
72
|
-
PeakSchedule.new(
|
73
|
-
on_peak_wday_range: on_peak_wday_begin..on_peak_wday_end,
|
74
|
-
on_peak_hour_range: on_peak_hour_begin..on_peak_hour_end,
|
75
|
-
timezone: on_peak_timezone
|
76
|
-
)
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
@@ -1,43 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "active_job"
|
4
|
-
require "json"
|
5
|
-
require "super_spreader/scheduler_config"
|
6
|
-
require "super_spreader/spreader"
|
7
|
-
require "super_spreader/stop_signal"
|
8
|
-
|
9
|
-
module SuperSpreader
|
10
|
-
class SchedulerJob < ActiveJob::Base
|
11
|
-
extend StopSignal
|
12
|
-
|
13
|
-
def perform
|
14
|
-
return if self.class.stopped?
|
15
|
-
|
16
|
-
log(started_at: Time.current.iso8601)
|
17
|
-
log(config.serializable_hash)
|
18
|
-
|
19
|
-
super_spreader = Spreader.new(*config.super_spreader_config)
|
20
|
-
next_id = super_spreader.enqueue_spread(**config.spread_options)
|
21
|
-
log(next_id: next_id)
|
22
|
-
|
23
|
-
return if next_id.zero?
|
24
|
-
|
25
|
-
self.class.set(wait_until: next_run_at).perform_later
|
26
|
-
log(next_run_at: next_run_at.iso8601)
|
27
|
-
end
|
28
|
-
|
29
|
-
def next_run_at
|
30
|
-
config.duration.seconds.from_now
|
31
|
-
end
|
32
|
-
|
33
|
-
def config
|
34
|
-
@config ||= SchedulerConfig.new
|
35
|
-
end
|
36
|
-
|
37
|
-
private
|
38
|
-
|
39
|
-
def log(hash)
|
40
|
-
SuperSpreader.logger.info({subject: self.class.name}.merge(hash).to_json)
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
@@ -1,39 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "active_record"
|
4
|
-
require "redis"
|
5
|
-
|
6
|
-
module SuperSpreader
|
7
|
-
class SpreadTracker
|
8
|
-
def initialize(job_class, model_class)
|
9
|
-
@job_class = job_class
|
10
|
-
@model_class = model_class
|
11
|
-
end
|
12
|
-
|
13
|
-
def initial_id
|
14
|
-
redis_value = redis.hget(initial_id_key, @model_class.name)
|
15
|
-
|
16
|
-
value = redis_value || @model_class.maximum(:id)
|
17
|
-
|
18
|
-
value.to_i
|
19
|
-
end
|
20
|
-
|
21
|
-
def initial_id=(value)
|
22
|
-
if value.nil?
|
23
|
-
redis.hdel(initial_id_key, @model_class.name)
|
24
|
-
else
|
25
|
-
redis.hset(initial_id_key, @model_class.name, value)
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
private
|
30
|
-
|
31
|
-
def redis
|
32
|
-
SuperSpreader.redis
|
33
|
-
end
|
34
|
-
|
35
|
-
def initial_id_key
|
36
|
-
"#{@job_class.name}:initial_id"
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
@@ -1,61 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "super_spreader/spread_tracker"
|
4
|
-
|
5
|
-
module SuperSpreader
|
6
|
-
class Spreader
|
7
|
-
def initialize(job_class, model_class, spread_tracker: nil)
|
8
|
-
@job_class = job_class
|
9
|
-
@model_class = model_class
|
10
|
-
@spread_tracker = spread_tracker || SpreadTracker.new(job_class, model_class)
|
11
|
-
end
|
12
|
-
|
13
|
-
def spread(batch_size:, duration:, per_second:, initial_id:, begin_at: Time.now.utc)
|
14
|
-
end_id = initial_id
|
15
|
-
segment_duration = 1.0 / per_second
|
16
|
-
time_index = 0.0
|
17
|
-
batches = []
|
18
|
-
|
19
|
-
while time_index < duration
|
20
|
-
break if end_id <= 0
|
21
|
-
|
22
|
-
# Use floor to prevent subsecond times
|
23
|
-
run_at = begin_at + time_index.floor
|
24
|
-
begin_id = clamp(end_id - batch_size + 1)
|
25
|
-
batches << {run_at: run_at, begin_id: begin_id, end_id: end_id}
|
26
|
-
|
27
|
-
break if begin_id == 1
|
28
|
-
|
29
|
-
end_id = begin_id - 1
|
30
|
-
time_index += segment_duration
|
31
|
-
end
|
32
|
-
|
33
|
-
batches
|
34
|
-
end
|
35
|
-
|
36
|
-
def enqueue_spread(**opts)
|
37
|
-
initial_id = @spread_tracker.initial_id
|
38
|
-
return 0 if initial_id.zero?
|
39
|
-
|
40
|
-
batches = spread(**opts.merge(initial_id: initial_id))
|
41
|
-
|
42
|
-
batches.each do |batch|
|
43
|
-
@job_class
|
44
|
-
.set(wait_until: batch[:run_at])
|
45
|
-
.perform_later(batch[:begin_id], batch[:end_id])
|
46
|
-
end
|
47
|
-
|
48
|
-
last_begin_id = batches.last[:begin_id]
|
49
|
-
next_id = last_begin_id - 1
|
50
|
-
@spread_tracker.initial_id = next_id
|
51
|
-
|
52
|
-
next_id
|
53
|
-
end
|
54
|
-
|
55
|
-
private
|
56
|
-
|
57
|
-
def clamp(value)
|
58
|
-
(value <= 0) ? 1 : value
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
@@ -1,29 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "redis"
|
4
|
-
|
5
|
-
module SuperSpreader
|
6
|
-
module StopSignal
|
7
|
-
def stop!
|
8
|
-
redis.set(stop_key, true)
|
9
|
-
end
|
10
|
-
|
11
|
-
def go!
|
12
|
-
redis.del(stop_key)
|
13
|
-
end
|
14
|
-
|
15
|
-
def stopped?
|
16
|
-
redis.exists(stop_key).positive?
|
17
|
-
end
|
18
|
-
|
19
|
-
private
|
20
|
-
|
21
|
-
def redis
|
22
|
-
SuperSpreader.redis
|
23
|
-
end
|
24
|
-
|
25
|
-
def stop_key
|
26
|
-
"#{name}:stop"
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
data/lib/super_spreader.rb
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "super_spreader/version"
|
4
|
-
|
5
|
-
require "super_spreader/batch_helper"
|
6
|
-
require "super_spreader/peak_schedule"
|
7
|
-
require "super_spreader/redis_model"
|
8
|
-
require "super_spreader/scheduler_config"
|
9
|
-
require "super_spreader/scheduler_job"
|
10
|
-
require "super_spreader/spread_tracker"
|
11
|
-
require "super_spreader/spreader"
|
12
|
-
require "super_spreader/stop_signal"
|
13
|
-
|
14
|
-
module SuperSpreader
|
15
|
-
class Error < StandardError; end
|
16
|
-
|
17
|
-
class << self
|
18
|
-
attr_accessor :logger, :redis
|
19
|
-
end
|
20
|
-
end
|
data/super_spreader.gemspec
DELETED
@@ -1,46 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
lib = File.expand_path("../lib", __FILE__)
|
4
|
-
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
-
require "super_spreader/version"
|
6
|
-
|
7
|
-
Gem::Specification.new do |spec|
|
8
|
-
spec.name = "super_spreader"
|
9
|
-
spec.version = SuperSpreader::VERSION
|
10
|
-
spec.authors = ["Benjamin Oakes"]
|
11
|
-
spec.email = ["boakes@doximity.com"]
|
12
|
-
|
13
|
-
spec.summary = "ActiveJob-based backfill orchestration library"
|
14
|
-
spec.description = "Provides tools for managing resource-efficient backfills of large datasets via ActiveJob"
|
15
|
-
spec.homepage = "https://github.com/doximity/super_spreader"
|
16
|
-
|
17
|
-
spec.metadata["homepage_uri"] = spec.homepage
|
18
|
-
spec.metadata["source_code_uri"] = spec.homepage
|
19
|
-
spec.metadata["changelog_uri"] = "https://github.com/doximity/super_spreader/blob/main/CHANGELOG.md"
|
20
|
-
|
21
|
-
# Specify which files should be added to the gem when it is released.
|
22
|
-
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
23
|
-
spec.files = Dir.chdir(File.expand_path("..", __FILE__)) do
|
24
|
-
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
25
|
-
end
|
26
|
-
spec.executables = []
|
27
|
-
spec.require_paths = ["lib"]
|
28
|
-
|
29
|
-
spec.add_dependency "activejob", ">= 6.1", "< 8.0"
|
30
|
-
spec.add_dependency "activemodel", ">= 6.1", "< 8.0"
|
31
|
-
spec.add_dependency "activerecord", ">= 6.1", "< 8.0"
|
32
|
-
spec.add_dependency "activesupport", ">= 6.1", "< 8.0"
|
33
|
-
spec.add_dependency "redis", ">= 4.8", "< 6.0"
|
34
|
-
spec.add_development_dependency "bundler"
|
35
|
-
spec.add_development_dependency "factory_bot"
|
36
|
-
spec.add_development_dependency "guard"
|
37
|
-
spec.add_development_dependency "guard-rspec"
|
38
|
-
spec.add_development_dependency "pry"
|
39
|
-
spec.add_development_dependency "rake"
|
40
|
-
spec.add_development_dependency "rspec"
|
41
|
-
spec.add_development_dependency "rspec-rails"
|
42
|
-
spec.add_development_dependency "rspec_junit_formatter"
|
43
|
-
spec.add_development_dependency "sqlite3"
|
44
|
-
spec.add_development_dependency "standard"
|
45
|
-
spec.add_development_dependency "yard"
|
46
|
-
end
|