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

How Optimizely scaled its REST API with asyncio

How Optimizely scaled its REST API with asyncio

Description
With developers, an awesome product isn’t everything, or at least we found that out. More than a product you need a platform. But what is a platform? Learn tips and tricks about building a public API using the latest and greatest tools: OpenAPI, Python 3 and asyncio.

Abstract
With developers, an awesome product isn’t everything, or at least we found that out. More than a product, you need a platform. But what is a platform? And how do you build one alongside all the other weird old code you might have inherited?

In a perfect world, Jeff Dean would ride in on a Unicorn with stone tablets inscribed with UML diagrams of an application architecture so perfect you’d weep with joy. In the real world however that tends not to happen, so the process is a bit more iterative. Sometimes you try things that don’t work or seem silly in retrospect.

Part of our platform journey has been building a public API with our favorite hand picked technologies: OpenAPI and Python 3, especially asyncio. Building a rock solid, fast, and scalable Python application was not all rainbows and unicorns along the way. Through this process, we learned quite a bit about describing REST APIs and how modern Python concurrency mechanisms behave in the real world.

We’ll share how we used OpenAPI to power our public API and improve the developer experience for both internal engineers and external consumers.

We started off with a mix of multiprocessing and asyncio, and have since migrated completely to asyncio. Also, we’ll share a few of the things we've learned in the process, which we hope will help you if you choose to use asyncio.

Bios
Nick DiRienzo is a software engineer at Optimizely. He enjoys writing code in Python, making developers’ lives better, and eating Mission burritos.

Vinay Tota is currently a member of the Application Platform team at Optimizely. A long time ago wanted to be a theoretical physicist but now has way more fun hacking code. More recently he's worked on building high throughput low latency systems for ad serving using Python.

Nick DiRienzo

August 19, 2017
Tweet

More Decks by Nick DiRienzo

Other Decks in Programming

