workers rails perform now job generate delayed_job_active_record active ruby-on-rails ruby-on-rails-3 backgroundworker push-notification delayed-job

ruby on rails - rails - sondeo con delayed_job



sidekiq perform now (6)

Comencemos con la API. Me gustaría tener algo como lo siguiente.

@available.working? # => true or false, so we know it''s running @available.finished? # => true or false, so we know it''s finished (already ran)

Ahora vamos a escribir el trabajo.

class AwesomeJob < Struct.new(:options) def perform do_something_with(options[:var]) end end

Hasta aquí todo bien. Tenemos un trabajo. Ahora vamos a escribir la lógica que lo enquista. Dado que Available es el modelo responsable de este trabajo, vamos a enseñarle cómo comenzar este trabajo.

class Available < ActiveRecord::Base def start_working! Delayed::Job.enqueue(AwesomeJob.new(options)) end def working? # not sure what to put here yet end def finished? # not sure what to put here yet end end

Entonces, ¿cómo sabemos si el trabajo está funcionando o no? Hay algunas maneras, pero en los rieles parece correcto que cuando mi modelo crea algo, generalmente se asocia con ese algo. ¿Cómo nos asociamos? Usando identificadores en la base de datos. job_id un job_id en el modelo Disponible.

Mientras estamos en eso, ¿cómo sabemos que el trabajo no está funcionando porque ya terminó, o porque aún no comenzó? Una forma es verificar realmente qué hizo realmente el trabajo. Si creó un archivo, verifique si el archivo existe. Si calculó un valor, verifique que el resultado esté escrito. Sin embargo, algunos trabajos no son tan fáciles de verificar, ya que es posible que no haya un resultado claro verificable de su trabajo. Para tal caso, puede usar una bandera o una marca de tiempo en su modelo. Suponiendo que este sea nuestro caso, agreguemos una marca de tiempo job_finished_at para distinguir un trabajo que aún no se ejecutó de uno ya terminado .

class AddJobIdToAvailable < ActiveRecord::Migration def self.up add_column :available, :job_id, :integer add_column :available, :job_finished_at, :datetime end def self.down remove_column :available, :job_id remove_column :available, :job_finished_at end end

Bien. Así que, en realidad, start_working! Available con su trabajo tan pronto como pongamos en cola el trabajo, modificando el start_working! método.

def start_working! job = Delayed::Job.enqueue(AwesomeJob.new(options)) update_attribute(:job_id, job.id) end

Estupendo. En este punto, podría haber escrito belongs_to :job , pero realmente no necesitamos eso.

¿Entonces ahora sabemos cómo escribir el working? método, tan fácil.

def working? job_id.present? end

¿Pero cómo marcamos el trabajo terminado? Nadie sabe que un trabajo ha terminado mejor que el trabajo en sí. Pasemos, por lo tanto, available_id al trabajo (como una de las opciones) y úselo en el trabajo. ¡Para eso tenemos que modificar el start_working! método para pasar la identificación.

