Flasync Await
This is a modal window.
Das Video konnte nicht geladen werden, da entweder ein Server- oder Netzwerkfehler auftrat oder das Format nicht unterstützt wird.
Formale Metadaten
Titel |
| |
Serientitel | ||
Anzahl der Teile | 130 | |
Autor | ||
Lizenz | CC-Namensnennung - keine kommerzielle Nutzung - Weitergabe unter gleichen Bedingungen 3.0 Unported: Sie dürfen das Werk bzw. den Inhalt zu jedem legalen und nicht-kommerziellen Zweck nutzen, verändern und in unveränderter oder veränderter Form vervielfältigen, verbreiten und öffentlich zugänglich machen, sofern Sie den Namen des Autors/Rechteinhabers in der von ihm festgelegten Weise nennen und das Werk bzw. diesen Inhalt auch in veränderter Form nur unter den Bedingungen dieser Lizenz weitergeben | |
Identifikatoren | 10.5446/50103 (DOI) | |
Herausgeber | ||
Erscheinungsjahr | ||
Sprache |
Inhaltliche Metadaten
Fachgebiet | ||
Genre | ||
Abstract |
|
EuroPython 2020106 / 130
2
4
7
8
13
16
21
23
25
26
27
30
33
36
39
46
50
53
54
56
60
61
62
65
68
73
82
85
86
95
100
101
102
106
108
109
113
118
119
120
125
00:00
TouchscreenÜberschallströmungGemeinsamer SpeicherXMLComputeranimationBesprechung/Interview
00:28
Exogene VariableÜberschallströmungFramework <Informatik>DatenparallelitätCodeProgrammbibliothekDatenkompressionTaskBefehlsprozessorProzess <Informatik>ThreadServerSynchronisierungZufallszahlenSpeicherabzugREST <Informatik>Nichtlinearer OperatorWeb-DesignerApp <Programm>InformationRoutingFramework <Informatik>BenutzerbeteiligungZentrische StreckungMultiplikationsoperatorBenchmarkResultanteLesen <Datenverarbeitung>Coxeter-GruppeURLVerschlingungDatenbankQuick-SortData DictionaryMailing-ListeServerBefehlsprozessorThreadProzess <Informatik>RechenschieberGeradeProgrammbibliothekWort <Informatik>Ein-AusgabeCodeEinfach zusammenhängender RaumTypentheorieExogene VariableRandomisierungTaskVirtuelle MaschineRechter WinkelOffene MengeE-MailErwartungswertSoftwareFront-End <Software>Konstruktor <Informatik>DifferenteDemo <Programm>DatenparallelitätAdditionStabMAPDatenkompressionSchnittmengeZweiÜberschallströmungUnrundheitCookie <Internet>DateiformatDienst <Informatik>Kartesische KoordinatenGewicht <Ausgleichsrechnung>TesselationAuthentifikationInhalt <Mathematik>Einfache GenauigkeitDrucksondierungFlash-SpeicherMessage-PassingFormation <Mathematik>
08:31
SpeicherabzugZufallszahlenMigration <Informatik>AdditionUmsetzung <Informatik>PunktwolkeMaßstabVersionsverwaltungÜberschallströmungDifferenz <Mathematik>Konstruktor <Informatik>KoroutineFunktion <Mathematik>Urbild <Mathematik>Exogene VariableKonfiguration <Informatik>FehlermeldungAuswahlaxiomArithmetisches MittelParametersystemAdditionFlash-SpeicherGraphiktablettDifferenteInverser LimesApp <Programm>PunktwolkeDatenbankRoutingExogene VariableKartesische KoordinatenVersionsverwaltungCodeKonstruktor <Informatik>Konfiguration <Informatik>Objekt <Kategorie>Umsetzung <Informatik>Projektive EbeneTypentheorieMigration <Informatik>Lesen <Datenverarbeitung>QuaderSystemaufrufDatenverwaltungSoftwareentwicklerRadikal <Mathematik>InjektivitätFunktionalVerschlingungBitKlasse <Mathematik>FehlermeldungMereologieData DictionaryAggregatzustandInhalt <Mathematik>InstantiierungDefaultMathematikVariableEin-AusgabeBenutzerbeteiligungAusnahmebehandlungHyperbelverfahren
16:33
Exogene VariableKontextbezogenes SystemClientSoftwaretestÜberschallströmungOBDDE-MailDifferenz <Mathematik>CodeInformationsverarbeitungLoopEreignishorizontBefehlsprozessorPaarvergleichp-BlockFramework <Informatik>SynchronisierungHybridrechnerInjektivitätZeitabhängigkeitUmsetzung <Informatik>App <Programm>AdditionCoxeter-GruppeEreignishorizontBitLoopProzess <Informatik>BefehlsprozessorSoftwaretestDatenkompressionGrenzschichtablösungDatenparallelitätArithmetisches MittelBenchmarkProjektive EbeneFramework <Informatik>ElementargeometrieDefaultRoutingUnrundheitBenutzerbeteiligungCodeEvoluteObjekt <Kategorie>Attributierte GrammatikDatenverwaltungDifferenteSoundverarbeitungHybridrechnerResultanteZahlenbereichKonfiguration <Informatik>RechenschieberMathematikKontextbezogenes SystemInjektivitätKartesische KoordinatenIntegralWeb-ApplikationTreiber <Programm>Dreiecksfreier GraphVersionsverwaltungProgrammbibliothekÜberschallströmungDatenbankElektronische UnterschriftClientSelbst organisierendes SystemCOMPaarvergleichExogene VariableDifferenz <Mathematik>ServerMultiplikationsoperatorInformationsverarbeitungOrdnung <Mathematik>EntscheidungstheorieMereologieProgramm/Quellcode
24:36
Gemeinsamer SpeicherTouchscreenProdukt <Mathematik>Stabilitätstheorie <Logik>BenutzerbeteiligungAbstimmung <Frequenz>ÜberschallströmungBesprechung/Interview
25:05
Lambda-KalkülFunktionalDatenbankGeradeSnake <Bildverarbeitung>Kartesische KoordinatenKrümmungsmaßLaufzeitfehlerRoutingAdditionMixed RealityBitDifferenteWort <Informatik>ProgrammierungBenutzerbeteiligungLoopMomentenproblemEntscheidungstheorieEreignishorizontImplementierungStabilitätstheorie <Logik>Chatten <Kommunikation>Produkt <Mathematik>Coxeter-GruppeMereologieBenchmarkParametersystemEinfach zusammenhängender RaumFramework <Informatik>KontrollstrukturZoomBesprechung/Interview
Transkript: Englisch(automatisch erzeugt)
00:06
You want to present today a talk that shows us how to go from synchronous Flask to asynchronous Sonic, is that correct? Pretty much, yeah. Pretty much it. I'm really looking forward to this.
00:21
So if you have your screenshot ready, then please activate it and I'll give you the spotlight and hope you have a great talk. Hi everybody, my name is David and today we're going to talk about Flask and Kuwait. Before we get started, I'll tell you a few words about myself. I'm a husband, I'm a
00:42
father of two boys, I'm a Pythonista for the last decade or so, and I'm a scale nerd. In addition, I recently started working for Nvidia as a software architect. Before we get started, I want to set up expectations. This session is more of a mind opener on how added value can be provided with minimal effort.
01:09
This session is definitely not about saying that this technology is good, or that technology is bad. That's not the message here. In addition, I'm assuming that all the attendees have prior knowledge in web development and in REST APIs in particular.
01:30
So first, let's get started with some motivation. You can see the code on the left. The code on the left is pretty basic Flask code, and the code on the right is pretty basic Cinecode.
01:43
So if you take the code on the left, and you transform it to the code on the right, as you can see, there aren't that many differences, then you can get between 3 and 4x performance improvement, which is pretty nice.
02:01
Here you can see the results of my benchmark. The benchmark that I did is 10,000 requests with 100 concurrently level. As you can see, for Flask, this took about 750 requests per second, with Suneek around 2500.
02:31
The time per request correlated as well. For Flask, it's 130 milliseconds, and with Suneek, it's 40 milliseconds.
02:42
Some extra notes about my motivation experiments. For Flask, I installed simple JSON as an optional dependency, as recommended in their documentation. By the way, whenever you see this sort of link in my presentation, it is a link, so if you'll get to the presentation later, everything is linkable.
03:06
The tool I use for benchmarking is a tool called AB, which stands for Apache Benchmarking. So, a couple of words about Flask. Flask is a microwave framework that revolutionized how web is developed with Python.
03:25
By microwave framework, meaning that as opposed to Django, Django is what's called a framework, a framework with batteries included. Flask does not include almost anything by its own. Whatever you need, cookies,
03:41
authentication, whatever, you need to either implement yourself or use a third party. To my opinion, it revolutionized web in Python because of its simplicity, because of its major simplicity.
04:00
Async IO. Async IO is a library to write concurrent IO bound code using the async await syntax. What's IO bound code? IO bound code, for example, is making HTTP requests. An example for something that's not IO bound, or to be more accurate, something that's CPU bound is compression.
04:21
This line is important for the next couple of slides. So, why Async IO? What's wrong with, I don't know, thread per request or process per request? Currently, we consume more HTTP services than ever. We can quite easily reach 10,000 concurrent connections on a single server, aka the C10K problem.
04:52
As a result, a cooperative task that can better utilize the CPU on a single machine can save us a lot of money.
05:03
Sanik is a Python 3.6 and above a web server and web framework that's written to go fast using the async await syntax. Now, I would like to introduce to you to Pyaday. Pyaday is an application, well, it does several things.
05:26
It's a crowd for Python packages metadata that's used by its content curators, and you can get your daily random Python package in for, for front-end profit. So, here's the gist of it. When you call a route named run, then you get a response JSON with info on the random Python package.
05:53
And now you can learn new stuff pretty fast. I used HTTP Py for this demo, but you can use curl over there as well.
06:08
This is how the main of the app looks like. You call the Flask constructor and you register two blueprints, blueprints for packages and blueprints for run.
06:22
The way I look at blueprints when it comes to REST, I like to put in one blueprint all the HTTP methods of a certain REST resource that I would like to consume. So, if we're talking about my example, where we talked about packages CRUD, then
06:41
all the CRUD operations of the packages route will all be in the same blueprint. And the run route will be the same route on the run blueprint. Here you can see a couple of imports, and here you can see our glorious database, which is a list of dictionaries.
07:05
You can also see we're in the blueprint here. Now, let's go through the CRUD methods. Let's start with the C, the create. Whenever there is a post to packages, then I add the new package to our database.
07:23
And as accustomed in REST, I return a 201 status code, an empty response, and I return a reference to the newly created package on the location header. Moving on to the R, the read. Here you can see a GET request, where we get a package name as input.
07:47
We check for the package with that name in our database, and if it exists, we return a JSON response with it. Otherwise, we return a 404 response with an explanation and a relevant content type.
08:05
Moving on to the U, the update. Here you can see there is a PUT method. It can also be a PATCH method. It depends on what you do. Again, we search for the package in our database. If we find it, we update it and return a 204 status code with an empty response.
08:26
Otherwise, as previously, we return a 404. And the delete method, again, gets the package name as input. If it finds it in the database, it deletes it and returns a 204 status code. Otherwise, it returns a 404.
08:47
And now you can see the RAND blueprint. Again, we need the blueprint, and there is a GET route that returns a random package from our database. So, why convert? If you have a large-scale expensive cloud deployment, or you have a limited resources on-premise deployment,
09:11
you could get a better bang for your buck, meaning it could save you a lot of money. In addition, we show you that the migration is not difficult, and all the flash knowledge accumulated this far is not wasted.
09:27
So, let the conversion begin. As a prerequisite, we need a project that could benefit from the conversion, meaning that it mostly does IO. And it's written in Python version between 3.6 and 3.8. I used Python 3.3.3.
09:50
The next step is not mandatory. It's my preference, poetry-init and poetry-adds-neat. Poetry is a dependency manager.
10:01
The great thing about it is that you get a lock file out of the box, so you never need to remember to add something to your requirements file. Again, there is a reference here. You could follow the link later. In addition, for the conversion, I used Flask version 1.1.2 and Sanic version 20.3.0.
10:26
So, if you're using other versions, then it probably means that syntax may vary a bit. So, let's get started. Let's get started with the constructor. Pretty straightforward. Instead of importing Flask from Flask, we import Sanic from Sanic, and we change the constructor call.
10:49
And that's it. Let's talk about routes. You can see, again, the decorator remains the same at app.rout.
11:04
But here you can see a couple of differences. On Flask, the request object is globally imported. And with Sanic, it is the first argument. It's a dependency injection. So, whenever you have a route, the request is always the first argument.
11:22
Another difference you can see here, that on Sanic, the route is a coroutine. A coroutine is a function that uses the async keyword, as you can see here. Now, let's go through the JSON response. There are a couple of options with Flask for the happy scenario.
11:42
You can either return a dictionary, or you can import JSONify and return the dictionary or as keyword args inside the JSONify. And it basically wraps the response with the response object and sets the content type, the minetype, and the status code.
12:05
With Sanic, there is one main way to go from Sanic response to import JSON, and you wrap your response dictionary with the JSON. And now for the not-so-happy part, or the end link, there is this paper called RC7807 that talks about how errors should be handled in web APIs.
12:36
So, my examples try to follow this RFC. With Flask, you import the response object, and then you instantiate the response class.
12:48
You give it a status 404, content type of application problem plus JSON instead of just application JSON. And you give it a response body. We have to dump our dictionary, so this is what we do here.
13:06
With Sanic, it's pretty much the same. We return the same response.json object, we give it a body, change the content type, and give it a custom status. There are other options for Flask as well. You could also use the JSONify that
13:20
we saw earlier and change its status because its default is 200 and return it. Auto-reload is a great feature for development purposes. For Flask and Sanic, happily, it's the same. There are other options as well. For instance, for Flask, you can, from your terminal, init a virtualenv called flask underscore env.
13:51
And if you set it to a development, then you get auto-reload. And with Sanic, you could start the app with a parameter called auto-reload equals true.
14:03
And then you don't get debug variables, outputs, or whatever. You only get the auto-reload that you need for development. Blueprints. Again, Blueprint is used for subrouting. It contains all the exposed methods of certain routes.
14:22
The main difference here, except for the import path, is that Sanic does not require an import name as an argument to the Blueprint constructor. In addition, when you register the Blueprint to your app, with Flask, you call register Blueprint, and with Sanic, you call Blueprint.
14:45
You can use register Blueprint as well, but it is marked as deprecated, and I only wanted to give you the latest and greatest. So now let's look on how our app looks like post-conversion.
15:03
As you can see here, there's a difference with the import paths, and for the Blueprint constructor, you don't need the import name. For the create method, you have the async keyword, you have request as the
15:21
first argument, and instead of initializing a response object and set the response to none, you call from response dot empty, and that's the main difference here. For the read, instead of returning the JSONify, you return the response to JSON. We saw it earlier.
15:45
Request is the first argument, but we're not using it, so I put an underscore instead, which is how you mark an unused variable in Python that you must pass.
16:01
In addition, you have the async keyword, and where we talked about this response type. For update, again, you have the async keyword, the request as the first argument, and again, instead of returning a response object with response as none, you simply return a response dot empty.
16:23
And for the for code, instead of returning a response object with JSON dance as response, you return a response dot JSON with your dictionary as body, and with delete, it's pretty much the same, so I won't repeat myself again.
16:43
Now, let's talk a bit more. Let's talk a bit about testing. The main difference here is that with Flask, you get the test client through a method and a context manager. With Sanik, you get it through an attribute. When you call server methods, with Flask, you get the response as object,
17:06
and with Sanik, you get a request and a response as object. Again, we're not using the request here, so I put an underscore instead. Another difference is that with Flask, you check the status code by an attribute called status underscore code,
17:24
and with Sanik, the attribute is called status. Here you can see the testing diff. You can see that apart from the changes we talked about at the previous slide, everything is pretty much the same, including how you pass a body to the post request.
17:44
You see your JSON that gets a dictionary. You can see that everything here is pretty much the same. There is another option. Sanik tests can also be asynchronous.
18:00
For this, you need to add an additional package called pytest-sanik. If you do that, there are a couple of differences. You get only a response as a result and not a request and a response, but you need to await whenever you call the server.
18:25
Other than that, it is the same. Okay, so let's talk a bit more about deployment. Throughout my presentation, I tried to use recommended stuff by the creators themselves
18:44
and not use other stuff, so here as well. I used GUnicorn to deploy my app. For Flask, I used the default workers. For Sanik, I used the Uvicorn workers.
19:00
I repeated my benchmark from the beginning, meaning I used AB and 10,000 requests with concurrency of 100. For the round route, we got between 5 and 6x performance. As you can see, the request per second instead of 1065 hundred.
19:26
The time per request instead of 98 milliseconds, it's now around 15 milliseconds, which is pretty awesome. So everything sounded already pretty awesome so far,
19:41
but it's not always a fairy tale, because now you have a cognitive burden, meaning that to get a performant and an effective synchronous code, the event loop must never be blocked, meaning whenever you do an IO, you should await it,
20:04
like we saw earlier for the asynchronous test example. Whenever you have something that's CPU bounded, like compression we talked about earlier, then it should run elsewhere. It could run in an executor or in another process,
20:25
but something that's CPU bounded should never block the event loop, otherwise the code wouldn't be effective. In addition, Sanik's ecosystem is not rich as Flask's ecosystem. It is noticeable on GitHub.
20:42
You can see the awesome Sanik project has around 9,000 stars and the awesome Sanik has around 250. It is also noticeable on the number of available tutorials and on third-party integration for stuff that is pretty useful for web application like Swagger CodeGen, Auth0, or Okta.
21:08
In addition, when you're using a third-party library, you need to use its version that's not blocking IO, meaning that if you're used to using Psycho PG2 as your process driver,
21:27
you cannot use it anymore because it will block the event loop and you need to use Async PG or AIO PG. Instead of using request, you can use HTTPX or AIO HTTP.
21:42
Instead of using Redis, you can use AIO Redis or Async AIO Redis and you get the picture, you understand where it's going. By the way, that's the reason I didn't use a database for my application. I wanted to make my comparison simple and fair
22:02
and I didn't want the database driver to have a part in the decision of what happened, of why there are performance differences.
22:21
Now I'm going to talk to you about the Async web framework landscape. I chose Sanik for this talk for several reasons. It's very popular on GitHub. It has around 4,000 stars.
22:43
In addition, the API it exposes is very similar to the API Flask exposes. When the API is different, it seems like a reasonable evolution that made possible because there wasn't too much of a need for backward compatibility.
23:01
It is also backed by a community-run organization and it's, at least for me, it's a flashback for the 90s. There is this Sanik the Hedgehog. In addition, there is QUART that its creator talked yesterday, Philip Jones.
23:26
It's also a Flask-like Async web framework. In addition, there is Fest API. I call it a hybrid web framework because it supports both synchronous and asynchronous
23:44
with dependency injection as a guiding principle. You don't have a global context that contains everything, but each route that needs to get what it needs to get, gets it in its signature, which is pretty awesome.
24:06
Let's conclude. When a Flask application that mostly performs IO becomes resource-hungry, it is worthwhile to convert it to a Sanik app and the effort for it is reasonable.
24:21
After you convert the app, the code must be IO and CPU aware in order to not block the event loop. That's it. Thank you very much. Any questions? Yes, we have questions.
24:40
Let's first see. Maybe you can stop your screen share unless we need to share more things. We had a lot of votes for people who came in late. I have to ask you that question again. Is Sanik production stable in your opinion? Yes, I use it for very focused internal web products that only did IO.
25:11
For that, it was very stable. Okay. People are asking what's the main difference between Sanik and FastAPI,
25:23
if you have any experience with that. Yes, I'm a bit less experienced with FastAPI, but with Sanik, there is an event loop and everything needs to be asynchronous. With FastAPI, you can have a mix-in. You can have several routes that are synchronous and several routes that are asynchronous.
25:45
In addition, whenever, let's say, if your Flask application or Sanik is the same and you have a database, then you hold it in your contacts and then you get the contacts. For FastAPI, the database connection or session or whatever is an argument to the route.
26:07
That's the main difference there, at least the way I see it. In your benchmark you showed for Sanik, it looks like less bytes were transferred. Could this explain the part of the speed increase you noticed?
26:26
I think I'll recheck my presentation again, but I think it might be either an end line or something that depended by something. It was pretty obvious that for IO, it can perform better.
26:47
Okay. Somebody says, without mentioning his name, because it says anonymous attendee. Maybe it's a stupid question, but I've never used Asynpio. If I'm using a serverless function, for example, Amazon Lambda,
27:05
there is no advantage in being asynchronous. Is that correct? Do AWS functions, have they be synchronous or does this give me any advantage?
27:21
I think it depends on their implementation, but I think that with AWS Lambda, you're paying for your execution time. If it reduces your execution time, then you can pay less.
27:41
I think it's worth benchmarking before making a decision. There's not really a reason to be asynchronous otherwise. Maybe this was a stupid question, but actually there are not supposed to be any, so thank you for that question too.
28:02
The last one I have here at the moment is a question, if in the programming, do we always have to use the word await if the function is asynchronous at all? Or is there others where you don't have to await? You always have to await if you don't want to block the event loop.
28:22
That's how I think IO works. Python has had and has other frameworks that work as well. I wrote an async program in Python 10 years ago, and I didn't have to await anything, but for async IO based,
28:43
you have to await everything. Again, because otherwise you would block your event loop and you'll get performance degradation that you cannot explain. This is all the questions we have in the Zoom chat,
29:00
and there has been a rather large discussion in the talk, Flassenck Await Discord channel. If you have any more questions, please go over to that channel. David, I think it would be nice if you could join that for a while so that people in their lunch break maybe ask some more questions.
29:22
I learned a lot. I'm really happy for your talk, so let's thank you about this.