Transcript

  1. We’ll talk about: • Optimizely’s API story • What makes

    an API “good” • Optimizely’s API gateway • Improving it with asyncio • Lessons learned
  2. Optimizely X Full Stack Code Based Experimentation Optimizely X Web

    Visual Editor Based Experimentation Experimentation Platform
  3. Getting Optimizely Results 1. Get Experiment data 2. Get Layer

    data (whatever that is) 3. Get Project Settings
  4. Getting Optimizely Results 1. Get Experiment data 2. Get Layer

    data (whatever that is) 3. Get Project Settings 4. Get results data
  5. 1. Get Experiment data 2. Get Layer data (whatever that

    is) 3. Get Project Settings 4. Get results data GET /v2/experiments/:id/results Getting Optimizely Results
  6. Getting Optimizely Results 1. Get Experiment data 2. Get Layer

    data (whatever that is) 3. Get Project Settings 4. Get results data
  7. 1. Get Experiment data 2. Get Layer data (whatever that

    is) 3. Get Project Settings 4. Get results data /v2/experiments/:id/results Getting Optimizely Results
  8. Benefits of an API gateway Operations through the API look

    a lot like their web UI counterparts

  9. Benefits of an API gateway Operations through the API look

    a lot like their web UI counterparts Allows us to orchestrate APIs instead of data models

  10. Benefits of an API gateway Operations through the API look

    a lot like their web UI counterparts Allows us to orchestrate APIs instead of data models
 
 Pulls problems away from business logic into a higher-level
 

  11. Benefits of an API gateway Operations through the API look

    a lot like their web UI counterparts Allows us to orchestrate APIs instead of data models
 
 Pulls problems away from business logic into a higher-level
 
 Internal and external consumers go through the same application
 

  12. Pyramid and Multiprocessing with multiprocessing.Pool() as pool, multiprocessing.Manager() as manager:


    response_queue = manager.Queue()
 try:
 # send our requests get back responses from the response_queue
 finally:
 pool.join()
 pool.terminate()
  13. Pyramid and Multiprocessing with multiprocessing.Pool() as pool, multiprocessing.Manager() as manager:


    response_queue = manager.Queue()
 try:
 # send our requests get back responses from the response_queue
 finally:
 pool.join()
 pool.terminate()
  14. Pyramid and Multiprocessing with multiprocessing.Pool() as pool, multiprocessing.Manager() as manager:


    response_queue = manager.Queue()
 try:
 # send our requests get back responses from the response_queue
 finally:
 pool.join()
 pool.terminate()
  15. Pyramid and Multiprocessing with multiprocessing.Pool() as pool, multiprocessing.Manager() as manager:


    response_queue = manager.Queue()
 try:
 # send our requests get back responses from the response_queue
 finally:
 pool.join()
 pool.terminate()
  16. Nonblocking IO Application Kernel Start read() Initiate read, returns immediately

    read() Receive response poll()until data has arrived
  17. Nonblocking IO Application Kernel Start read() Initiate read, returns immediately

    read() Receive response poll()until data has arrived
  18. Non Blocking IO Tw isted N odeJS Tornado 2002 N

    etty 2004 2009 2006 EventM achine 2008 Eventlet 2015 Asyncio 2012 G o/G orutines
  19. async def i_take_a_while(): print('slow func start') await asyncio.sleep(3) print('slow func

    done') async def i_run_fast(): print('fast func start') await asyncio.sleep(1) print('fast func done') async def run_me(): await asyncio.gather( i_run_fast(), i_take_a_while() ) loop = asyncio.get_event_loop()
 loop.run_until_complete(run_me())
 loop.close() Event loop i_take_a_while i_run_fast
  20. async def i_take_a_while(): print('slow func start') await asyncio.sleep(3) print('slow func

    done') async def i_run_fast(): print('fast func start') await asyncio.sleep(1) print('fast func done') async def run_me(): await asyncio.gather( i_run_fast(), i_take_a_while() ) loop = asyncio.get_event_loop()
 loop.run_until_complete(run_me())
 loop.close() Event loop i_take_a_while i_run_fast
  21. async def i_take_a_while(): print('slow func start') await asyncio.sleep(3) print('slow func

    done') async def i_run_fast(): print('fast func start') await asyncio.sleep(1) print('fast func done') async def run_me(): await asyncio.gather( i_run_fast(), i_take_a_while() ) loop = asyncio.get_event_loop()
 loop.run_until_complete(run_me())
 loop.close() Event loop i_take_a_while i_run_fast fast func start
  22. async def i_take_a_while(): print('slow func start') await asyncio.sleep(3) print('slow func

    done') async def i_run_fast(): print('fast func start') await asyncio.sleep(1) print('fast func done') async def run_me(): await asyncio.gather( i_run_fast(), i_take_a_while() ) loop = asyncio.get_event_loop()
 loop.run_until_complete(run_me())
 loop.close() Event loop i_take_a_while i_run_fast fast func start
  23. async def i_take_a_while(): print('slow func start') await asyncio.sleep(3) print('slow func

    done') async def i_run_fast(): print('fast func start') await asyncio.sleep(1) print('fast func done') async def run_me(): await asyncio.gather( i_run_fast(), i_take_a_while() ) loop = asyncio.get_event_loop()
 loop.run_until_complete(run_me())
 loop.close() Event loop i_take_a_while i_run_fast slow func start fast func start
  24. async def i_take_a_while(): print('slow func start') await asyncio.sleep(3) print('slow func

    done') async def i_run_fast(): print('fast func start') await asyncio.sleep(1) print('fast func done') async def run_me(): await asyncio.gather( i_run_fast(), i_take_a_while() ) loop = asyncio.get_event_loop()
 loop.run_until_complete(run_me())
 loop.close() Event loop i_take_a_while i_run_fast slow func start fast func start
  25. async def i_take_a_while(): print('slow func start') await asyncio.sleep(3) print('slow func

    done') async def i_run_fast(): print('fast func start') await asyncio.sleep(1) print('fast func done') async def run_me(): await asyncio.gather( i_run_fast(), i_take_a_while() ) loop = asyncio.get_event_loop()
 loop.run_until_complete(run_me())
 loop.close() Event loop i_take_a_while i_run_fast slow func start fast func start
  26. async def i_take_a_while(): print('slow func start') await asyncio.sleep(3) print('slow func

    done') async def i_run_fast(): print('fast func start') await asyncio.sleep(1) print('fast func done') async def run_me(): await asyncio.gather( i_run_fast(), i_take_a_while() ) loop = asyncio.get_event_loop()
 loop.run_until_complete(run_me())
 loop.close() Event loop i_take_a_while i_run_fast slow func start fast func start 1 sec after start fast func done
  27. async def i_take_a_while(): print('slow func start') await asyncio.sleep(3) print('slow func

    done') async def i_run_fast(): print('fast func start') await asyncio.sleep(1) print('fast func done') async def run_me(): await asyncio.gather( i_run_fast(), i_take_a_while() ) loop = asyncio.get_event_loop()
 loop.run_until_complete(run_me())
 loop.close() Event loop i_take_a_while i_run_fast slow func start fast func start 1 sec after start fast func done
  28. async def i_take_a_while(): print('slow func start') await asyncio.sleep(3) print('slow func

    done') async def i_run_fast(): print('fast func start') await asyncio.sleep(1) print('fast func done') async def run_me(): await asyncio.gather( i_run_fast(), i_take_a_while() ) loop = asyncio.get_event_loop()
 loop.run_until_complete(run_me())
 loop.close() Event loop i_take_a_while i_run_fast slow func start fast func start 1 sec after start fast func done
  29. async def i_take_a_while(): print('slow func start') await asyncio.sleep(3) print('slow func

    done') async def i_run_fast(): print('fast func start') await asyncio.sleep(1) print('fast func done') async def run_me(): await asyncio.gather( i_run_fast(), i_take_a_while() ) loop = asyncio.get_event_loop()
 loop.run_until_complete(run_me())
 loop.close() Event loop i_take_a_while i_run_fast slow func start fast func start 1 sec after start fast func done 3 sec after start slow func done
  30. Blocking -> Asyncio Gunicorn Pyramid Pyramid View Switch to asyncio

    worker class http:// docs.gunicorn.org/en/stable/design.html#asyncio- workers
  31. async def i_block(): print('slow func start') time.sleep(3) print('slow func done')

    async def i_run_fast(): print('fast func start') await asyncio.sleep(1) print('fast func done') async def run_me(): await asyncio.gather( i_run_fast(), i_block() ) loop = asyncio.get_event_loop()
 loop.run_until_complete(run_me())
 loop.close() Event loop i_block i_run_fast
  32. async def i_block(): print('slow func start') time.sleep(3) print('slow func done')

    async def i_run_fast(): print('fast func start') await asyncio.sleep(1) print('fast func done') async def run_me(): await asyncio.gather( i_run_fast(), i_block() ) loop = asyncio.get_event_loop()
 loop.run_until_complete(run_me())
 loop.close() Event loop i_block i_run_fast fast func start
  33. async def i_block(): print('slow func start') time.sleep(3) print('slow func done')

    async def i_run_fast(): print('fast func start') await asyncio.sleep(1) print('fast func done') async def run_me(): await asyncio.gather( i_run_fast(), i_block() ) loop = asyncio.get_event_loop()
 loop.run_until_complete(run_me())
 loop.close() Event loop i_block i_run_fast fast func start
  34. async def i_block(): print('slow func start') time.sleep(3) print('slow func done')

    async def i_run_fast(): print('fast func start') await asyncio.sleep(1) print('fast func done') async def run_me(): await asyncio.gather( i_run_fast(), i_block() ) loop = asyncio.get_event_loop()
 loop.run_until_complete(run_me())
 loop.close() Event loop i_block i_run_fast block func start fast func start
  35. async def i_block(): print('slow func start') time.sleep(3) print('slow func done')

    async def i_run_fast(): print('fast func start') await asyncio.sleep(1) print('fast func done') async def run_me(): await asyncio.gather( i_run_fast(), i_block() ) loop = asyncio.get_event_loop()
 loop.run_until_complete(run_me())
 loop.close() Event loop i_block i_run_fast block func start fast func start
  36. async def i_block(): print('slow func start') time.sleep(3) print('slow func done')

    async def i_run_fast(): print('fast func start') await asyncio.sleep(1) print('fast func done') async def run_me(): await asyncio.gather( i_run_fast(), i_block() ) loop = asyncio.get_event_loop()
 loop.run_until_complete(run_me())
 loop.close() Event loop i_block i_run_fast block func start fast func start 3 sec later block func done
  37. async def i_block(): print('slow func start') time.sleep(3) print('slow func done')

    async def i_run_fast(): print('fast func start') await asyncio.sleep(1) print('fast func done') async def run_me(): await asyncio.gather( i_run_fast(), i_block() ) loop = asyncio.get_event_loop()
 loop.run_until_complete(run_me())
 loop.close() Event loop i_block i_run_fast block func start fast func start 3 sec later block func done
  38. async def i_block(): print('slow func start') time.sleep(3) print('slow func done')

    async def i_run_fast(): print('fast func start') await asyncio.sleep(1) print('fast func done') async def run_me(): await asyncio.gather( i_run_fast(), i_block() ) loop = asyncio.get_event_loop()
 loop.run_until_complete(run_me())
 loop.close() Event loop i_block i_run_fast block func start fast func start 3 sec later block func done
  39. async def i_block(): print('slow func start') time.sleep(3) print('slow func done')

    async def i_run_fast(): print('fast func start') await asyncio.sleep(1) print('fast func done') async def run_me(): await asyncio.gather( i_run_fast(), i_block() ) loop = asyncio.get_event_loop()
 loop.run_until_complete(run_me())
 loop.close() Event loop i_block i_run_fast block func start fast func start 3 sec later block func done
  40. async def i_block(): print('slow func start') time.sleep(3) print('slow func done')

    async def i_run_fast(): print('fast func start') await asyncio.sleep(1) print('fast func done') async def run_me(): await asyncio.gather( i_run_fast(), i_block() ) loop = asyncio.get_event_loop()
 loop.run_until_complete(run_me())
 loop.close() Event loop i_block i_run_fast block func start fast func start 3 sec later block func done fast func done
  41. Code is Social • Better make sure it actually works!

    • Pull Request Open 3+ weeks • Everyone on team added as a reviewer
  42. Code is Social • Better make sure it actually works!

    • Pull Request Open 3+ weeks • Everyone on team added as a reviewer • 63 comments on Pull Request
  43. Code is Social • Better make sure it actually works!

    • Pull Request Open 3+ weeks • Everyone on team added as a reviewer • 63 comments on Pull Request • Meetings for questions/concerns
  44. • asyncio from the start is ideal • But can

    add it later if you have to asyncio
  45. • asyncio from the start is ideal • But can

    add it later if you have to • Still early, but support will only improve asyncio
  46. Good APIs are Hard
 • Lots of details to get

    right • Using the right technologies helps
  47. Good APIs are Hard
 • Lots of details to get

    right • Using the right technologies helps • Always keep iterating and improving
  48. Q&A