chained_job 0.2.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 +7 -0
- data/lib/chained_job.rb +28 -0
- data/lib/chained_job/clean_up_queue.rb +45 -0
- data/lib/chained_job/config.rb +33 -0
- data/lib/chained_job/helpers.rb +19 -0
- data/lib/chained_job/middleware.rb +28 -0
- data/lib/chained_job/process.rb +56 -0
- data/lib/chained_job/start_chains.rb +62 -0
- data/lib/chained_job/store_job_arguments.rb +51 -0
- data/lib/chained_job/version.rb +5 -0
- metadata +111 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 21a653ae3a0eaaf596d741a3208b4aee1a7d297700e1d506c7005bf7cd529618
|
4
|
+
data.tar.gz: 03ab75c95d0610a9f192d3f6f7bfc7eeac516f641b4bbc32730ac45d1710f3bc
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: fb6be468a09e6824fb2dd21fd8aa8029790a63c145d9f5048b5c29dea2632e85e6b6ebcfb29558e7504982a4ad48261140e6b96b3b8e4ef55726d58a18ed58c0
|
7
|
+
data.tar.gz: 75a1aefe58b764b69d05514bfa47209ec4ff9d3f743379301841d132e15fc60c34f6254e0fae7dfacbca393c973f4ad2809dffcfb5a7041a42e62d3155a95857
|
data/lib/chained_job.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'chained_job/config'
|
4
|
+
require 'chained_job/middleware'
|
5
|
+
require 'chained_job/version'
|
6
|
+
|
7
|
+
module ChainedJob
|
8
|
+
class Error < StandardError; end
|
9
|
+
class ConfigurationError < Error; end
|
10
|
+
|
11
|
+
module_function
|
12
|
+
|
13
|
+
def redis
|
14
|
+
config.redis || raise(ConfigurationError, 'Redis is not configured')
|
15
|
+
end
|
16
|
+
|
17
|
+
def logger
|
18
|
+
config.logger
|
19
|
+
end
|
20
|
+
|
21
|
+
def config
|
22
|
+
@config ||= ChainedJob::Config.new
|
23
|
+
end
|
24
|
+
|
25
|
+
def configure
|
26
|
+
yield(config)
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'chained_job/helpers'
|
4
|
+
|
5
|
+
module ChainedJob
|
6
|
+
class CleanUpQueue
|
7
|
+
def self.run(job_class)
|
8
|
+
new(job_class).run
|
9
|
+
end
|
10
|
+
|
11
|
+
TRIM_STEP_SIZE = 1_000
|
12
|
+
|
13
|
+
attr_reader :job_class
|
14
|
+
|
15
|
+
def initialize(job_class)
|
16
|
+
@job_class = job_class
|
17
|
+
end
|
18
|
+
|
19
|
+
# rubocop:disable Metrics/AbcSize
|
20
|
+
def run
|
21
|
+
loop do
|
22
|
+
tag = ChainedJob.redis.spop(tag_list)
|
23
|
+
|
24
|
+
break unless tag
|
25
|
+
|
26
|
+
redis_key = Helpers.redis_key(job_key, tag)
|
27
|
+
size = ChainedJob.redis.llen(redis_key)
|
28
|
+
(size / TRIM_STEP_SIZE).times { ChainedJob.redis.ltrim(redis_key, 0, -TRIM_STEP_SIZE) }
|
29
|
+
|
30
|
+
ChainedJob.redis.del(redis_key)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
# rubocop:enable Metrics/AbcSize
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def tag_list
|
38
|
+
@tag_list ||= Helpers.tag_list(job_key)
|
39
|
+
end
|
40
|
+
|
41
|
+
def job_key
|
42
|
+
@job_key ||= Helpers.job_key(job_class)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
module ChainedJob
|
6
|
+
class Config
|
7
|
+
DEFAULT_ARGUMENTS_BATCH_SIZE = 1_000
|
8
|
+
DEFAULT_ARGUMENTS_QUEUE_EXPIRATION = 7 * 24 * 60 * 60 # 7 days
|
9
|
+
|
10
|
+
attr_accessor(
|
11
|
+
:arguments_batch_size,
|
12
|
+
:arguments_queue_expiration,
|
13
|
+
:around_start_chains,
|
14
|
+
:around_chain_process,
|
15
|
+
:debug,
|
16
|
+
:logger,
|
17
|
+
:redis,
|
18
|
+
:queue,
|
19
|
+
)
|
20
|
+
|
21
|
+
def initialize
|
22
|
+
self.arguments_batch_size = DEFAULT_ARGUMENTS_BATCH_SIZE
|
23
|
+
self.arguments_queue_expiration = DEFAULT_ARGUMENTS_QUEUE_EXPIRATION
|
24
|
+
|
25
|
+
self.logger = ::Logger.new(STDOUT)
|
26
|
+
|
27
|
+
self.around_start_chains = ->(_options, &block) { block.call }
|
28
|
+
self.around_chain_process = ->(_options, &block) { block.call }
|
29
|
+
|
30
|
+
self.debug = true
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ChainedJob
|
4
|
+
module Helpers
|
5
|
+
module_function
|
6
|
+
|
7
|
+
def job_key(job_class)
|
8
|
+
"chained_job:#{job_class}"
|
9
|
+
end
|
10
|
+
|
11
|
+
def redis_key(job_key, tag)
|
12
|
+
"#{job_key}:#{tag}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def tag_list(prefix)
|
16
|
+
"#{prefix}:tags"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'chained_job/start_chains'
|
4
|
+
require 'chained_job/process'
|
5
|
+
|
6
|
+
module ChainedJob
|
7
|
+
module Middleware
|
8
|
+
def self.included(base)
|
9
|
+
base.queue_as ChainedJob.config.queue if ChainedJob.config.queue
|
10
|
+
end
|
11
|
+
|
12
|
+
def perform(worker_id = nil, tag = nil)
|
13
|
+
if worker_id
|
14
|
+
ChainedJob::Process.run(self, worker_id, tag)
|
15
|
+
else
|
16
|
+
ChainedJob::StartChains.run(self.class, array_of_job_arguments, parallelism)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def array_of_job_arguments
|
21
|
+
raise NoMethodError, 'undefined method array_of_job_arguments'
|
22
|
+
end
|
23
|
+
|
24
|
+
def parallelism
|
25
|
+
raise NoMethodError, 'undefined method parallelism'
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'chained_job/helpers'
|
4
|
+
|
5
|
+
module ChainedJob
|
6
|
+
class Process
|
7
|
+
def self.run(job_instance, worker_id, job_tag)
|
8
|
+
new(job_instance, worker_id, job_tag).run
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :job_instance, :worker_id, :job_tag
|
12
|
+
|
13
|
+
def initialize(job_instance, worker_id, job_tag)
|
14
|
+
@job_instance = job_instance
|
15
|
+
@worker_id = worker_id
|
16
|
+
@job_tag = job_tag
|
17
|
+
end
|
18
|
+
|
19
|
+
def run
|
20
|
+
with_hooks do
|
21
|
+
return log_finished_worker unless argument
|
22
|
+
|
23
|
+
job_instance.process(argument)
|
24
|
+
job_instance.class.perform_later(worker_id, job_tag)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def with_hooks
|
31
|
+
ChainedJob.config.around_chain_process.call(options) { yield }
|
32
|
+
end
|
33
|
+
|
34
|
+
def options
|
35
|
+
{ job_class: job_instance.class, worker_id: worker_id }
|
36
|
+
end
|
37
|
+
|
38
|
+
def log_finished_worker
|
39
|
+
ChainedJob.logger.info(
|
40
|
+
"#{job_instance.class}:#{job_tag} worker #{worker_id} finished"
|
41
|
+
)
|
42
|
+
end
|
43
|
+
|
44
|
+
def argument
|
45
|
+
@argument ||= ChainedJob.redis.lpop(redis_key)
|
46
|
+
end
|
47
|
+
|
48
|
+
def redis_key
|
49
|
+
Helpers.redis_key(job_key, job_tag)
|
50
|
+
end
|
51
|
+
|
52
|
+
def job_key
|
53
|
+
Helpers.job_key(job_instance.class)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'chained_job/helpers'
|
4
|
+
require 'chained_job/clean_up_queue'
|
5
|
+
require 'chained_job/store_job_arguments'
|
6
|
+
|
7
|
+
module ChainedJob
|
8
|
+
class StartChains
|
9
|
+
def self.run(job_class, array_of_job_arguments, parallelism)
|
10
|
+
new(job_class, array_of_job_arguments, parallelism).run
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :job_class, :array_of_job_arguments, :parallelism
|
14
|
+
|
15
|
+
def initialize(job_class, array_of_job_arguments, parallelism)
|
16
|
+
@job_class = job_class
|
17
|
+
@array_of_job_arguments = array_of_job_arguments
|
18
|
+
@parallelism = parallelism
|
19
|
+
end
|
20
|
+
|
21
|
+
# rubocop:disable Metrics/AbcSize
|
22
|
+
def run
|
23
|
+
with_hooks do
|
24
|
+
ChainedJob::CleanUpQueue.run(job_class)
|
25
|
+
|
26
|
+
next unless array_of_job_arguments.count.positive?
|
27
|
+
|
28
|
+
ChainedJob::StoreJobArguments.run(job_class, job_tag, array_of_job_arguments)
|
29
|
+
|
30
|
+
log_chained_job_start
|
31
|
+
|
32
|
+
parallelism.times { |worked_id| job_class.perform_later(worked_id, job_tag) }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
# rubocop:enable Metrics/AbcSize
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def with_hooks
|
40
|
+
ChainedJob.config.around_start_chains.call(options) { yield }
|
41
|
+
end
|
42
|
+
|
43
|
+
def options
|
44
|
+
{
|
45
|
+
job_class: job_class,
|
46
|
+
array_of_job_arguments: array_of_job_arguments,
|
47
|
+
parallelism: parallelism,
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
def job_tag
|
52
|
+
@job_tag ||= Time.now.to_f.to_s
|
53
|
+
end
|
54
|
+
|
55
|
+
def log_chained_job_start
|
56
|
+
ChainedJob.logger.info(
|
57
|
+
"#{job_class}:#{job_tag} starting #{parallelism} workers "\
|
58
|
+
"processing #{array_of_job_arguments.count} items"
|
59
|
+
)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'chained_job/helpers'
|
4
|
+
|
5
|
+
module ChainedJob
|
6
|
+
class StoreJobArguments
|
7
|
+
def self.run(job_class, job_tag, array_of_job_arguments)
|
8
|
+
new(job_class, job_tag, array_of_job_arguments).run
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :job_class, :job_tag, :array_of_job_arguments
|
12
|
+
|
13
|
+
def initialize(job_class, job_tag, array_of_job_arguments)
|
14
|
+
@job_class = job_class
|
15
|
+
@job_tag = job_tag
|
16
|
+
@array_of_job_arguments = array_of_job_arguments
|
17
|
+
end
|
18
|
+
|
19
|
+
def run
|
20
|
+
set_tag_list
|
21
|
+
|
22
|
+
array_of_job_arguments.each_slice(config.arguments_batch_size) do |sublist|
|
23
|
+
ChainedJob.redis.rpush(redis_key, sublist)
|
24
|
+
end
|
25
|
+
|
26
|
+
ChainedJob.redis.expire(redis_key, config.arguments_queue_expiration)
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def set_tag_list
|
32
|
+
ChainedJob.redis.sadd(tag_list, job_tag)
|
33
|
+
end
|
34
|
+
|
35
|
+
def tag_list
|
36
|
+
Helpers.tag_list(job_key)
|
37
|
+
end
|
38
|
+
|
39
|
+
def redis_key
|
40
|
+
@redis_key ||= Helpers.redis_key(job_key, job_tag)
|
41
|
+
end
|
42
|
+
|
43
|
+
def job_key
|
44
|
+
@job_key ||= Helpers.job_key(job_class)
|
45
|
+
end
|
46
|
+
|
47
|
+
def config
|
48
|
+
ChainedJob.config
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
metadata
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: chained_job
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Mantas Kūjalis
|
8
|
+
- Titas Norkūnas
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2020-07-22 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '1.17'
|
21
|
+
type: :development
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - "~>"
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '1.17'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: minitest
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - "~>"
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '5.0'
|
35
|
+
type: :development
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - "~>"
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '5.0'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: rake
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - "~>"
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '12.0'
|
49
|
+
type: :development
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - "~>"
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '12.0'
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: rubocop-vinted
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - "~>"
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0.3'
|
63
|
+
type: :development
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - "~>"
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0.3'
|
70
|
+
description: Chained job allows you to define an array of queued jobs that should
|
71
|
+
be run in sequence after the main job has been executed successfully.
|
72
|
+
email:
|
73
|
+
- mantas.kujalis@vinted.com
|
74
|
+
- titas@vinted.com
|
75
|
+
executables: []
|
76
|
+
extensions: []
|
77
|
+
extra_rdoc_files: []
|
78
|
+
files:
|
79
|
+
- lib/chained_job.rb
|
80
|
+
- lib/chained_job/clean_up_queue.rb
|
81
|
+
- lib/chained_job/config.rb
|
82
|
+
- lib/chained_job/helpers.rb
|
83
|
+
- lib/chained_job/middleware.rb
|
84
|
+
- lib/chained_job/process.rb
|
85
|
+
- lib/chained_job/start_chains.rb
|
86
|
+
- lib/chained_job/store_job_arguments.rb
|
87
|
+
- lib/chained_job/version.rb
|
88
|
+
homepage: https://github.com/vinted/chained_job
|
89
|
+
licenses:
|
90
|
+
- MIT
|
91
|
+
metadata: {}
|
92
|
+
post_install_message:
|
93
|
+
rdoc_options: []
|
94
|
+
require_paths:
|
95
|
+
- lib
|
96
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '0'
|
101
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
102
|
+
requirements:
|
103
|
+
- - ">="
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '0'
|
106
|
+
requirements: []
|
107
|
+
rubygems_version: 3.0.3
|
108
|
+
signing_key:
|
109
|
+
specification_version: 4
|
110
|
+
summary: Chained job helper
|
111
|
+
test_files: []
|