Uso de apio con Django para el procesamiento de tareas en segundo plano

Las aplicaciones web generalmente comienzan siendo simples pero pueden llegar a ser bastante complejas, y la mayoría de ellas exceden rápidamente la responsabilidad de responder solo a las solicitudes HTTP.

Cuando eso sucede, uno debe hacer una distinción entre lo que tiene que suceder instantáneamente (generalmente en el ciclo de vida de la solicitud HTTP) y lo que puede suceder eventualmente. ¿Porqué es eso? Bueno, porque cuando tu aplicación se sobrecarga de tráfico, cosas simples como esta hacen la diferencia. 

Las operaciones en una aplicación web se pueden clasificar como operaciones críticas o de tiempo de solicitud y tareas en segundo plano, las que ocurren fuera del tiempo de solicitud. Estos mapas a los descritos anteriormente: 

  • tiene que suceder al instante: operaciones de tiempo de solicitud
  • tiene que suceder eventualmente: tareas de fondo

Las operaciones de tiempo de solicitud se pueden realizar en un solo ciclo de solicitud / respuesta sin preocuparse de que la operación se agote o que el usuario pueda tener una mala experiencia. Los ejemplos comunes incluyen CRUD (Crear, Leer, Actualizar, Eliminar) operaciones de base de datos y administración de usuarios (rutinas de inicio de sesión / cierre de sesión).

Las tareas en segundo plano son diferentes, ya que suelen requerir bastante tiempo y son propensas a fallar, principalmente debido a dependencias externas. Algunos escenarios comunes entre aplicaciones web complejas incluyen:

  • enviando correos electrónicos de confirmación o de actividad
  • Rastreo y raspado diario de información de varias fuentes y su almacenamiento.
  • realizar análisis de datos
  • eliminar recursos innecesarios
  • Exportación de documentos / fotos en varios formatos.

Las tareas de fondo son el enfoque principal de este tutorial. El patrón de programación más común utilizado para este escenario es el Producer Consumer Architecture.. 

En términos simples, esta arquitectura se puede describir así: 

  • Los productores crean datos o tareas.
  • Las tareas se ponen en una cola que se conoce como la cola de tareas. 
  • Los consumidores son responsables de consumir los datos o ejecutar las tareas. 

Por lo general, los consumidores recuperan las tareas de la cola en una modalidad de primero en entrar, primero en salir (FIFO) o según sus prioridades. También se hace referencia a los consumidores como trabajadores, y ese es el término que usaremos en todo momento, ya que es consistente con la terminología utilizada por las tecnologías analizadas..

¿Qué tipo de tareas se pueden procesar en segundo plano? Tareas que:

  • No son esenciales para la funcionalidad básica de la aplicación web.
  • no se puede ejecutar en el ciclo de solicitud / respuesta ya que son lentos (uso intensivo de E / S, etc.)
  • depende de los recursos externos que pueden no estar disponibles o no comportarse como se espera
  • podría necesitar ser reintentado al menos una vez
  • tiene que ser ejecutado en un horario

El apio es la opción de facto para realizar el procesamiento de tareas en segundo plano en el ecosistema de Python / Django. Tiene una API simple y clara, y se integra perfectamente con Django. Soporta varias tecnologías para la cola de tareas y varios paradigmas para los trabajadores..

En este tutorial, vamos a crear una aplicación web de juguetes Django (que trata con escenarios del mundo real) que utiliza el procesamiento de tareas en segundo plano.

Preparando las cosas

Suponiendo que ya está familiarizado con la administración de paquetes y los entornos virtuales de Python, instalemos Django:

$ pip instala Django

He decidido construir otra aplicación de blogging. El enfoque de la aplicación estará en la simplicidad. Un usuario puede simplemente crear una cuenta y, sin demasiados problemas, puede crear una publicación y publicarla en la plataforma.. 

Configurar el quick_publisher Proyecto Django:

$ django-admin startproject quick_publisher

Empecemos la aplicación:

$ cd quick_publisher $ ./manage.py startapp main

Al iniciar un nuevo proyecto de Django, me gusta crear un principal Aplicación que contiene, entre otras cosas, un modelo de usuario personalizado. Más a menudo que no, encuentro limitaciones del Django predeterminado Usuario modelo. Tener una costumbre Usuario El modelo nos da el beneficio de la flexibilidad..

