 Yeah, first of all, thank you for coming. Let me ask you, how many of us have used Django, or used Django in daily work? Great, that's great. OK, I'll first quickly introduce myself before we start. My name is Sival Donchif. I'm from Bulgaria. I'm a technical team lead in Hacksoft, in a software company in Bulgaria. We're basically using Django a lot. And yeah, we aim to be up to date with the latest Django trading. And that's why the topic of facing Django came. It's not something new. We know it like three years ago with Django 3.1. But it's something that's still growing. It's not yet established as a standard. So when we had the icing Django, if you open the documentation, you'll see that. And then when I opened it, my first thought was, OK, it seems like I need to just put an icing keyword before my definition of the view. And that's all. It works. It really works. But then I said to myself, OK, I'm going to execute an ORM query. And I got that. And I was like, OK, I put this magic icing word to whatever it means before my view. Isn't it already an icing? Yeah, I knew nothing at that point. So let's first go through the terms. What's the difference between sync and tasting? Well, the standard pattern code that we use in Django is synchronous. It means that we define a sequence of actions that will be executed once at a time. The asynchronous code is another pattern. You basically define a set of actions that needs to be executed, not necessarily in the same order. And they need to be executed concurrently and potentially in parallel. So what's the difference? You can, if two things are going parallel, it means they're actually making progress at the same point. So we'll see that in the examples after that. So on our question, how many of you have used async Python? OK, great. I'm going to quickly go through this. So we basically have three main tools for async Python. We have other, but this is a treatment that we are going to focus on. We have processes, we have threads, and we have coordinates. So multiprocessing, what that means. It's basically a Python way to physically start a new process in the operating system that potentially could be handled by another CPU. So that's basically the way to scale and use your CPUs from your Python program. And it's really powerful for heavy calculations. Trading. OK, so trading in Python are not like trading in some other languages, like Java, for example. Normally, you'd use thread in Java to use another CPU and basically gain more CPU power. In Python, because of the global interpreter lock, you cannot do that because C Python is not thread safe. The decision had been made to introduce this global interpreter lock on top of C Python that basically prevents a single CPU or a single process to handle more than one thread. So it basically cannot scale and scale in CPUs, but you can unblock your IO operations. We see that. So yeah, there is a really good talk of David Bealy that he said that threads bind you the ability to stop and block, basically. So yeah, let's see an example. We have a simple function that only sleeps for two seconds. We use time sleep a lot in this presentation. You can think of it as equivalent to an ORM query or calling a third party. They're all IO operations. So if I want to execute this code five times, this function, normally I'll make a four-loop and just run it five times. If I want to make it concurrently and in parallel, I will then import this threading module, pass the function, then start all the threads, which will basically tell the operating system that it should start making progress on them, and then wait for them to finish. And we can see in the example that they're actually sleeping at the same time without each one blocking the others. So yeah, we have processes and threads. But since Python 3, we have a really good interface to another pattern of facing programming. And this is the asyncIO module and basically the async await keywords that came with Python 3. The key thing here is that you're getting exactly the same benefits that you would get with threads. But coroutines are faster than threads because of the implementation of the coroutines. And they run inside a thread. And if you run the coroutines in a thread, we call that async thread. And the other key thing is threads are cooperative. We'll see that in the next example, why? So how do you use that? First, you need to start your main function in asyncIO loop. Then each coroutine is a standard Python function, but it needs to be defined with this async word before that. And basically, if you want to call an async function, you need to await it. So yeah, here's a more complex example. You have the main function that's awaiting a coroutine function twice, which is calling a synchronous function, which is we don't have problem of that. You can actually call synchronous code in asynchronous context. But this is actually really dangerous. I told you that the coroutines are cooperative. And we see that in this example. We have two coroutines, good coroutines, which uses the asyncIO sleep. That's the time sleep equivalent in async context that doesn't actually block the entire thread. And you have a bad coroutine that uses standard time sleep that actually blocks the thread. And then we see we call the asyncIO gather, which is the Python way to say, I wait for all this coroutine concurrently. So what happens is that both functions sleep for five seconds. We have 1,000 coroutines starting. Then they wait for one coroutine that blocks the thread to finish and then continue. Basically mean if you have one bad coroutine, it could ruin the day for everyone, basically. So yeah, that's really dangerous. And the rule number one is don't block the main thread. That's the most important thing. And then we have the futures library. Library is the high-level interface that uses both protesters, threads, and coroutines. So if we have these three two links, let's say, what if we can actually call, can actually make the async Python and the synchronous Python to talk each other and make a blocking operation from the synchronous Python for example, from some legacy code that doesn't actually block the main thread. And we can do that. Let's say we want to interface that just a decorator, a magic decorator that puts this into event loop and doesn't block the thread. Let's say that decorator looks like this. It's a pretty complex. But what it does is basically runs the thread pool executor, which is class instantiated from the futures and starts a new thread, throws the function there, make an so-called future or awaitable objects that behaves like a coroutine. Then you can actually use it as a coroutine. But it's actually you're moving your code to another thread and waiting to finish the main thread. So yeah, just have in mind this example. We'll get back to it later. Oh, it's in Django. We're going to see this picture of what? I think it's really, really nice. This shows on a high level what's happening to the request response cycle in Django. That's how it normally looks like. We have an ng and x that usually behaves like a load balancer. Then the request goes to the withgi. Then it moves through a chain of middlewares. It hits our view with the business logic. Meanwhile communicates with the ORM. So the first bottleneck of introducing asynchronous behavior in Django is the views. And that's the tools that we have, processes, threads, and coroutines. Well, we don't really talk much about the processes because the websites and the web apps are by definition more IO heavily, not more CPU heavily. Normally, when you make an HTTP request, your website communicates with a database or calling a third party rather than making some calculations. So yeah, let's see what if we use threads in our reviews. This example, it looks strange. It actually works. You can actually start new threads and make an ORM query or send an email, then start for them to finish, and then start them, then wait for them to finish. But that's for hard to manage. Then how do you handle exceptions? I mean, that's the entire new thread. What happens with the transaction atomic? By the way, transaction atomic, the key point here is that the database connection in Django is thread bound. So if you start a new thread, you forget about the transaction atomic in the same context. And that's not good for data fetching, obviously. So it's clear that Django went with async IO for a deep behavior. We have this small example with this view. And that would work, but we don't have the event opening. That was the first problem when introducing these views. And that leads us to the second bottleneck, the whiskey. What is whiskey? Whiskey is maybe one of the best things that happens to Python. It's actually the standard that make Python suitable for web programming. It's used from Django, from fast API, Flask, any web framework, basically. It defined a single interface for that. But it's never made to be async. That was not the idea of it. So we need a new standard. We need the Ergy, asynchronous server gateway interface. And this is a code from documentation. Ergy is a pure tool successor to Whitgy. It basically adding an event loop implementation and provide an interface for coroutines. So we solve that. We have an interface for coroutines in the gateway interface. And on the other side, we have a way to define asynchronous view. But we have middlewares between them. How do we handle them? Here's how a standard middleware looks like. It's basically a higher order function that accepts a getResponse function that will handle the business logic. And the new thing here is that this function could be a standard Python function or could be a coroutine. So we basically need an if-else statement that checks if this is a coroutine. But the next problem is how does the Ergy knows if your middleware is already handling both coroutines and standard functions? Because it would be good if you have some not suitable middleware to get a warning about that. Well, we have decorators that comes out of the box from Django. And what they do is just attaching two new properties to your middleware. So if you imagine the Ergy server like a four-loop handling request, it checks the view, checks the middlewares if they're async-capable, and then pass the request. And that's actually the official example from the Django documentation. That's how it looks like to write an async middleware. But this decorator is more or less depending on the good will of the developers. So let's say you're installing a third party, which is making a blocking operation inside the middleware, like ORM query. And they didn't put the decorator. So what happens then, I'm telling you from my experience a few hours of debugging, is you're sending a request. You're sending a bunch of requests. And you know that you have async APIs, but they all behave as synchronous APIs. Because the middleware is like a funeral. It blocks the main thread. It doesn't allow the Django to be async. So yeah, just to have in mind. I'm bad experience. And yeah, this is my favorite part of the ORM. So imagine that you're a Django core developer and you have a project that's like 15 years old. And the most complex module inside is never made to be async. Where do you start from? You cannot start from scratch, obviously. So we don't have an easy way to do that, because the database of first of all database adapter is synchronous. So we need to prevent that. We need to do this error, basically saying to you, I don't know what you're doing, but please stop that. That's wrong. I mean, you're blocking the thread. And they do that, yeah, number one, don't block the main thread. And they do that. This is a code block from the podcast database backend. It applies to the MySQL and the other. But they have this AC unsafe decorator. It's basically telling you, if you're trying at this key point where the SQL query is triggered, if you're trying to do that into an event loop, just raise an error. And that's the example that we saw before starting with Django. So we actually have a way to make a blocking operation that is blocking operation inside an event loop, behave as a core team that doesn't actually block the main thread. And that's what they did, but they call it sync to async and async to sync. That's basically the core implementation. I mean, these guys that release the algorithm, that's part from the Django. It's an hour package, part of the Django organization. These guys are very smart. There are tons of checks and tons of validation inside. But the core logic says, if you want to use the ORM, put it into a thread, wait for it, and yeah. Async to sync is basically doing the opposite thing with similar implementation. So this is a more complex example of how it works. You can basically put this decorator to sync to async to a normal Python function. And you can await it. You can call a core team from it. You can decorate the core team like async to sync, which will make it a normal standard Python function. And that works fine. So what happens with the transaction atomic? We said that the database connection is thread bound. But if every time that you make some ORM query, you go to another thread, you still lose the ability to make atomic transactions. Well, the truth is that you can make them, but you must encapsulate the codes that make sense to be transaction atomic into a synchronous box and then call them with sync to async. You cannot depend on the standard way of making atomic requests to an entire HTTP request. You just have to pay more attention, which blocks actually make sense to be put into transaction. So yeah, it's increased complexity a little bit. And you can use this utility functions, sync to async, to basically all the ORM methods. But you need to put it into the method that actually triggers the query. So if you see the last example with the user.object.all, because the user.object.all doesn't make a query, it makes a query when you start actually iterating over it, you need to apply the function to the thing that actually triggers the iterator inside it. And that's actually, there was a pull request that was open in the Django repository for a few months or something. And it was merged two or three weeks ago. It's completely new. It will be released in Django 4.1, which puts an asynchronous version of almost each of the methods of the query set. And they add this A before the method. So we can await them. With the functions that are iterators, you don't need that, because you can define them both as standard iterators and asynchronous generated backset, basically. So yeah, this is really nice. If you wonder how it works under the hood, they wrap the function that trigger the query with sync to async. It's the same thing. But the good thing is that if they change the implementation, you're using the official Django API of the ORM. So they are free to change the internal implementation now. And you don't have to use this utility function. So yeah, what did we achieve with all of this? If we did our task to not block the main thread, let's say this simple view. This is just sleeping for 0.1 seconds. It's not doing something. What I did is sent 100 requests simultaneously to the API and see how it behaves. And this is the performance when you deploy your code with G. So what basically done is if you sent us 100 requests, it handled the first, handled the second, the third, the fourth. So yeah, you wait around for 10 seconds. If you use G, what happens is if you sent 100 requests, it's actually slower for everyone. It's actually slower because you block the thread. Each of them blocks the thread, and all of them wait. Because AdGy, by definition, is handling that's what they say in the documentation. It's async outside, but it's synchronous inside. They handle all the requests, and then it depends on the fact you are not blocking the thread. So let's say we rework our view as an async API. What happens is it's like 10 times faster or something. All of the only limitation is the threads that you could spawn to handle the requests, handle the if you use the standard time sleep. For example, if you repeat that with an ORM query. And yeah, having that in mind, you can also, but this is on our topic, you have a way to fine-tune how many threads you could spawn from the very beginning in the jungle that can handle the ORM interactions, and basically have a way to potentially concurrently make multiple transactional atomic blocks. So what we had, we have four phases to make our jungle framework async. The first one was with the AdGy support, which makes it possible to have a cortine interface of the chain. Then we have the middlewares. And this is maybe the slowest part, because it will depend on the ecosystem around jungle. All the third parties to migrate and make sure they're suitable for async, which many of them already did. Then we have the views. That just need to be a cortines. And we have the ORM that the current state is you can make it asynchronous from the perspective of the main thread, but you cannot simultaneously send 100 queries. And yeah, the point of these talks, and I hope I made it clear, was that we have a really good and stable framework. We knew that the future is async, and we need to get there at some point. We knew that back three years ago when jungle 3.1 came. And we have a plan to do that. You just need times to get there. Here's a list of useful links that explains the, by the way, we're going to share the presentation in LinkedIn and Twitter. So if it's interesting to you, you can check it later. And yeah, I'll be happy if you have some questions and thank you very much for the attention. Hello, thank you for your presentation. I have a question for ORM. Do we have some limitation for database in giants? Like will it work for my SQL, Postgres, for all of them? Okay. So I mean, you have some limitations in the simultaneous connection? Yes, yes. Well, there is, every database has a limitation how many connections could be made to it. You have a way to fine-tune that from the jungle. It's the so-called AdGy underscore threads setting, environment setting. So that basically tells the sync to async class to how many threads it should execute. It executed when you start the jungle. How many threads it will execute from the very beginning and wait for them to handle. Thank you. Hi, thanks for the presentation. I was wondering if you know, like in terms of the solution that was designed to have a sync ORM, how does this compare to like a SQL Alchemy that has also an async backend now, currently? I'm not sure how it handled it. I used SQL Alchemy like a few years ago. Not sure what to say there. Okay. Thank you. Hello. About the middlewares and their compatibility with async, there is fun fact, the middleware mixing that was supposed to bring very old middlewares into jungle to point out, I think, also adds the async function just by wrapping it in async to sync. So you can apply it to newer middlewares to make them async aware. Yes, yeah. Not really a question, just, I think. Yeah, yeah. Okay, I haven't tried that. But yeah, sounds like a good idea. Hi. Thank you for your talk. As a developer that has a non-async Django application and wants to move to an async one, do I have to go to every step of the way? Like I assume ORMs and VLCS and switch from the WSG to the A, like how does the middleware, like how does migration look like? Well, it depends on basically the state of the project. If you can move to, first step is obviously moving from WSG to ASG, then you need to make sure that you're first up-to-date Django, so your internal middlewares are handling coordinates. Then if you use some third-party middlewares, that they're actually doing that. And the next step is basically defining, I don't know if there is an easy way for existing views APIs, probably better ways will be to introduce new ones that could reuse the same logic from the legacy code by wrapping them with sync to async. Hi, thanks for the talk. Are there some benchmarks available comparing Django, run the traditional old way and the async way? No, what I use for the example was CLI2 called, hey, that you can tell how many requests are signed simultaneously and what portions. And what I did is just send a single portion of 100 requests to the view.