Learn Active Job in Ruby on Rails
- Tram Ho
CONTENT
1. What is ActiveJob?
– ActiveJob is a framework in rails used to declare jobs and process them in queue order, is the frontend to declare jobs.
– These jobs can be anything from regularly scheduled cleaning, to billing, to emailing.
– Anything that can be broken down into small work units and run in parallel independently of the main thread can be queued for execution (backend)
2. What is a background job?
– BackgroundJob can be called background jobs, jobs that execute on a thread independent of the main thread of the web application.
– Normally, websites receive requests from users and return a response, but Background jobs are different
=> Starting from a request to the Website but requiring a longer execution time than usual (it can be thought that these requests cannot be processed immediately), we will need to use the Background job
=> Process asynchronously on a separate thread, returning the response to the user.
3. What are the ways to handle background jobs?
– There are 10 ways to handle in the table below:
Async | Queues | Delayed | Priorities | Timeout | Retries | |
---|---|---|---|---|---|---|
Backburner | Yes | Yes | Yes | Yes | Job | Global |
Delayed Job | Yes | Yes | Yes | Job | Global | Global |
Que | Yes | Yes | Yes | Job | No | Job |
queue_classic | Yes | Yes | Yes* | No | No | No |
Resque | Yes | Yes | Yes (Gem) | Queue | Global | Yes |
Sidekiq | Yes | Yes | Yes | Queue | No | Job |
Sneakers | Yes | Yes | No | Queue | Queue | No |
Sucker Punch | Yes | Yes | Yes | No | No | No |
Active Job Async | Yes | Yes | Yes | No | No | No |
Active Job Inline | No | Yes | N/A | N/A | N/A | N/A |
note:
–Async: Asynchronous handling.
–Queues: Queue.
–Delayed: Add execution request after perform_later example.
–Priorities: The order of jobs can change in the queue.
–Timeout: Expiration time
–Retries: Retry failed jobs
– By default, Rails provides us with Active job (1 framework to handle Background jobs) built-in queue process (built-in queue).
– Processes in the queue are saved to RAM, so if the server is shut down, the jobs that are not or are in progress will be lost, which can negatively affect our users and services, so it only likes Suitable for small applications or non-critical jobs.
4. How to Create, Configure, Use ActiveJob?
First we have to create a job that you want to use:
1 2 3 4 5 | rails g job <span class="token constant">SendMail</span> invoke test_unit create test<span class="token operator">/</span>jobs<span class="token operator">/</span>send_mail_job_test<span class="token punctuation">.</span>rb create app<span class="token operator">/</span>jobs<span class="token operator">/</span>send_mail_job<span class="token punctuation">.</span>rb |
1 2 3 4 5 6 7 8 9 | <span class="token keyword">class</span> <span class="token class-name">SendMailJob</span> <span class="token operator"><</span> <span class="token constant">ApplicationJob</span> queue_as <span class="token symbol">:default</span> <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">perform</span></span><span class="token punctuation">(</span><span class="token operator">*</span>args<span class="token punctuation">)</span> <span class="token comment"># Do something later</span> <span class="token keyword">end</span> <span class="token keyword">end</span> |
With Active job you can send as many parameters as you want, including a complex object like:
- Basic types (NilClass, String, Integer, Float, BigDecimal, TrueClass, FalseClass)
- Symbol / Array
- Date / Time / DateTime
- ActiveSupport::TimeWithZone / ActiveSupport::Duration /Hash (Keys should be of String or Symbol type)…
config :
Default active job uses queue :default (:async) to save the job, there is also another adapter :inline, To use it, we must configure the following:
config.active_job.queue_adapter = :inline
=> job will execute immediately in main thread
config.active_job.queue_adapter = :async
=> job will be executed in another thread pool, which is the default queue adapter, suitable for dev/test environments because it doesn’t need external infrastructure, but it’s not suitable in production environments, as it will eliminate quit pending jobs on reboot.
We can configure the number of active threads as follows:
1 2 3 4 5 | config<span class="token punctuation">.</span>active_job<span class="token punctuation">.</span>queue_adapter <span class="token operator">=</span> <span class="token constant">ActiveJob</span><span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token constant">QueueAdapters</span><span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token constant">AsyncAdapter</span><span class="token punctuation">.</span><span class="token keyword">new</span> min_threads<span class="token punctuation">:</span> <span class="token number">1</span><span class="token punctuation">,</span> max_threads<span class="token punctuation">:</span> <span class="token number">2</span> <span class="token operator">*</span> <span class="token constant">Concurrent</span><span class="token punctuation">.</span>processor_count<span class="token punctuation">,</span> idletime<span class="token punctuation">:</span> <span class="token number">600.</span>seconds |
5. Queue
We can push background work to specific queue and check priority
1 2 3 4 5 | <span class="token keyword">class</span> <span class="token class-name">GuestsCleanupJob</span> <span class="token operator"><</span> <span class="token constant">ApplicationJob</span> queue_as <span class="token symbol">:low_priority</span> <span class="token comment"># ...</span> <span class="token keyword">end</span> |
You can preset queue names for all your jobs using:
config.active_job.queue_name_prefix
in file application.rb:
1 2 3 4 5 6 7 | <span class="token comment"># config/application.rb</span> <span class="token keyword">module</span> <span class="token constant">YourApp</span> <span class="token keyword">class</span> <span class="token class-name">Application</span> <span class="token operator"><</span> <span class="token constant">Rails</span><span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token constant">Application</span> config<span class="token punctuation">.</span>active_job<span class="token punctuation">.</span>queue_name_prefix <span class="token operator">=</span> <span class="token constant">Rails</span><span class="token punctuation">.</span>env <span class="token keyword">end</span> <span class="token keyword">end</span> |
1 2 3 4 5 6 | <span class="token comment"># app/jobs/guests_cleanup_job.rb</span> <span class="token keyword">class</span> <span class="token class-name">GuestsCleanupJob</span> <span class="token operator"><</span> <span class="token constant">ApplicationJob</span> queue_as <span class="token symbol">:low_priority</span> <span class="token comment"># ...</span> <span class="token keyword">end</span> |
– You can configure the queue in the job as follows:
1 2 3 4 5 6 | <span class="token keyword">class</span> <span class="token class-name">GuestsCleanupJob</span> <span class="token operator"><</span> <span class="token constant">ApplicationJob</span> queue_as <span class="token symbol">:low_priority</span> <span class="token keyword">self</span><span class="token punctuation">.</span>queue_name_prefix <span class="token operator">=</span> <span class="token keyword">nil</span> <span class="token comment"># ...</span> <span class="token keyword">end</span> |
– You can also configure when calling the job:
1 2 | <span class="token constant">MyJob</span><span class="token punctuation">.</span>set<span class="token punctuation">(</span>queue<span class="token punctuation">:</span> <span class="token symbol">:another_queue</span><span class="token punctuation">)</span><span class="token punctuation">.</span>perform_later<span class="token punctuation">(</span>record<span class="token punctuation">)</span> |
6. Callback in ActiveJob?
Similar to ActiveRecord, Active Job also uses callbacks to interfere with the lifecycle of a job
1 2 3 4 5 6 7 | before_enqueue around_enqueue after_enqueue before_perform around_perform after_perform |
7. Handling exceptions, Retry or Discard failed Job ?
1 2 3 4 5 6 7 8 9 10 11 | <span class="token keyword">class</span> <span class="token class-name">RemoteServiceJob</span> <span class="token operator"><</span> <span class="token constant">ApplicationJob</span> retry_on <span class="token constant">CustomAppException</span> <span class="token comment"># defaults to 3s wait, 5 attempts</span> discard_on <span class="token constant">ActiveJob</span><span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token constant">DeserializationError</span> <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">perform</span></span><span class="token punctuation">(</span><span class="token operator">*</span>args<span class="token punctuation">)</span> <span class="token comment"># Might raise CustomAppException or ActiveJob::DeserializationError</span> <span class="token keyword">end</span> <span class="token keyword">end</span> |
– Default Job failed won’t be retry that job will be discard always
– In case you want retry or discard for the purpose then we raise exception then use retryon exception to retry default wait 3s and try again 5 times
– Discardon exception to delete the job