def start_working! job = Delayed::Job.enqueue(AwesomeJob.new(options.merge(:available_id => id)) update_attribute(:job_id, job.id) end

Y deberíamos agregar la lógica en el trabajo para actualizar nuestra job_finished_at tiempo job_finished_at cuando esté lista.

class AwesomeJob < Struct.new(:options) def perform available = Available.find(options[:available_id]) do_something_with(options[:var]) # Depending on whether you consider an error''ed job to be finished # you may want to put this under an ensure. This way the job # will be deemed finished even if it error''ed out. available.update_attribute(:job_finished_at, Time.current) end end

Con este código en su lugar, ¿sabemos cómo escribir nuestro finished? método.

def finished? job_finished_at.present? end

Y terminamos. Ahora podemos simplemente sondear contra @available.working? y @available.finished? Además, obtiene la conveniencia de saber qué trabajo exacto se creó para su Disponible al marcar @available.job_id . Puede convertirlo fácilmente en una asociación real diciendo belongs_to :job .

Tengo un proceso que generalmente tarda unos segundos en completarse, así que estoy tratando de usar el método de retraso para manejarlo de forma asincrónica. El trabajo en sí funciona bien, mi pregunta es cómo hacer una encuesta sobre el trabajo para saber si está hecho.

Puedo obtener una identificación de delayyed_job simplemente asignándola a una variable:

job = Available.delay.dosomething (: var => 1234)

+------+----------+----------+------------+------------+-------------+-----------+-----------+-----------+------------+-------------+ | id | priority | attempts | handler | last_error | run_at | locked_at | failed_at | locked_by | created_at | updated_at | +------+----------+----------+------------+------------+-------------+-----------+-----------+-----------+------------+-------------+ | 4037 | 0 | 0 | --- !ru... | | 2011-04-... | | | | 2011-04... | 2011-04-... | +------+----------+----------+------------+------------+-------------+-----------+-----------+-----------+------------+-------------+

Pero tan pronto como completa el trabajo, lo elimina y la búsqueda del registro completado devuelve un error:

@job=Delayed::Job.find(4037) ActiveRecord::RecordNotFound: Couldn''t find Delayed::Backend::ActiveRecord::Job with ID=4037 @job= Delayed::Job.exists?(params[:id])

¿Debo molestarme en cambiar esto y quizás posponer la eliminación de registros completos? No estoy seguro de qué otra forma puedo recibir una notificación de su estado. O está sondeando un registro muerto como prueba de que está bien? ¿Alguien más enfrenta algo similar?


Creo que la mejor manera sería usar las devoluciones de llamada disponibles en el trabajo demorado. Estos son:: éxito,: error y: después. para que pueda poner un código en su modelo con el después:

class ToBeDelayed def perform # do something end def after(job) # do something end end

Porque si insiste en utilizar el método obj.delayed.thethod, tendrá que aplicar el parche monkey Delayed :: PerformableMethod y agregar allí el método after . En mi humilde opinión, es mucho mejor que la votación por algún valor que incluso podría ser específico para el back-end (ActiveRecord vs. Mongoid, por ejemplo).


El método más simple de lograr esto es cambiar su acción de sondeo para que sea similar a lo siguiente:

def poll @job = Delayed::Job.find_by_id(params[:job_id]) if @job.nil? # The job has completed and is no longer in the database. else if @job.last_error.nil? # The job is still in the queue and has not been run. else # The job has encountered an error. end end end

¿Por qué funciona esto? Cuando Delayed::Job ejecuta un trabajo desde la cola, lo elimina de la base de datos si tiene éxito . Si el trabajo falla, el registro permanece en la cola para volver a ejecutarse más tarde y el atributo last_error se establece en el error encontrado. Con las dos funciones anteriores, puede verificar los registros eliminados para ver si tuvieron éxito.

Los beneficios del método anterior son:

  • Obtienes el efecto de encuesta que estabas buscando en tu publicación original
  • Usando una rama de lógica simple, puede proporcionar comentarios al usuario si hay un error al procesar el trabajo

Puede encapsular esta funcionalidad en un método modelo haciendo algo como lo siguiente:

# Include this in your initializers somewhere class Queue < Delayed::Job def self.status(id) self.find_by_id(id).nil? ? "success" : (job.last_error.nil? ? "queued" : "failure") end end # Use this method in your poll method like so: def poll status = Queue.status(params[:id]) if status == "success" # Success, notify the user! elsif status == "failure" # Failure, notify the user! end end


La tabla delayed_jobs en su aplicación está destinada a proporcionar el estado de trabajos en ejecución y puestos en cola solamente. No es una tabla persistente, y realmente debe ser lo más pequeña posible por motivos de rendimiento. Es por eso que los trabajos se eliminan inmediatamente después de la finalización.

En su lugar, debe agregar campo a su modelo Available que indique que el trabajo está hecho. Como normalmente estoy interesado en cuánto tarda el trabajo en procesarse, agrego los campos start_time y end_time. Entonces mi método de dosomething sería algo como esto:

def self.dosomething(model_id) model = Model.find(model_id) begin model.start! # do some long work ... rescue Exception => e # ... ensure model.finish! end end

¡El comienzo! ¡y acaba! los métodos simplemente registran la hora actual y guardan el modelo. Entonces, ¿tendría una completed? método que su AJAX puede sondear para ver si el trabajo está terminado.

def completed? return true if start_time and end_time return false end

Hay muchas formas de hacerlo, pero considero que este método es simple y funciona bien para mí.


Sugeriría que, si es importante recibir una notificación de que el trabajo se ha completado, escriba un objeto de trabajo personalizado y haga cola en lugar de confiar en el trabajo predeterminado que se pone en cola cuando llama a Available.delay.dosomething . Crea un objeto como:

class DoSomethingAvailableJob attr_accessor options def initialize(options = {}) @options = options end def perform Available.dosomething(@options) # Do some sort of notification here # ... end end

y enquéue con:

Delayed::Job.enqueue DoSomethingAvailableJob.new(:var => 1234)


Terminé usando una combinación de Delayed_Job con una devolución de llamada posterior a (trabajo) que rellena un objeto memcached con el mismo ID que el trabajo creado. De esta forma, minimizo el número de veces que llegué a la base de datos para conocer el estado del trabajo, en lugar de eso, sondeé el objeto que se ha archivado. Y contiene el objeto completo que necesito del trabajo completo, por lo que ni siquiera tengo una solicitud de ida y vuelta. Obtuve la idea de un artículo de los chicos de Github que hicieron más o menos lo mismo.

https://github.com/blog/467-smart-js-polling

y usó un plugin jquery para el sondeo, que sondea con menos frecuencia, y se da por vencido después de un cierto número de intentos

https://github.com/jeremyw/jquery-smart-poll

Parece que funciona bien.

def after(job) prices = Room.prices.where("space_id = ? AND bookdate BETWEEN ? AND ?", space_id.to_i, date_from, date_to).to_a Rails.cache.fetch(job.id) do bed = Bed.new(:space_id => space_id, :date_from => date_from, :date_to => date_to, :prices => prices) end end