Django LiveView vs Phoenix LiveView: a real benchmark

I was curious: how does Django LiveView hold up against the original Elixir implementation when you compare them under identical conditions? Not with theoretical arguments, but with real numbers.

I built an identical alert dashboard in both frameworks: add, delete, and search alerts in real time. The benchmark is automated with Playwright headless Chromium, measuring the time between the user action and the DOM change, plus the bytes sent over WebSocket per interaction.

The source code is in the repository and is fully reproducible with Docker Compose.

Let's look at the results.

The stack

Component Django LiveView Phoenix LiveView
Language Python 3.12 Elixir 1.17.3 / OTP 27
Framework Django 6.0.5 Phoenix 1.7
LiveView django-liveview 2.2.0 phoenix_live_view 1.0
Server Uvicorn 0.47.0 Bandit 1.5
WS layer Channels 4.3.2 + Redis 7 BEAM (built-in)

Common scenarios

I ran 10 iterations (2 warmup) for add, delete, and search on a list with a few items.

Latency — common scenarios

Scenario Django LiveView avg (ms) Phoenix avg (ms)
Add alert 21.91 22.69
Delete alert 21.71 22.44
Search / filter 6.46 7.97

A complete tie. Both frameworks land in the same range: ~22 ms for mutations and ~7 ms for search. The difference is statistically irrelevant.

Latency distribution

Edge cases

This is where things get interesting.

Latency — edge case scenarios

Scenario Django avg (ms) Phoenix avg (ms)
Large list (500 items) 52.84 49.70
Rapid fire (5 clicks) 270.42 274.36
Empty search 6.56 8.16

With 500 alerts loaded, Phoenix is slightly faster. Rapid fire is practically identical.

However, the most striking data point is the payload:

Data received per action

Scenario Django receives Phoenix receives
Add alert 5,343 B 1,236 B
Delete alert 3,419 B 735 B
Search / filter 4,345 B 698 B
Large list (500 items) 327,495 B 67,131 B
Rapid fire (5 clicks) 60,822 B 12,466 B

With the large list, Django LiveView transfers 327 KB per action versus Phoenix's 67 KB. Every time you add an alert with 500 already loaded, Django LiveView sends you the entire table again. Phoenix only sends the new row.

It's a design difference, not an implementation flaw. In Django LiveView you explicitly define which selector to update and with what HTML:

@liveview_handler("add_alert")
def add_alert(consumer, content):
    alerts = list(Alert.objects.all())
    html = render_to_string("components/_alerts_table.html", {"alerts": alerts})
    send(consumer, {"target": "#alerts-container", "html": html})

In Phoenix LiveView you update the assigns and the framework computes the diff:

def handle_event("add_alert", _params, socket) do
  alert = Alerts.create_random_alert()
  {:noreply, assign(socket, alerts: [alert | socket.assigns.alerts])}
end

Phoenix knows which part of the template changed and only sends that. Django LiveView has no such mechanism, so granularity is up to you: point to a small selector and you send less, point to a large container and you send everything. Does this matter? On slow connections or with many concurrent clients, yes. In an app with few users or small lists, it will be indistinguishable.

Concurrency

What happens when multiple users act at the same time? I launched between 1 and 50 simultaneous clients all firing the same action together, synchronized with a barrier so they all start at once.

Latency under concurrent load

Clients Django add p50 Phoenix add p50 Django search p50 Phoenix search p50
1 18.4 ms 21.4 ms 10.5 ms 13.6 ms
5 24.6 ms 29.3 ms 17.1 ms 23.2 ms
10 56.0 ms 52.8 ms 31.4 ms 38.0 ms
25 239.9 ms 85.8 ms 85.5 ms 109.2 ms
50 483.0 ms 243.8 ms 303.6 ms 326.6 ms

With few clients (1-5) both behave similarly, with Django even slightly faster in p50. From 25 simultaneous clients the gap becomes clear: Phoenix scales better, especially on add_alert where at 50 clients Django doubles Phoenix's latency.

Conclusion

The data answers my original question: yes, Django LiveView holds up against Phoenix LiveView. In day-to-day operations they are practically equal. Phoenix's advantage shows when the payload grows or when concurrency is high, although these are problems you can mitigate with careful design in Django LiveView by targeting more specific selectors and distributing load.

The advantage of Django LiveView is staying in Python and the Django ecosystem without leaving what you already know, and that Django LiveView is more explicit and predictable: you decide what HTML to send and where to place it.

I'm pleasantly surprised.

PS: You are looking at a site built 100% with Django LiveView, I invite you to push its limits.

Este trabajo está bajo una licencia Attribution-NonCommercial-NoDerivatives 4.0 International.

¿Me invitas a un café?

Comentarios

Todavía no hay ningún comentario.

Escrito por Andros Fenollosa

mayo 15, 2026

4 min de lectura

Sigue leyendo

Visitantes en tiempo real

Estás solo: 🐱