# main / models.py de django.db importan modelos de django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, clase BaseUserManager UserAccountManager (BaseUsererManager): use_in_migrations = true def _create_user (self, email, password, ** extra_fields) no correo electrónico: aumentar ValueError ('Debe proporcionarse una dirección de correo electrónico') si no es contraseña: aumentar ValueError ('Debe proporcionarse contraseña') correo electrónico = self.normalize_email (email) usuario = self.model (correo electrónico = email, ** extra_fields) user.set_password (password) user.save (utilizando = self._db) return user def create_user (self, email = Ninguno, password = None, ** extra_fields): return self._create_user (email, password, ** extra_fields) def create_superuser (self, email, password, ** extra_fields): extra_fields ['is_staff'] = True extra_fields ['is_superuser'] = True return self._create_user (email, password, ** extra_fields) clase Usuario (AbstractBaseUser, PermissionsMixin): REQUIRED_FIELDS = [] USERNAME_FIELD = objetos 'email' = UserAccountManager () email = models.EmailField ('email', unique = True, blank = False, null = False) full_name = models.CharField ('nombre completo', blank = True, null = True, max_length = 400) is_staff = models.BooleanField ('estado del personal' , predeterminado = falso) is_active = models.BooleanField ('activo', predeterminado = verdadero) def get_short_name (self): return self.email def get_full_name (self): return self.email def __unicode __ (self): return self.email

Asegúrese de consultar la documentación de Django si no está familiarizado con el funcionamiento de los modelos de usuario personalizados..

Ahora debemos decirle a Django que use este modelo de usuario en lugar del modelo predeterminado. Añade esta línea a la quick_publisher / settings.py expediente:

AUTH_USER_MODEL = 'main.User' 

También necesitamos agregar el principal aplicación a la INSTALLED_APPS lista en el quick_publisher / settings.py expediente. Ahora podemos crear las migraciones, aplicarlas y crear un superusuario para poder iniciar sesión en el panel de administración de Django:

$ ./manage.py makemigrations main $ ./manage.py migrate $ ./manage.py creasupplususuario

Ahora vamos a crear una aplicación de Django separada que sea responsable de las publicaciones:

$ ./manage.py startapp publish

Vamos a definir un modelo de post simple en editor / modelos.py:

desde django.db importar modelos desde django.utils importar zona horaria desde django.contrib.auth importar get_user_model class Post (models.Model): author = models.ForeignKey (get_user_model ()) created = models.DateTimeField ('Created Date', por defecto = timezone.now) title = models.CharField ('Title', max_length = 200) content = models.TextField ('Content') slug = models.SlugField ('Slug') def __str __ (self): return '"% s "por% s '% (self.title, self.autor)

Enganchando el Enviar modelo con el administrador de Django se realiza en el editor / admin.py archivo como este:

desde django.contrib importar administrador desde .models importar Post @ admin.register (Post) clase PostAdmin (admin.ModelAdmin): pasar

Finalmente, enganchemos el editor aplicación con nuestro proyecto añadiéndolo a la INSTALLED_APPS lista.

Ahora podemos ejecutar el servidor y dirigirnos a http: // localhost: 8000 / admin / y crea nuestras primeras publicaciones para que tengamos algo con lo que jugar:

$ ./manage.py runserver

Confío en que hayas hecho tu tarea y hayas creado las publicaciones.. 

Vamonos. El siguiente paso obvio es crear una forma de ver las publicaciones publicadas.. 

# publisher / views.py from django.http import Http404 desde django.shortcuts import render desde .models import Post def view_post (request, slug): try: post = Post.objects.get (slug = slug) excepto Post.DoesNotExist: raise Http404 ("Poll no existe") return render (request, 'post.html', context = 'post': post)

Asociemos nuestra nueva vista con una URL en: quick_publisher / urls.py

# quick_publisher / urls.py desde django.conf.urls importar url desde django.contrib importar administrador desde publisher.views importar view_post urlpatterns = [url (r '^ admin /', admin.site.urls), url (r '^ (?PAG[a-zA-Z0-9 \ -] +) ', view_post, name = "view_post")]

Finalmente, vamos a crear la plantilla que representa la publicación en: editor / plantillas / post.html

       

título de la entrada

Publicar Contenido

Publicado por post.author.full_name en post.created

