DjangoMaxxing
Serving thousands of OpenTelemetry requests per second in one process
Paris Kasidiaris · Django Day Denmark 2026
Serving thousands of OpenTelemetry requests per second in one process
Paris Kasidiaris · Django Day Denmark 2026
/ˈdʒæŋɡoʊ ˈmæksɪŋ/ verb, informal
"We DjangoMaxxed it. We built a single-process OTEL server with Turso."
one process. one asyncio loop. no threadpool. no database server.
thousands of opentelemetry requests per second.
this is our performance gate
| scenario | result |
|---|---|
| mixed ingestion of logs + metrics + traces, 5 minutes | 1,100 req/s, 0 failures |
| ingestion of logs only, 1 record per request, peak | 2,395 req/s, p99 24 ms |
| ingestion of logs only, 10 records per request, peak | 1,536 req/s, p99 97 ms |
| ingestion of logs only, 10 records, 10 clients | p50 4.5 ms, p99 32 ms |
| search in logs by service, 3.2 million logs | median 24.9 ms |
| trace detail (161,051 spans) during 1,100 req/s ingestion | p99 43 ms |
macbook air m1 · docker desktop · load tester on the same host
pyturso for end-to-end single threaded io_uringwe built async single-threaded orm primitives without sync_to_async:
AsyncManagerAsyncQuerysetAsyncConnectionAsyncCursorthe price: no joins or
select_related(for now)
we rebuilt for single-threaded asyncio:
async def __call__(self, scope, receive, send):
match scope.get("type"):
case "lifespan":
await self.lifespan(receive, send)
case "http" if self.is_ingest(scope):
await self.ingest(scope, receive, send)
case "http":
await self.django(scope, receive, send)
def is_ingest(scope):
otel_default_paths = {"/v1/logs", "/v1/metrics", "/v1/traces"}
return scope.get("path", "") in otel_default_paths
body = await decode(receive) # 400 if it is not valid OTLP
committed = loop.create_future()
if not enqueue(body, committed): # the queue is full
return await respond(503) # the client retries later
await committed # the writer resolves it
return await respond(200) # raw data stored on disk
def enqueue(self, request):
if self.closed or self.is_full(request): # items and bytes are bounded
return False # the caller answers 503
self.waiting.append(request)
self.wake_writer() # the writer sleeps until this
return True # never waits for room
a request is admitted at once or rejected at once. Nothing waits in line for space.
one coroutine. One connection.
sync_to_asyncasyncio sqlite, via turso
django_tursoio_uring for asynchronous file i/oDATABASES = {
"fakos": {
"ENGINE": "django_turso.db.backends.turso",
"NAME": FAKOS.data / "fakos.db",
"OPTIONS": STORE_OPTIONS,
"CONN_MAX_AGE": None,
},
}
bounds = await Span.objects.filter(trace_id=trace_id).aaggregate(
first=Min("start_ts"),
last=Max("end_ts"),
)
Where does a trace of multiple spans start and end?
recent = (
MetricPoint.objects
.filter(series_id__in=series_ids, time_unix_nano__gte=since)
.values("series_id")
.annotate(recent_count=Count("id"))
)
counts = {
row["series_id"]: row["recent_count"]
async for row in recent.aiterator()
}
How many points did each metric report recently?
services = (
Service.objects
.filter(name_key__glob="*ceryx*")
.order_by("-last_seen")
)
async for service in services.aiterator():
...
glob is a lookup we added to Django SQLite-compatible backends.
__init____init__: −71% construction cost__init__construction cost in the profile: 0.216 s → 0.063 s (−71%)
@classmethod
def fast_init(cls, fields, values):
instance = object.__new__(cls)
for field in fields:
instance.__dict__[field.attname] = field_value(field, values)
instance._state = ModelState()
return instance
take up to 32 waiting requests (never wait for more):
a
synchronous=FULLcommit costs 0.8 ms (~1,260/s)
async def store_raw(self, batch):
raw_requests = [
RawRequest.fast_init(**request)
for request in batch
]
async with self.database.atransaction():
await RawRequest.objects.abulk_create(
raw_requests,
ignore_conflicts=True,
)
sync_to_async hops
dj11 superpackage including; aorm, django_glob and ssedjango_tursopoints.annotate(p99=PointQuantile(0.99)))Join for early preview 12 Oct 2026
Thank you.