Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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. [Quase] Tudo que você precisa saber sobre tarefas assíncronas Filipe

    Ximenes @xima
  2. @xima

  3. None
  4. vinta.com.br/playbook

  5. FLOSS Django React boilerplate https://github.com/vintasoftware/django-react-boilerplate Django Role Permissions https://github.com/vintasoftware/django-role-permissions Tapioca

    https://github.com/vintasoftware/tapioca-wrapper
  6. Context

  7. queremos responder rápido para o usuário

  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;
  9. Produtor & Consumidor

  10. None
  11. Celery

  12. Dando nome aos componentes

  13. Broker ?

  14. None
  15. Compatibilidade

  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
  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)
  18. Não passe objetos complexos nos parâmetros da task

  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)
  20. Faltou algo result = add.delay(4, 5).get()

  21. O que acontece com o valor retornado?

  22. Lidando com Erros

  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
  24. Operações Idempotentes 3 * 0 = 0 * 0 =

    0 * 0 = 0 …. 3 * 1 = 3 * 1 = 3 * 1 = 3 ....
  25. Operações Idempotentes GET /events/12/ POST /events/ { "name": "DjangoDay" }

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

    DELETE /events/12/ PUT /events/12/ { "name": "DjangoDay" }
  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
  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.
  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.
  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)
  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))
  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()
  33. None
  34. Limite de tempo from celery.exceptions import SoftTimeLimitExceeded @app.task(task_time_limit=60, task_soft_time_limit=50) def

    mytask(): try: possibly_long_task() except SoftTimeLimitExceeded: recover()
  35. acks_late Notifica & Executa vs. Executa & Notifica

  36. Monitoramento

  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
  38. Se falhar, GRITE!

  39. Celery Flower

  40. vintasoftware/django-celerybeat-status

  41. vintasoftware/django-celerybeat-status

  42. Testes e Depuração

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

    app.conf.task_always_eager: return self.apply(...) # ... # ...
  44. Rdb: pdb para Celery from celery.contrib import rdb @app.task def

    add_debug(a, b): rdb.set_trace() return a + b
  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;
  46. celerytaskschecklist.com

  47. Obrigado! http://bit.ly/vinta-pybr13 celerytaskschecklist.com Newsletter: vinta.com.br/blog/ twitter.com/@xima ximenes@vinta.com.br github.com/filipeximenes Licences: http://www.flaticon.com/authors/madebyoliver

  48. Extending the base class

  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
  50. Canvas

  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)
  52. Callbacks r = add.apply_async((1, 2), link=add.s(3)) r.get() # 3 r.children[0].result

    # 6
  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
  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], # ...
  55. Multiple Queues

  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')
  57. Task dependency

  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)
  59. Never wait inside a task

  60. Decouple as much as possible