Ahora podemos dirigirnos a http: // localhost: 8000 / the-slug-of-the-post-you-created / en el navegador. No es exactamente un milagro del diseño web, pero hacer publicaciones atractivas está fuera del alcance de este tutorial..

Envío de correos electrónicos de confirmación

Aquí está el escenario clásico:

  • Creas una cuenta en una plataforma.
  • Usted proporciona una dirección de correo electrónico para ser identificado de forma única en la plataforma.
  • La plataforma comprueba que usted es el propietario de la dirección de correo electrónico al enviar un correo electrónico con un enlace de confirmación.
  • Hasta que no realice la verificación, no podrá (totalmente) usar la plataforma.

Vamos a añadir un es verificado bandera y la verificación_uuid sobre el Usuario modelo:

# main / models.py importa uuid class User (AbstractBaseUser, PermissionsMixin): REQUIRED_FIELDS = [] USERNAME_FIELD = 'email' objects = UserAccountManager () email = models.EmailField ('email', unique = blank, blank = False, null = Falso) full_name = models.CharField ('nombre completo', blank = True, null = True, max_length = 400) is_staff = models.BooleanField ('estado del personal', predeterminado = False) is_active = models.BooleanField ('active', default = True) is_verified = models.BooleanField ('verified', default = False) # Agregue el indicador 'is_verified' verified_uuid = modelsIDIDField ('Unique UUID de verificación', default = uuid.uuid4) def get_short_name (self): return self.email def get_full_name (self): devolver self.email def __unicode __ (self): devolver self.email

Aprovechemos esta ocasión para agregar el modelo de usuario al administrador:

desde django.contrib importar administrador desde .models importar Usuario @ admin.register (Usuario) clase UserAdmin (admin.ModelAdmin): pasar

Hagamos que los cambios se reflejen en la base de datos:

$ ./manage.py makemigrations $ ./manage.py migrate

Ahora necesitamos escribir un fragmento de código que envíe un correo electrónico cuando se crea una instancia de usuario. Para esto son las señales de Django, y esta es una ocasión perfecta para tocar este tema.. 

Las señales se activan antes / después de que ocurran ciertos eventos en la aplicación. Podemos definir funciones de devolución de llamada que se activan automáticamente cuando se disparan las señales. Para hacer un activador de devolución de llamada, primero debemos conectarlo a una señal.

Vamos a crear una devolución de llamada que se activará después de que se haya creado un modelo de usuario. Añadiremos este código después de la Usuario definición del modelo en: main / models.py

desde django.db.models importar señales desde django.core.mail importar send_mail def user_post_save (remitente, instancia, señal, * args, ** kwargs): si no es instance.is_verified: # Enviar correo electrónico de verificación send_mail ('Verifique su cuenta de QuickPublisher ',' Siga este enlace para verificar su cuenta: "http: // localhost: 8000% s '% reverse (' verificar ', kwargs = ' uuid ': str (instance.verification_uuid)),' desde @ quickpublisher. dev ', [instance.email], fail_silently = False), signal.post_save.connect (user_post_save, sender = User)

Lo que hemos hecho aquí es que hemos definido una user_post_save función y lo conectó a la post_save señal (una que se activa después de que se haya guardado un modelo) enviada por el Usuario modelo.

Django no solo envía correos electrónicos por sí solo; Debe estar vinculado a un servicio de correo electrónico. En aras de la simplicidad, puede agregar sus credenciales de Gmail en quick_publisher / settings.py, o puede agregar su proveedor de correo electrónico favorito. 

Así es como se ve la configuración de Gmail:

EMAIL_USE_TLS = Verdadero EMAIL_HOST = 'smtp.gmail.com' EMAIL_HOST_USER = '@ gmail.com 'EMAIL_HOST_PASSWORD =''EMAIL_PORT = 587

Para probar las cosas, vaya al panel de administración y cree un nuevo usuario con una dirección de correo electrónico válida que pueda verificar rápidamente. Si todo salió bien, recibirás un correo electrónico con un enlace de verificación. La rutina de verificación aún no está lista.. 

Aquí está cómo verificar la cuenta:

# main / views.py from django.http import Http404 from django.shortcuts import render, redirect desde .models import User def home (solicitud): return render (request, 'home.html') def verificar (request, uuid): try: user = User.objects.get (verified_uuid = uuid, is_verified = False) excepto User.DoesNotExist: raise Http404 ("El usuario no existe o ya está verificado") user.is_verified = True user.save () return redirect ( 'casa')

