super_spreader 0.1.0.beta2 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|