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.
@@ -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
@@ -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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ChainedJob
4
+ VERSION = '0.2.0'
5
+ 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: []