Enganche las vistas en: quick_publisher / urls.py

# quick_publisher / urls.py desde django.conf.urls importar url desde django.contrib importar administrador desde publisher.views importar view_post desde main.views importar inicio, verificar urlpatterns = [url (r '^ $', inicio, nombre = " inicio "), url (r '^ admin /', admin.site.urls), url (r '^ verificar / (? P[a-z0-9 \ -] +) / ', verificar, nombre = "verificar"), url (r' ^ (? P[a-zA-Z0-9 \ -] +) ', view_post, name = "view_post")]

Además, recuerda crear un home.html archivar bajo main / templates / home.html. Será rendido por el casa ver.

Intenta ejecutar todo el escenario de nuevo. Si todo está bien, recibirá un correo electrónico con una URL de verificación válida. Si sigue la URL y luego ingresa al administrador, puede ver cómo se verificó la cuenta.

Enviando correos electrónicos de forma asíncrona

Aquí está el problema con lo que hemos hecho hasta ahora. Quizás hayas notado que crear un usuario es un poco lento. Eso es porque Django envía el correo electrónico de verificación dentro del tiempo de solicitud. 

Así es como funciona: enviamos los datos del usuario a la aplicación Django. La aplicación crea un Usuario modela y luego crea una conexión a Gmail (u otro servicio que hayas seleccionado). Django espera la respuesta, y solo entonces devuelve una respuesta a nuestro navegador.. 

Aquí es donde entra el apio. Primero, asegúrese de que esté instalado:

$ pip instala apio

Ahora necesitamos crear una aplicación de apio en nuestra aplicación Django:

# quick_publisher / celery.py import os desde celery import Celery os.environ.setdefault ('DJANGO_SETTINGS_MODULE', 'quick_publisher.settings') app = Celery ('quick_publisher') app.config_from_object ('django.conf settings) Módulos de tareas de todas las configuraciones de aplicaciones Django registradas. app.autodiscover_tasks ()

El apio es una cola de tareas. Recibe tareas de nuestra aplicación Django y las ejecutará en segundo plano. El apio debe combinarse con otros servicios que actúan como intermediarios. 

Los intermediarios intermedian el envío de mensajes entre la aplicación web y el apio. En este tutorial, estaremos usando Redis. Redis es fácil de instalar y podemos comenzar a utilizarlo fácilmente sin demasiados problemas..

Puede instalar Redis siguiendo las instrucciones en la página de Inicio rápido de Redis. Tendrá que instalar la biblioteca Redis Python, pip instalar redis, y el paquete necesario para usar Redis y Apio: pip instalar apio [redis].

Inicie el servidor Redis en una consola separada como esta: $ redis-server

Agreguemos las configuraciones relacionadas con Apio / Redis en quick_publisher / settings.py:

# REDIS ajustes relacionados REDIS_HOST = 'localhost' REDIS_PORT = '6379' BROKER_URL = 'redis: //' + REDIS_HOST + ':' + REDIS_PORT + '/ 0' BROKER_TRANSPORT_OPTIONS = 'visibilidad_timeout': 3600 CELERY_RESULTADO_BACKEND_BACKEND = Redis_PRODUCTUM_BACKEND_BACKEND = '' / '+ REDIS_HOST +': '+ REDIS_PORT +' / 0 '

Antes de que se pueda ejecutar algo en el apio, debe declararse como una tarea.. 

Aquí está cómo hacer esto:

# main / tasks.py importar el registro desde django.urls importar inverso desde django.core.mail importar send_mail desde django.contrib.auth importar get_user_model desde quick_publisher.celery importar app @ app.task def send_verification_email (user_id): UserModel = get_user_model ( ) intente: usuario = UserModel.objects.get (pk = user_id) send_mail ('Verifique su cuenta de QuickPublisher', 'Siga este enlace para verificar su cuenta: "http: // localhost: 8000% s'% reverse ('verificar' , kwargs = 'uuid': str (user.verification_uuid)), '[email protected]', [user.email], fail_silently = False,) excepto UserModel.DoesNotExist: logging.warning ("Se intentó enviar la verificación correo electrónico al usuario no existente '% s' "% user_id)

