Async Django: The practical guide you've been **awaiting** for.

This video features Carlton Gibson at DjangoCon Europe 2022 in Porto, Portugal.

Async Django: The practical guide you've been **awaiting** for.
0:42:43
Published October 17, 2022
8,155 views

Async Django: The practical guide you've been awaiting for by Carlton Gibson

There’s a lot of excitement about Django going async in 3.0+ but also many questions. This talk will provide a brief introduction to async, cover its pros/cons, and show how to build async into your Django app.

Summary

Async Django does not require understanding every low-level detail of asynchronous programming. Carlton Gibson explains Python’s event loop, `async`/`await`, task gathering, and why `create_task()` is not a substitute for a real background-task system with retries and error handling. He shows how async views can aggregate concurrent HTTP requests even under WSGI, then builds a message board using polling, long polling, server-sent events, and WebSockets, explaining when each approach is appropriate and how Channels bridges Django to ASGI. His practical advice is to use polling when it is sufficient, choose streaming or WebSockets for responsive real-time features, and deploy conservatively—often keeping the main application on WSGI while isolating experimental async endpoints behind ASGI and testing every proxy and connection layer.

Key takeaways

  • `async` functions pause at `await` so the event loop can run other I/O-bound tasks, but the event loop must remain alive for scheduled tasks to finish.
  • `asyncio.create_task()` provides no built-in retries, status tracking, or robust error handling, so dedicated task queues are usually better for background work.
  • An async Django view can aggregate several HTTP requests concurrently and can be added to an existing WSGI application without converting the whole project.
  • Polling is often the simplest solution; long polling and server-sent events provide more responsive one-way updates, while WebSockets support two-way communication.
  • Channels supplies channel layers and Django-friendly consumers for communicating between connections and implementing real-time features.
  • ASGI deployment is still more operationally complex than WSGI, so async endpoints may be isolated behind ASGI and infrastructure should be tested with both active and idle connections.

Summarised automatically from the transcript.

Chapters

  1. 0:00 Introduction to Async Django Carlton Gibson introduces himself, Django maintenance, and the goals of the talk.
  2. 3:07 Asyncio Fundamentals An introductory example explains event loops, coroutines, async and await, tasks, and gather.
  3. 6:57 Background Tasks and Event Loops The talk examines whether asyncio tasks are suitable for background work and why task failures need explicit handling.
  4. 9:17 WSGI and ASGI WSGI and ASGI are compared, including how Django runs asynchronous views and why event-loop lifetime matters.
  5. 10:49 Aggregating Views An asynchronous Django view combines concurrent HTTP requests to provide a single response for a mobile client.
  6. 14:47 Chat Application Setup A simple Django message board is introduced as a practical example for progressively adding asynchronous updates.
  7. 18:51 Polling and Its Tradeoffs The talk demonstrates polling with HTMX and discusses responsiveness, request volume, and scalability.
  8. 21:12 Long Polling with Channels Channels, channel layers, and consumers are used to hold requests open until a new chat message arrives.
  9. 31:10 Server-Sent Events Server-sent events keep a connection open for repeated one-way updates without reconnecting after every message.
  10. 34:01 WebSockets WebSockets provide bidirectional communication, and the talk shows how Channels supports them with a familiar consumer pattern.
  11. 36:35 Choosing an Async Update Strategy Polling, long polling, server-sent events, and WebSockets are compared according to scale, latency, and communication needs.
  12. 38:10 Deploying Async Django The talk closes with WSGI-versus-ASGI deployment advice and a checklist for testing servers, proxies, load balancers, and long-lived connections.

Transcript

7,842 words · auto-generated Show

Automatically transcribed, so expect mistakes in names and technical terms.

0:01

This is me. I'm Carlton Gibson. I'm at Carlton Gibson on Twitter and GitHub. You can find me there. I respond if you. Put that in and I get a notification. If you don't know me, I'm one of the Django fellows, so together with my colleague Marius who sat back there. Um We do the kind of day-to-day maintenance on the framework. We're contracted by the Django Software Foundation, which is the the governing body of Django to do things like tick ticket triage, pull request for a few, handle security issues, do releases, etc. We don't do any of that without an absolutely awesome band of contributors, which will include lots of you. But Django is a project that's so big that there's too much of it to be done just on volunteer work, so it's necessary that there's there's paid people to do that too.

0:48

So I always describe us as the janitors. We do the stuff that on the day-to-day basis keeps the framework going So, before I move on, I'm going to remind you that you can sponsor the DSF. Either individually or ideally, your company takes up a corporate sponsorship of the DSF. You make a small investment to help secure the continued development of the web framework that you built your business on. It's like having public liability insurance but for your tech dependencies. Okay When I'm not following, I help maintain various packages in the Django ecosystem. The relevant ones for today's talk are the channels trio, which was originally started by Andrew but which I took over a little while ago. There's channels Daphne and Channels Redis there We've got some new releases just pending. They're waiting for me to write the um release announcement, so there's a a a tail that needs to wag

1:36

a dog to get that done and then I'll get that out. I had hoped to get that out for Before the talk obviously I that didn't happen, obviously, but the next few weeks that will be that. Um when I'm not doing that, nothing to do with this short I've got a podcast with my friend Will Vincent, Django Chat. Um we get guests from the community and we chat about Django. If you haven't listened to that, please do. You know. Anyway, fine. Today's talk is about async Django. Async is one of those buzzwords. It's an insight exciting top topic, but I think there's lots of confusion over what it's really about and there are questions. What's all this async? What's it good for? How do I use it?

2:22

And so on. So I've called this the practical guide you've been awaiting for. Well, I thought the pun and then the title was irresistible. Now it's async is a totally massive topic and there's much more than I can talk about today. But it's also a massive topic that, as Katie mentioned in her keynote , we don't have to think about most of the time as as Django naughts. So what I want to do today is focus on some a few just a few examples of using it in Django that bring in the the bits of async that are relevant to us and we can leave the deeper stuff. to the the group of very clever people who spend a lot of time thinking about and pushing it forwards where which most of the time it's nice that we know it's there. You know, Katie said be aware of the the layer below. Well we need to sort of have an an overview

3:07

But we don't need to get in there as as Django naughts. We can do a lot of async and add to our Django applications without getting into the lowest level stuff where it gets it gets complicated, frankly. So My hope is that you leave with some ideas about using async in your Django application without making it too complicated. So let's go We're going to start with just a simple async I. O. example, just to bring in a few concepts and to set the seed. So first we can import this module async. io. Async. io is the Python standard library's implementation of an async runtime. It gives you an event loop and lets you schedule tasks, concurrent tasks that will run on it. Great. There are other implementations of async

3:53

runtimes available for Django, notably the Trio project. But as Django people as doing async Django, we don't need to think about those. Async. io is the main async implementation in Python. It's the one that's in the standard library, and it's what Django's async is based upon. Okay, at least for now. So with async IO in place, then we can define a couple of helper functions. The first one here, it just prints a dot every second And the second one will take a task name and a time to hang around for, and then it will just print when it started, wait for a bit, and then print when it finishes. They're not very exciting, but they let us demonstrate some things. A couple of things I want to highlight here is the C in the syntax. The first is the async and await syntax that are used to mark async

4:40

functions And to signal to async IO that we're ready to pause the function normally while we wait for I. O. but here we're just sleeping. And we give control back to the event loop at that time. So that it can do something else while it's waiting for a whatever our our awaitable was is doing. An async dev function is called a coroutine. And when you call it it doesn't actually run the code. It returns a helpfully named coroutine object, coroutine function, coroutine. It gets confusing, right? But the kick the coroutine object is the thing that you can await. You can say It go and run this event loop please. And when you await that coroutine object, your code is paused, the event loop handle is running the coroutine, and then sometime later you get the actual return value of your function and your code continues from where it left off.

5:26

Let's look at that. So here's our main function, and this is going to be the end of this first introductory example. First, we use the async. io create task method to schedule our print every second coroutine to run. Okay? And then in the loop we create five tasks, name them A to E, and um we give them a time to wait. And then really important step at the bottom, we gather those tasks to wait for them to complete. That's a bit like calling join on a thread if you've ever used that syntax Then at the bottom there we just use this async run method to create an event loop, start it and run it until our main function completes So that's the whole script, right? Yeah, you can still see that. So but that's the whole script.

6:12

When we run it, this is the output. Our our tasks A to E start, then our printer dot one gets a go, and then a couple of them finish, the printer dot one gets another one. It's interesting why the dot there pinishes kind of uh appears after the B instead of before the B, and it's because the the event loop has a kind of queue of task and it just goes through them in the order that they come up and runs them. And so Depending on which which order they end up in that list depends on which order they get um executed And so on. Let me just show you one more example. If if I comment out that um gather call, that one where I said it was important. Let's go back.

6:57

That when it says important, wait for all the tasks to complete. If I commy that out, this is the output. Okay? And then what happens is all our tasks start, but then the main function exits. And the event loop shuts down, and all our other tasks never get the opportunity to run. So our poor little print a dot every second, it doesn't get to do anything. So the key point here is we need to not exit our main function because we need a running event loop to execute our concurrent tasks. So thinking about Django The first question that comes up whenever the async word pops up is, so can I use this for background tasks? So a user signs up. I want to send them an email to confirm, you know, something like that, but I don't want to spend five to ten seconds with the browser not doing anything before I send the request.

7:43

So could we use async IO create task to send the email in the background? Well, here comes my old man answer. Well it kind of and it depends. Um So let's go through those in time. So can I use some background task? Well, kind of. The question is, is your background task going to fail? What happens if it goes wrong? Because there's no error handling built for you in Async IO create task. Um there's no statuses, there's no retries, there's no error handling, there's none of that stuff. So if you just fire off a coroutine to create task and it goes wrong You've got an awful lot of error handling that you need to write with that for that to be robust, where you could just use something like Django DBQ or Django Q or Celery or, you know, if that's your cup of tea

8:30

Now you're all adults, you can do what you like. And maybe in your code you don't need much error handling and maybe you know it doesn't really matter if this task just blows up. But Kind of you could use it, but no, not really. Maybe someone's going to build a f you know a task queue with retries and statuses and scheduling and all these things. built on top of async. io with another but it it would handle all that stuff. That's not code you want to write yourself. Okay? So kind of What about can I use it for background tasks? Well, it depends. It depends on how you're running Django. So there's one more thing we need to talk about, which is WISGI and ASGI. So WISGI Maybe you all know. Is the st is the old standard for running synchronous web applications in Python, so Flask, is

9:17

is a whiskey compliant framework. Django is a WISGI compliant framework, etc. ASGI, created by Andrew over there, is the Asynchronous sort of modern uh not modern, it's the asynchronous version of WIP. It's for asynchronous um frameworks. So um Fast API, Starlight, Starlight, Starlet, Django are all ACE uh ASCII compatible frameworks. And simplifying, a WISGI app gets the whole request at once and it returns the whole at once. It's kind of one shot. And then ASGI app has It's event-based and it has a pipe for incoming events and one for outcoming events. And so communication can be long-lived and it can be bit by bit and it can be two-way.

10:03

The point is that in in in general under Whiskey we don't have a running event loop. Django Allows you to write async def functions which will be run asynchronously with an event loop. But what it does is when when the the the handler when whisk whiskey handler finds a or when base handler finds an async death view it spins up an event loop just to run that it runs your view function and with the When it returns its response, it will shut down the event loop. So it's like the example where we didn't call gather. We could start a background task sure. But as soon as the the event loop exits, that task would get shut down. So it kind of depends.

10:49

You could run backdown tasks using create tasks, but only if you've got a running event loop. Okay? So that's the first example. And there are a few concepts there that we bought that we wanted to bring in. The event loop, async and awake syntax, that'll do. And gather. Those are the kind of Basics. Now as Django naughts, I want to give us one example that I think is very handy. I think this is one of my favourite patterns And so it's called aggregating views. And the the the what I actually call it is what we did before GraphQL was ever a thing. So them let me set that up You've got an app, and it's a

11:35

app for hotels. And so you've got a couple of models. You've got hotels and you've got rooms and prices, right? Then you have some basic DRF serializers for those. You've got hotel serializer, room serializer, fine. And you've got a couple of DRF views. So you've got hotel detail view and a room list view. And they all work great and they've got URLs. That's fine. Except your mobile team says to you we can't make two requests. We've got a view that needs a l a hotel and a list of matching rooms, and we can't make two requests for that. quite rightly. Mobile connections are slow, they um data is expensive, connections aren't reliable, they don't want the page to take all day to load. We what they want is a single view that fetches all the required data in one go. Now this is exactly the use case for which GraphQL

12:23

was um invented for But you don't necessarily need the whole GraphQL setup because you can have an aggregating view. Now this is it. Can we see that? I'm going to go through that bit by bit. So at the top, first of all we just import HTTPX, which is a asynchronous um requests library. asynchronous capable. And then we in import some other bits that we need for a JSON response. Then we define our async dev request handler. And from Django four point one, we can define async def handlers on on class-based views, on view subclasses, right? So we get a nice namespace and can decompose our logic into separate methods if we want to. If you did this with a function-based view, you could D-Nest that a level, and you could get rid of it, you could get rid of the class, you could get rid of the self, and you could just, you know.

13:11

remove the indent. But then you you that's up to you. You can do that. You get rid of the self-argument, but you wouldn't then have a namespace. You just have your module namespace to work on. So it's up to you which is more useful for you And then we get the URLs for the views that we want to aggregate. So we get the hotel detail view and the f and the filtered room list for that hotel. And then we can use HTTPX's async clients to fetch the URLs concurrently. The client get method isn't awaitable. It returns a coroutine when you call it. And we can use that with async. io gather again to wait for all the tasks. So that's essentially which of these requests is going to operate most slowly. Wait for them to complete and then we move on. And then finally we compose the response structure we needed

13:58

for our front end and we return the response. Okay? And that's what the front end team looked for. Now this is a pattern I've used for many years. Before async IO was around, before async Python, we used to use Node. js. Now we could have done it with Python threads probably, but Node. js was the thing that you you did, so we did it with Node. js. And you'd have a little Node. js service next to your Django app that would provide the aggregating endpoints. And now we just now we can just do it in Django. Which is better because you know, when you've got two texts, you've got two documentation learn two things, it's d You know, focus your energies. So the the best bit about this pattern I think is that you can do this with your existing WizardGee application right now

14:47

Because Django is capable under WISGI, under a synchronous running that you've used, your G Unicorn, whatever it is that you're using, to have these async dev views, and you don't need to change a thing. It will spin up the revent loop just for your this these these aggregating views and the rest of your application remains the same. So I think this is a really good A really good pattern. I then want to look at another example, which is a chat app. We're going to look at it four ways. And the goal here is to see is to see how the need for async comes in, some different considerations as you go through. So let's the setup. We're going to build a simple app that lets you post messages to a single list, like the old guessbooks you might see in the late 90s, you know, where you've got a

15:38

you know just a a wall where people post their messages. The channel's tutorial has got a multi has got a version which is multi-room and you can have different rooms and it it's worth checking that out in contrast, but what I want to focus on here is how we take we we start with a very simple synchronous thing and then we add various options on async to to change it in various ways. So let's have a model. We've got a message and we've just got sort of a text field by who it's posted by. We're not going to bother with user and all the rest of that When it's updating, we just can order by its credible. Let's have a form and a filter. So um we have a Django filter um Django filter filters filter set there so we can filter by um since. There's a little trick here with Django

16:24

filters that nobody likes to create it at as the as the URL parameter in Django filters. So you can call the filters the the URL parameter since and then you just say which field name it points to. People like. And then we just have a message form, which is just a simple Django model form that will take the fields for the posted byte and then the text that people want to post. Let's have a view. This is just a list for a Django list view, class-based list view. Let's go through it bit by bit. At the top, just you know, template, context object name, ordering model etc. For the get query set, we just get the we you call the super method, we just run it through the Django filter set in g if in case this the since parameter was passed, and then I'm just limiting it to the first 30 records, just you know

17:14

If there's 400 messages, I don't want to get the whole lot. I just want some. Okay. For the context data, um We're gonna see if the the only interesting thing is is for the message form we're gonna give it some initial data if the um if the posted buy is in the session because what we're gonna you don't want to have to type in your name every time you want to type it in once and forget about it And then for the get template names, we're just going to use HT um hang on, HTTPX, no, HTM now. Alex, which is the what was it called? Ah HGMX. HGMX, that's the one. We're gonna use that. Sorry. I went to the party too. Um We just can use HTMX if we want that.

17:59

Why not? Because that's great. That's it. That's quite a basic list view. And then we're going to add two bits, yes. We're going to add a um post message to create the messages, which is just again standard create view based on Django's generic class-based views. There's two bits I wanted to call out there. One is that the success URL, we're going to just redirect back to not the object detail, but back to our list view. So we've got this, the message board. We just from the post view we don't go to a detail for individual messages, we just see the list each time. And then the second bit was that if if they did po if the user did post, did put a posted buy-in, then we will save that in this in the session. Okay. So

18:51

We're going to look at four ways that you would could up you know you could deal with this if you want to update. I want to know I've got it I I've got my message list open in the in the browser and I want to Um know when I get when it's updated. If someone else posts a message, I want to see that. And so how do I do that? The first way I could do it is is by polling. I could request um every five seconds, every ten seconds, and s check to see if there are new messages. And if there are, I can update the the HTML in the browser. So HTMX yes, thank you. Does this for us in a you know so I just put a check a checkbox here, type checkbox, I hit the list view. Um because it's

19:36

an HTMX request, it will just send the the formatted bit that I want to swap in, I give it a target, I say look, I want to go and replace the message list ID DOM element and a trigger that When I check the checkbox start and then every five seconds send another request. Right now that might well be all you ever need And that might be perfectly good. And if it is, stop there. Right? Well but but scaling. Right? What if I've got lots of Quark clients and they're repeatedly making requests? Now maybe it's big enough, but likely you would you wouldn't at some point it's gonna start

20:22

um getting a problem. What about responsiveness, you might say? Like five seconds is quite well, it might be a long time, you know, ten seconds might be a long time. I want my app to feel responsive, so if someone posts I want to reply straight away You know? Now I could poll more frequently. I could say, well, okay, let's poll every one second. But then that scaling problem becomes a lot more serious And so that there's a sort of a ratio between how long do your requests take and how often are you hitting them and how many clients have you got. And if that n if that sort of reaches saturation, you're in big trouble. Okay, so you're at that point you're essentially DOSing your own application. So if it's an internal user face, internal, you know, and it's only one user and it's only one browser session, yeah fine, polling

21:12

But if it's you know hundreds of users concurrently, that's not going to scale. So that's when we have the second strategy. And I want to introduce that, that's long polling And here we get bet beyond what we can do with Django itself, because we're kind of changing the schema of what we want to do. We want event-based or real-time updates, and that's not what Django was originally set up to do. We go from the traditional request-response model where we um get the the view gets the request and then it sends the response. to the second model which is event-based where the view gets the request, it somehow waits for an update, and then it sends the response. And we can't do that

21:57

under d with traditional Django views. There's no there's no real option for waiting. I mean You can synchronously block on a queue which is g uh no. So this is why this waiting this waiting bit, this need to be event-based, is why we need asynchronous It's um we need to respond to events and we need to be able to communicate between requests, between open browser windows, basically. For this we need the channels package, which again Andrew created. And this is a few parts. The ones I want to talk about today is a channel layer, and this is a way of sending messages between different connections. So you post a message, I want to hear about that, a chat and the channel layer is how we do that.

22:44

Essentially you have a Redis instance or something else and a message goes via that to anyone who's subscribed and you send out to a a group of a windows saying hey a new a new chat message was posted And then the second component that we need is consumers. And consumers are essentially user-friendly abstractions on top of lower level ASCII Okay, so we as Django developers don't want to spend our time worrying about the details of ASGI. It can it it's a bit gnarly and there are gotchas that we don't need to know. So on top of those consumers make but writing your async apps a bit more human, a bit more Django-like. Now all of this is in the channel's docs and if you fans follow the channel's tutorial you you can get the feel for it. But I want to go through an example and sort of show you how it comes up

23:30

So the first thing we need if we're going to update our message list, I've got my browser open, I want that to update, is we want to we need a way of notifying when a new message is posted. So let's we create a meth just a helper function to do that. And for this we use the channel layer. And basically we need to send a message to the channel layer and saying, hey, there was a new message So we um first of all we instantiate the channel layer, or we we we use this get channel layer function which gives us an instance of the channel layer. Okay. And then This is a synchronous function we're calling. We're writing it here, you'll see why in just a second. So we use the the wrapper from the um I should have put an import statement there. Anyway, but it's from the Asgiv

24:16

package, AsGUF sync. So it's async to sync helper. And that will turn an asynchronous function into one that we can call synchronously. And then the channel layer has the this concept of groups. So I want every browser window that's open to get the mess to be updated. So we have a group for that, and that's just called chat in this example. And then we send it this this um dictionary here which is the message itself. Now the key field there is the type. Every um Every event that we send to the channel to the channel layer has to have this type and um the Yeah, I'll come back to why. But uh how could the bottom line is consumers know how to map um

25:03

types the type of the message to the handler that's going to deal with them. So chick consumers will have a handler which is chat underscore message and they know when they get an incoming message, look for a handler that matches this message message type and dispatch it. That's the that's why the type is important. The message here doesn't matter. You can have key values, but the key the values have to be like essentially basic. They need to be serializable. They can't be model instances You can send model IDs, but not model instances, because you're going to pass them into a different thread and you you can't pass model instances across threads that will all go wrong. So With the notify message in place, we need to just make sure it's called whenever we create a new method. And we do that in our post

25:48

view. In the form valid method, we're just going to call tru um the notify message when the when a transaction ends. So we use the transaction on commit. Callback fun there. Because we might have atomic requests on, we might be inside of a um a transaction. We call if we call it just on form save Well the the the the the object won't be in the database. We'll send the um message all our consumer all our browser windows will go oh refresh can I have some gonna have some normal data and there won't be any new data and be like, oh what's gone wrong? It's because we didn't wait for the transaction to finish. Right? So that's a little Django gotcha. But And then we just call it.

26:35

Transaction on commit will fire when fire the notification when the transaction is committed, and that's it. We don't need anything else to that's the notification side. That's like, hey, an update's Ready to go. So then we need to listen to it. And here's where we need the consumers. So this is going to be a long, a long pole consumer, and it's a an asynchronous HTTP consumer. So long polling is just like polling. What you do though is you wait around. You you you don't you get a request And you you hold on, you just don't reply. You send some headers to say I'm gonna start replying. But you don't reply. And after a timeout, the client will normally disconnect after a particular timeout, or you might time out yourself. But

27:20

If an update comes, then you send the the response at that point. Okay. So the first thing we have to do is we're gonna we're just gonna set a um a sense date which we're gonna use later to say any messages created from when the request started. Then we can do We could get more clever, but we don't need to for the example. And then we add ourselves to the channel layer, to the same group, the chat same chat group. That we're we're sending the notifications to. And what that says is, hey, when a notification arrives at that group at the channel layer, please send me, please forward it to me so that I can be notified. And then when we disconnect, we need to remove ourselves from that channel there. Then we just need a helper meth method to um fetch new messages and to render them.

28:06

Now this is a sync method This is an important bit, okay? This is a sync method. We could use the ORM's new asynchronous query interface, but we're going to take the model interfaces that um the the model instances that we get back and we're going to render them in a template. Now template rendering is a CPU intensive operation. It takes time. And if we were to do that on the event loop in in our async death function, we would block that event loop and it wouldn't be able to handle any other requests. And so in sp template rendering is something you want to put off to a thread so that the event loop can carry on serving requests while the template's busy rendering over

28:51

here. But we can't pass RM objects through into a into a um into a thread, into another thread, in order to render them in the template. So what we'd have to do if we used the the asynchronous query interface is we'd have to serialize those model objects into dictionaries And then pass those dictionaries into our um into our function into our thread threaded function which is going to render the template. But that's a lot of work. It's much easier to just put the whole lot in one synchronous function. Fetch the objects and render the templates together and then call that once from the asynchronous view. So let me show you the calling the thing.

29:37

Here's our Here's our chat message. This is where we listen to the events. So if you recall, we sent that message of type chat. message And here the method name is chat underscore message. And the point is channels knows to take the message type and it to a you can't have chat. message as a Python identifier because that's not a valid identifier. So it knows how to moan the message type to a handler and call the right handler. So we will get past in that event with just the message string which we don't have. Then we use sync to async, which is did we use that before? Did we use the other one? No, we use the other one. We used async to sync. Now we're going to use sync to async, which enables us to take a synchronous function and then await it

30:24

execute it in a thread pool and wait for it to finish and then get release control back to the the event loop and wait wait for it to re-resume when the r when the response is available So we get the HTML from our message list helper and then we send the the body down to the client. We use the sent um the send Body method enables us to you know output the the response. The more body false bit there says and stop the response, close it. So this is long polling We get a response in, we wait for an update to arrive. When the update arrives,

31:10

we render the template, we fetch the data, render the template, send the response. Job done. The upside here is that we get a new message just as soon as it's posted. So it's responsive. We've solved that response to this. But um Yeah, yeah, okay. And the upside we're giving soon. Yeah. So rather than polling, we have a poll rather than having the polling interview uh interval we we get the response instantly That leads us on to the second option, which is server-side events, which are a teeny which is the same idea but a slightly different version on it. With long polling, when we get the chat message event, we finalise the response and that says the end of it. Let's close up And that's like the polling

31:56

example in the traditional request-response. We get the request, we get w we we send the request, we get the response, but there's just a little bit of waiting in the middle. But most clients will then go and immediately reconnect again. Alright, so they're gonna ask you for the are there any more updates now? What you doing now? What you doing? What you doing now? What you doing now? Right There's overhead to that. So let's assume that our even if our web server conducts requests at zero cost, which it doesn't, right, we've still got a cost because we're still adding ourselves to the to the channel layer, we're removing ourselves, every disconnect, all the rest. Server cent events is for that, well can't we just keep using the same connection sort of idea. And so, yes, we can

32:42

So here we have a the s the service and events version of the consumer, and it's kind of exactly the same. In handle we connect to the channel layer group exactly the same. In the disconnect we do exactly the same and we have exactly the same message list HTML helper that goes off to the ORM, fetches the the new objects and renders the template which we call by sync to async. The only difference apart i is in the the chat message handler, the one that when we get the ch when the when we send the notification from the from the form creation view gets called in relation in response to the update. We have the same sync uh sync to async call to the query ORM render template, and then we have to format the event to send

33:28

because server sent events can't have new lines in them Um because they're they're delimited by new lines. You get a kind of event injection if you didn't get rid of them. So we take all the new lines and we prefix them with this date each line in service and event in a in a in an event body has its data colon. line of stuff, data connot line of stuff. And so we just format it like that, we have add a couple of new lines and then we send it. It's almost exactly the same except At the end, we pass more body equals true. And that says, hey, I'm going to follow up with more here. Please don't close the connection. There's going to be another one next time. And so instead of having to disconnecting, reconnecting, and all of that, I just keep waiting for more and more.

34:13

And HTMX or whatever whatever you're using can take the HTML Put it into the DOM and then just wait for a bit more. And HTMX has an extension which handles all this for you. And it just goes in, you know, it's same attribute-based, target that selector. All done. Okay. If the connection fails, HTMX will take care of reconnecting, but in general we just keep the same connection going for the lifetime of the browser session, which is nice. So that was polling, long polling, server center vents, and then the fourth option is WebSockets, which might be the one you jump to straight away, because it's very popular and the library support and things like this. It's what you're going to want to use if you're building SPAs

35:01

or you want to keep sending data back from the client. Because the difference between what the examples we've talked about thus far and WebSockets is WebSockets allow two-directional. um data. So for this example we're not going to rewrite our post view to send the the the the the new message via we're just going to keep using the forms we've already written it and there's Django nauts, we're not going to read like that. So something more complex, we're just going to use it. But in principle you could. And if you're using you know JavaScript libraries, they're they'll do that. HGMX again has a WebSockets um extension which you can just you know, wheel out on use. Okay, let's look at it. So here it's exactly the same again as the Q. And this is the as Django developers, this is the genius of channels and the channels

35:49

consumers is that they have this nice Django friendly pattern and it applies to all these different methods. With all I've done is change the import and change the superclass and then I've got this chat message in sim implementation Which is, oh look, I call the self-message list HTML helper in the SYNC2ASync so that it can go and query the new models, render the HTML, give me back the HTML, and then I can just use on the WebSocket consumer, I can just use self. send and say, hey, we're sending text data here. Because you can send binary data and you know other things. And that's the four different ways, right? Again, HTMX has got that extension. So Which should I use?

36:35

Well polling is simplest, right? Um if you've not got a lot of clients, not got a lot of requests relative to your response time, and small delay doesn't really matter to you, you know, ten seconds is fine. I 'm I'm not You know, I'm not in a conversation here. I just want to know, you know, if someone scored. If it's ten seconds later, that's fine. My view is that the best software is the software nobody wrote. No software. So you've already got a Django app that just works. And you can add polling with that, you know Those few HTMX attributes and it's job done. Stop. But if you've got a lot of clients, lots of connections, and then that's when you want one of our other three. For Django 4. 2, if it's feasible, we'd like a story for streaming async responses in Django itself.

37:22

So at the moment you can write the aggregating view. the example I gave the async def and that returns the response but what you can't do is stream a response like the long polling requires or the server sent events required. So you need channels. But maybe if we can get it in, we'll try and get that that in for 4. 2. And then you just write with nascent geth, otherwise you can use channels. If you want real-time responses though, then you've got to use one of the async options. You've got to use long polling, you've got to use server-side events, you've got to use WebSockets If you want two-way, well then you've got to use WebSockets. If you just want to get something from the server, well you know, one of the other options is available. But if you want a you know a conversation on the same connection, rather than, you know A side path, you need WebSockets.

38:10

You might find that your chosen libraries support WebSockets already. It's quite popular, it's very established, and it might just be, oh, do you know what we'll just use we're using this library, it's just a change of import from channels. Let's use WebSockets rather than um you know uh a streaming HTTP response So the answer is hey it depends. Um right. I just want to talk to finish off a few thoughts about getting it online Okay. So at the beginning we said Whizgi or ASCII. Well, hang on, what about WISGI and ASCII? Uh at least for now. Right? Async is something that we're still working on. And I don't mean we in Django, I mean everybody's still working on it. The patterns aren't a hundred percent clear.

38:57

There are bugs and gotchas that you sort of have to learn the hard way, and it's still kind of a bit like the Wild West. Okay, compare that to WISGI, where we've got 15 years of rock solid experience to build on. Okay, every problem that could come up, kind of, already has and it's already been fixed. The scaling patterns are known And frankly, why wouldn't you use it, right? You can deploy totally on ASGI. It's not a problem, you can. Six feet up, right? They build the loud swarm um uh uh product which we used for the conference the DjangoCon conferences after the la the last few years. It's brilliant. It works great but six feet up are a very experienced teams team and they've got a lot of ops you know, capacity to make sure that it's working correctly.

39:45

Maybe you haven't got that. Maybe you don't want to spend the time it takes to n you know make sure everything's robust. Maybe the best thing for you to do is to deploy your core app with WISGI, as you exactly are, and then just have your async code in a in an ASGI app on the side and then you know Nginx can root the request just to the ASGI app. Keep it simple. Keep go to bed at night when you're on page a call knowing that the b uh at least the core of your app isn't going to wake you up and maybe it's only one or two endpoints that you know are experimental that might. So that's one thought. And then the second thought is double check everything. Most of the issue reports that I see on channels are um problems with the setup. Generally it isn't channels or

40:31

isn't Daphne, it's whatever you've got in front of that. Maybe your load balancer isn't configured for long live long -lived connections. Maybe your web server isn't using the right HTTP version, so the the WebSocket upgrade thingy bulb never quite works. Maybe that. Um the trouble with issues like that, I can't, you know, I can't help those issues, so they sit there forever unanswered, right? So build it out. Check it works with just your app. Check it works going via just the web server. Then check it goes via the like it works going via the load balancer. Check it with an active connection, one that's you know that's noisy. Then check it with an idle connection. Because you know a lot of people they oh it's all working fine, yeah, and then an idle connection, so

41:17

it just disconnects all the time. I can why Well, it's because the of a request timeout, you know, maybe at the low bounds level, wherever. How long do you want connections to be open for? Ask that. And then tests that they can stay open that long. I want them to be open for an hour. Okay? So have you actually got tests that they can stay open for an hour with your setup? Do you need to be sending periodic um lifetime events. Because a lot of this can be fixed just by sending a heartbeat every you know, your your low bouncer will kill your connection after 30 seconds. So 20 seconds send a You know, a heartbeat. Bam. And that keeps it alive and it fixes lots of problems. And then the third point for getting in the line is to have fun. Async is really interesting. There's lots to learn. I mean it's it

42:03

it's lots to experiment with. I think it's of it as programmer catnip. It's just like Right? So have fun. What you're waiting for. I'm Carlton Gibson, I'm your friendly Django fellow. I'm Carlton Gibson on Twitter and Git. Hope you can find me there. If you haven't listened to the podcast, do check it out, jjangochat. com. Hope you enjoyed the talk. I hope you've got a few ideas about whether whether and how you can add async to your Django application. If you've got any questions, really happy to talk to you through the rest of the conference. Thank you

Questions this talk answers

Can I use `asyncio.create_task()` for background tasks in Django?

Only with caution: `create_task()` provides no built-in retries, status tracking, or error handling, so a real task queue such as Celery is usually more appropriate. It also only works if the event loop remains running; under WSGI, Django shuts that loop down when the view returns, cancelling background work.

Discussed at 7:43

What’s the difference between WSGI and ASGI in Django?

WSGI handles a request and response as a single synchronous exchange, while ASGI is event-based and supports long-lived, incremental, two-way communication. Django can run async views under WSGI, but it creates an event loop just for the view and closes it afterward; ASGI provides a persistent async environment.

Discussed at 9:17

How can I aggregate multiple Django API requests into one response?

Create an async view that uses an async HTTP client such as HTTPX and `asyncio.gather()` to fetch the required endpoints concurrently, then compose their results into one response. This pattern can be added to an existing WSGI Django application without converting the whole application to async.

Discussed at 12:23

What are the ways to make a Django page update when new data arrives?

The talk covers polling, long polling, server-sent events, and WebSockets. Polling is simplest, while the other approaches reduce repeated requests and provide more immediate updates; WebSockets additionally support two-way communication.

Discussed at 18:51

When do I need Django Channels for real-time updates?

You need Channels when the application must wait for events, keep connections open, or communicate updates between requests and browser windows—capabilities that traditional Django request-response views do not provide. Channels supplies a channel layer for passing messages and consumers as Django-friendly abstractions over ASGI.

Discussed at 21:12

How do I notify connected Django clients when a new database record is created?

Send an event through the Channels channel layer to a group containing the connected clients, and trigger that notification with `transaction.on_commit()` so clients are updated only after the database transaction has successfully committed. Consumers then dispatch the event to the appropriate handler based on its message type.

Discussed at 23:30

Why should Django async consumers move database queries and template rendering into a sync function?

Template rendering is CPU-intensive and would block the event loop, preventing it from serving other requests. Putting the query and rendering together in a synchronous helper and calling it with `sync_to_async()` runs that work in a thread while leaving the event loop responsive.

Discussed at 28:06

Which Django real-time update strategy should I use?

Use polling when the client count is small and a delay is acceptable, since it is the simplest option. For many clients or immediate updates, use long polling or server-sent events; choose WebSockets when you need two-way communication.

Discussed at 36:35

How should I deploy async Django safely?

You can deploy the whole application under ASGI, but if async is limited to a few experimental endpoints, a safer approach is to keep the core application under WSGI and run the async endpoints in a separate ASGI app. Whatever the setup, test the application directly and through the web server and load balancer, including both active and idle long-lived connections.

Discussed at 38:10

Presenters

Note: We understand that names change, people change, and bodies change. We respect each individual's journey and privacy. If you have any concerns about a video or need us to remove content, please don't hesitate to contact us. We will handle your request with care and promptly address any issues.

More videos by Carlton Gibson

More videos from DjangoCon Europe