Quase tudo que você precisa saber sobre tarefa assincronas

Quase tudo que você precisa saber sobre tarefa assincronas

Por quanto tempo é aceitável deixar o seu usuário esperando uma resposta do servidor? Para a maioria dos casos a resposta certa é: o mínimo que a conexão de internet dele permitir. Isso significa que o tempo que o seu servidor deve passar processando a requisição deve ser o mais próximo de zero possível. Nessa palestra vamos falar sobre o que são, para que servem, como funcionam e quais são as boas práticas a se seguir quando estamos desenvolvendo tarefas assíncronas.

Ce373a3291defccc69a1392feb587f17?s=128

Filipe Ximenes

October 08, 2017
Tweet

Transcript

  1. 2.
  2. 3.
  3. 6.
  4. 8.

    Pra que servem tasks assíncronas? • Delegar tarefas longas; •

    Executar chamadas a API remotas; • Preparar e cachear valores; • Espalhar pilhas de inserções ao banco ao longo do tempo; • Executar tarefas recorrentes;
  5. 10.
  6. 11.
  7. 13.
  8. 14.
  9. 16.

    Web e Worker from celery import Celery app = Celery(...)

    @app.task def add(a, b): return a + b from tasks import add r = add.delay(4, 5).get() print(r) # 9
  10. 17.

    No Django @app.task def update_attendees(event, n): event.attendees_number = n event.save()

    ------------------------------------------------- event = Event.objects.get(name='DjangoDay') update_attendees.delay(event, 9001)
  11. 19.

    No Django @app.task def update_attendees(event_id, n): e = Event.objects.get(id=id_event) e.attendees_number

    = n e.save() ------------------------------------------------- e = Event.objects.get(name='DjangoCon') update_attendees.delay(e.id, 9001)
  12. 23.

    Idempotência "... é a propriedade que algumas operações têm de

    poderem ser aplicadas várias vezes sem que o valor do resultado se altere após a aplicação inicial." - wikipedia
  13. 24.

    Operações Idempotentes 3 * 0 = 0 * 0 =

    0 * 0 = 0 …. 3 * 1 = 3 * 1 = 3 * 1 = 3 ....
  14. 25.

    Operações Idempotentes GET /events/12/ POST /events/ { "name": "DjangoDay" }

    DELETE /events/12/ PUT /events/12/ { "name": "DjangoDay" }
  15. 26.

    Operações Idempotentes GET /events/12/ POST /events/ { "name": "DjangoDay" }

    DELETE /events/12/ PUT /events/12/ { "name": "DjangoDay" }
  16. 27.

    Atomicidade "... é uma operação, ou conjunto de operações, em

    uma base de dados, ou em qualquer outro sistema computacional, que deve ser executada completamente em caso de sucesso, ou ser abortada completamente em caso de erro" - wikipedia
  17. 28.

    Como ser atômico? @app.task def update_data(): user.status = 'updated' user.save()

    r = facebook_request() user.name = r.name user.save() @app.task def update_data(): r = facebook_request() if r.status != 200: return user.name = r.name user.status = 'updated' user.save() vs.
  18. 29.

    Como ser atômico? @app.task def newsletter(): users = all_users() for

    u in users: send(email, 'newsletter') @app.task def send_newsletter(email): send(email, 'newsletter') @app.task def newsletter(): users = all_users() for u in users: send_newsletter.delay(u.email) vs.
  19. 30.

    Falhou? Tente novamente! from tapioca.exceptions import TapiocaException from tapioca_facebook import

    Facebook @app.task(bind=True, retry_limit=4 default_retry_delay=10) def likes_do_facebook(self): api = Facebook(access_token=ACCESS_TOKEN) try: api.user_likes(id='me').get() except TapiocaException as e: raise self.retry(exc=e)
  20. 31.

    Backoff! def exponential_backoff(task_self): minutes = task_self.default_retry_delay / 60 rand =

    random.uniform(minutes, minutes * 1.3) return int(rand ** task_self.request.retries) * 60 self.retry(exc=e, countdown=exponential_backoff(self))
  21. 32.

    Auto Retry from tapioca.exceptions import TapiocaException from tapioca_facebook import Facebook

    @app.task(autoretry_for=(TapiocaException,)) def likes_do_facebook(self): api = Facebook(access_token=ACCESS_TOKEN) api.user_likes(id='me').get()
  22. 33.
  23. 37.

    Logging from celery.utils.log import get_task_logger logger = get_task_logger(__name__) @app.task def

    add(a, b): logger.info('Adds {0} + {1}'.format(a, b)) return a + b
  24. 43.

    task_always_eager class Task(object): # ... def apply_assync(): # ... if

    app.conf.task_always_eager: return self.apply(...) # ... # ...
  25. 44.

    Rdb: pdb para Celery from celery.contrib import rdb @app.task def

    add_debug(a, b): rdb.set_trace() return a + b
  26. 45.

    Revisão • Não use objetos complexos nos parâmetros das suas

    tasks; • Escreva tasks idempotentes e atômicas; • Utilize `retry` e lembre de 'recuar' quando necessário; • Utilize ao máximo ferramentas de monitoramento; • Seja notificado se algo falhar; • Use `task_always_eager` para testar e depurar;
  27. 49.

    from celery import Task class CustomTask(Task): def after_return(self, status, retval,

    task_id, args, kwargs, einfo): # ... def on_failure(self, exc, task_id, args, kwargs, einfo): # ... def on_retry(self, exc, task_id, args, kwargs, einfo): # ... def on_success(self, retval, task_id, args, kwargs): # ... ------------------------------------------------------------------ @app.task(base=CustomTask) def my_task(): # … ------------------------------------------------------------------ app.Task = CustomTask
  28. 50.
  29. 51.

    Signatures from celery import signature signature('tasks.add', args=(2, 2), countdown=10) add.signature((2,

    2), countdown=10) add.s(2, 2) add.s(2, 2)() add.delay(2, 2) add.signature((2, 2), countdown=1).apply_async() partial = add.s(2) partial.delay(4)
  30. 53.

    Chains, Groups & Chords c = chain(add.s(4, 4), mul.s(8), mul.s(10))

    res = c().get() # 640 g = group(add.s(2, 2), add.s(4, 4)) res = g().get() # [4, 8] callback = tsum.s() header = [add.s(i, i) for i in range(100)] result = chord(header)(callback) result.get() # 9900
  31. 54.

    Map, Startmap & Chunks x = xsum.map([range(10), range(100)]).delay() x.get() #

    [45, 4950] p = zip(range(10), range(10)) p # [(0, 0), (1, 1), (2, 2), ...] s = add.starmap(p).delay() s.get() # [0, 2, 4, 6, 8, 10, 12, 14, 16, 18] p2 = zip(range(100), range(100)) c = add.chunks(p2, 10) c().get() # [[0, 2, 4, 6, 8, 10, 12, 14, 16, 18], # [20, 22, 24, 26, 28, 30, 32, 34, 36, 38], # ...
  32. 56.

    app.conf.task_routes = { 'app.tasks.add: {'queue': 'add'}, 'app.tasks.subtract: {'queue': 'subtract'} }

    $ celery -A proj worker -Q add, celery $ celery -A proj worker -Q subtract sub.apply_async((2, 1), queue='subtract')
  33. 58.

    Calling a task from another @app.task def add(a, b): return

    a + b @app.task def add_all(*args): result = 0 for i in args: result = add.delay(result, i).get() return result add_all.delay(1, 2, 3, 4)