chained_job 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []