Writing a chat application in Django 4.2 using async StreamingHttpResponse, Server-Sent Events and PostgreSQL LISTEN/NOTIFY

Hi y’all

I just wanted to plug my blog post about using the async capabilities of StreamingHttpResponse in Django 4.2 to stream Server-Sent Events and use PostgreSQLs LISTEN/NOTIFY as a way to broadcast events to be streamed.

https://valberg.dk/django-sse-postgresql-listen-notify.html

Let me know what you think and if you can see any problems and/or good use cases for this kind of setup.

Cheers
Víðir

6 Likes

Thanks for posting this here @valberg! I think this is a super article! :star_struck:

1 Like

Thanks for sharing! I enjoyed the article.

1 Like

very useful for learning, thanks for sharing this!

1 Like

Hi @valberg, I think your issue regarding with cursor_factory should be fixed as of Fixed #34466 -- Reallowed setting cursor_factory in DATABASES["option… · django/django@73cbb37 · GitHub (still only in main) – well fixed in the sense that you can now again set cursor_factory in the settings yourself.

EDIT:// Actually this was backported to 4.2.1, the issue is that Django here simply doesn’t work with an AsyncCursor and probably will not in the forseeable future. Sorry for the noise.

1 Like

Yeah, I think that PR is the reason why the problem appears in 4.2.1.

The work-around is simple enough and just a small “annoyance”.

Hi Víðir!

Thank you for sharing this guide. I love the clever combination of HTMX, SSE, async and Postgres NOTIFY mechanism.

This solution & design can work great for a small number of people in the chat. But it may not work with say 10,000 online users.

Each online user requires an open connection to the database. Number of maximum connections depends on how beefy your postgres server is. For a basic instance it can be a 100 connections, for a beefy one maybe 5000.

Best regards,
Vik

Hi @valberg ,
thank you, this post came exactly when i was looking for implementing such!

a question for you: when running in dev mode (runserver) and changing the code inline, the server restarts (by design) and then this mechanism crashes with:

Traceback (most recent call last):
  File "/usr/lib/python3.10/site-packages/django/core/handlers/exception.py", line 55, in inner
    response = get_response(request)
  File "/usr/lib/python3.10/site-packages/django/core/handlers/base.py", line 197, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/usr/lib/python3.10/site-packages/asgiref/sync.py", line 250, in __call__
    loop_future = loop_executor.submit(
  File "/usr/lib/python3.10/concurrent/futures/thread.py", line 169, in submit
    raise RuntimeError('cannot schedule new futures after '
RuntimeError: cannot schedule new futures after interpreter shutdown

have you experienced that? any solution?

thank you again.
E

Yes it definitely would benefit with some way of pooling - when I get the time I’m going to investigate how to make it more performant (if that is even possible).

That is sadly not something I have encountered - if you figure out a solution let me know!

Pooling is always a good idea. It’s enabled by default in most of driver implementations IIRC. But won’t help since NOTIFY subscriptions are blocking. Perhaps some pubsub able platform like Redis could do the trick, but it’s a whole different story :slight_smile: Although that can be an overkill if one is good with <5k concurrent users.

I’m very shallow-knowledge in Django, but it’s always happen after I save code, in dev. So it’s probably the dev server restarting after the save would kill something that does not come back up automatically. I’m not sure if that wouls manifest in production because the is no save in production, I have not gotten there yet.