Lo que hemos hecho aquí es esto: movimos la funcionalidad de correo electrónico de verificación de envío a otro archivo llamado tareas.py

Algunas notas:

  • El nombre del archivo es importante. El apio pasa por todas las aplicaciones de INSTALLED_APPS y registra las tareas en tareas.py archivos.
  • Observe cómo decoramos el Envia un correo electronico de verificación funcionar con @ app.task. Esto le dice a Celery que esta es una tarea que se ejecutará en la cola de tareas.
  • Note como esperamos como argumento user_id preferible a Usuario objeto. Esto se debe a que podemos tener problemas para serializar objetos complejos al enviar las tareas a Celery. Es mejor mantenerlos simples..

Volviendo a main / models.py, El código de señal se convierte en:

desde django.db.models importar señales de main.tasks import send_verification_email def user_post_save (remitente, instancia, señal, * args, ** kwargs): si no es instance.is_verified: # Enviar señales de verificación enviar_verificación_email.delay (instance.pk) señales .post_save.connect (user_post_save, sender = User)

Observe cómo llamamos al .retrasar Método en el objeto de tarea. Esto significa que estamos enviando la tarea a Celery y no esperamos el resultado. Si usamos send_verification_email (instance.pk) En cambio, seguiríamos enviándolo a Celery, pero estaríamos esperando que la tarea termine, lo cual no es lo que queremos..

Antes de comenzar a crear un nuevo usuario, hay un problema. El apio es un servicio, y tenemos que iniciarlo. Abra una nueva consola, asegúrese de activar el virtualenv apropiado y navegue a la carpeta del proyecto.

$ celery worker -A quick_publisher --loglevel = debug --concurrency = 4

Esto da inicio a cuatro trabajadores del proceso de Apio. Sí, ahora ya puedes ir y crear otro usuario. Observe cómo no hay demora, y asegúrese de ver los registros en la consola de Celery y ver si las tareas se ejecutan correctamente. Esto debería verse algo como esto:

[2017-04-28 15: 00: 09,190: DEBUG / Proceso principal] Tarea aceptada: main.tasks.send_verification_email [f1f41e1f-ca39-43d2-a37d-9de085dc99de] pid: 62065 [2017-04-28 15: 00: 11,740: INFO / PoolWorker-2] Tarea main.tasks.send_verification_email [f1f41e1f-ca39-43d2-a37d-9de085dc99de] se realizó con éxito en 2.5500912349671125s: Ninguno

Tareas Periódicas Con Apio

Aquí hay otro escenario común. La mayoría de las aplicaciones web maduras envían a sus usuarios correos electrónicos de ciclo de vida para mantenerlos comprometidos. Algunos ejemplos comunes de correos electrónicos de ciclo de vida:

  • reportes mensuales
  • Notificaciones de actividad (me gusta, solicitudes de amistad, etc.)
  • recordatorios para realizar ciertas acciones ("No olvide activar su cuenta")

Esto es lo que vamos a hacer en nuestra aplicación. Vamos a contar cuántas veces se ha visto cada publicación y enviar un informe diario al autor. Una vez cada día, vamos a revisar todos los usuarios, buscar sus publicaciones y enviar un correo electrónico con una tabla con las publicaciones y los recuentos de visitas..

Vamos a cambiar el Enviar Modelo para que podamos acomodar el escenario de conteos de vistas..

clase Post (models.Model): author = models.ForeignKey (Usuario) created = models.DateTimeField ('Date Date', default = timezone.now) title = models.CharField ('Title', max_length = 200) content = models .TextField ('Contenido') slug = models.SlugField ('Slug') view_count = models.IntegerField ("View Count", default = 0) def __str __ (self): return '"% s" by% s'% ( título propio, autor propio

Como siempre, cuando cambiamos un modelo, necesitamos migrar la base de datos:

$ ./manage.py makemigrations $ ./manage.py migrate

También modifiquemos el ver publicacion Django vista para contar vistas:

def view_post (request, slug): try: post = Post.objects.get (slug = slug) excepto Post.DoesNotExist: raise Http404 ("Poll no existe") post.view_count + = 1 post.save () retorno render (solicitud, 'post.html', context = 'post': post)

Sería útil mostrar el conteo de visitas en la plantilla Agrega esto 

Visto post.view_count veces

 en algún lugar dentro del editor / plantillas / post.html expediente. Haga algunas vistas en una publicación ahora y vea cómo aumenta el contador.

Vamos a crear una tarea de apio. Ya que se trata de posts, lo voy a colocar en editor / tareas.py:

desde django.template importar plantilla, contexto desde django.core.mail importar send_mail desde django.contrib.auth importar get_user_model desde quick_publisher.celery importar aplicación desde publisher.models importar Publicar REPORT_TEMPLATE = "" Así es como lo hizo ahora: % para la publicación en las publicaciones% "post.title": vista post.view_count veces | % endfor% "" "@ app.task def send_view_count_report (): para el usuario en get_user_model (). objects.all (): posts = Post.objects.filter (author = user) si no es posts: continue template = Template (REPORT_TEMPLATE) send_mail ('Your QuickPublisher Activity', template.render (context = Context ('posts': publicaciones)), '[email protected]', [user.email], fail_silently = False,)

Cada vez que realice cambios en las tareas de apio, recuerde reiniciar el proceso de apio. El apio necesita descubrir y recargar las tareas. Antes de crear una tarea periódica, deberíamos probar esto en el shell de Django para asegurarnos de que todo funciona como debe:

$ ./manage.py shell En [1]: desde publisher.tasks import send_view_count_report In [2]: send_view_count_report.delay ()

Esperemos que hayas recibido un pequeño e ingenioso informe en tu correo electrónico.. 

Ahora vamos a crear una tarea periódica. Abrir quick_publisher / celery.py y registrar las tareas periódicas:

# quick_publisher / celery.py import os del celery import Celery from celery.schedules import crontab os.environ.setdefault ('DJANGO_SETTINGS_MODULE', 'quick_publisher.settings') app = Celery ('quick_publisher') app.config_fu_fop / : settings ') # Cargar módulos de tareas de todas las configuraciones de aplicaciones Django registradas. app.autodiscover_tasks () app.conf.beat_schedule = 'send-report-every-single-minute': 'task': 'publisher.tasks.send_view_count_report', 'schedule': crontab (), # change to 'crontab (minuto = 0, hora = 0) 'si desea que se ejecute diariamente a la medianoche,

Hasta ahora, hemos creado un calendario que ejecutaría la tarea publisher.tasks.send_view_count_report cada minuto indicado por el crontab () notación. También puede especificar varios horarios de apio Crontab. 

Abra otra consola, active el entorno adecuado e inicie el servicio Celery Beat. 

$ apio - Un golpe rápido de editor

El trabajo del servicio Beat es impulsar las tareas en el apio de acuerdo con el calendario. Ten en cuenta que el horario hace que el send_view_count_report Tarea ejecutada cada minuto según la configuración. Es bueno para las pruebas, pero no se recomienda para una aplicación web del mundo real..

Haciendo las tareas más confiables

Las tareas a menudo se utilizan para realizar operaciones no confiables, operaciones que dependen de recursos externos o que pueden fallar fácilmente debido a varias razones. Aquí hay una guía para hacerlos más confiables:

  • Hacer tareas idempotentes. Una tarea idempotente es una tarea que, si se detiene a mitad de camino, no cambia el estado del sistema de ninguna manera. La tarea realiza cambios completos en el sistema o ninguno en absoluto.
  • Vuelva a intentar las tareas. Si la tarea falla, es una buena idea intentarlo una y otra vez hasta que se ejecute correctamente. Puedes hacer esto en Celery con Celery Reintentar. Otra cosa interesante a considerar es el algoritmo de retroceso exponencial. Esto puede ser útil cuando se piensa en limitar la carga innecesaria en el servidor de tareas reintentadas.

Conclusiones

Espero que haya sido un tutorial interesante para ti y una buena introducción al uso de Celery con Django.. 

Aquí hay algunas conclusiones que podemos sacar:

  • Es una buena práctica mantener las tareas no confiables y que requieren mucho tiempo fuera del tiempo de solicitud.
  • Las tareas de larga ejecución deben ejecutarse en segundo plano mediante procesos de trabajo (u otros paradigmas).
  • Las tareas en segundo plano se pueden usar para varias tareas que no son críticas para el funcionamiento básico de la aplicación.
  • El apio también puede manejar tareas periódicas usando el batido de apio Servicio.
  • Las tareas pueden ser más confiables si se hacen idempotentes y se reintentan (tal vez utilizando un retroceso exponencial).