How Adding Two Brackets Made a Django API 8× Faster: A Real‑World Performance Debugging Tale
When a high‑traffic Django endpoint that returned a 7 MB JSON payload slowed dramatically, the author traced the bottleneck to a misuse of StreamingHttpResponse that iterated over the response string character‑by‑character, and fixing it with a simple list wrapper or HttpResponse yielded up to an eight‑fold speedup.
The story begins on a busy afternoon when the monitoring system flagged a sudden drop in success rate for the get_svr_info API, which returns information about thousands of servers. The legacy service runs on Python 2.7, Django 1.11, and MySQL 5.5, using a straightforward view that queries all servers, builds a dict for each, and returns the result via StreamingHttpResponse(json.dumps(result)). Because the response can exceed 7 MB, the high request rate caused noticeable latency spikes.
Initial Diagnosis
The author first suspected the ORM and dict construction: fetching 10 000+ rows creates a model instance per row, and each instance is converted to a dict, which is CPU‑intensive. Switching the query to Server.objects.filter(...).values(*db_fields) reduced object creation, and trimming fields based on the caller’s needs further cut work. A benchmark script measured the average latency before and after these changes, showing only a 1.12× overall speedup, far less than expected.
Exploring Other Causes
Next, the author examined JSON serialization and network transfer. Enabling gzip in Nginx compressed the 7 MB payload to ~400 KB, but total response time increased (TTFB rose from 1.07 s to 3.05 s, total from 13.67 s to 14.85 s). Local loopback tests showed a download speed of only ~573 KB/s, indicating the bottleneck was not the network but the server’s output path.
TTFB Analysis Reveals the Real Culprit
Measuring TTFB (time to first byte) and total time with curl -w showed TTFB ≈ 1 s while total ≈ 13 s, meaning the first byte was sent quickly but the body took a long time to stream. The author realized that StreamingHttpResponse expects an iterable; passing a plain string makes Python iterate over each character, resulting in ~7 million tiny chunks and massive overhead.
Correct Use of StreamingHttpResponse
According to Django documentation, a proper streaming response should provide an iterator that yields larger chunks, e.g., using a generator that yields JSON pieces as they are produced. The faulty code was effectively:
return StreamingHttpResponse(json.dumps(result), content_type="application/json")which iterates per character. The fix is either to wrap the string in a list so it is iterated once:
return StreamingHttpResponse([json.dumps(result)], content_type="application/json")or, since the response is not truly streamed, simply use a normal HttpResponse with a faster serializer:
return HttpResponse(fast_dumps(result), content_type="application/json")Final Results
After applying the correct response handling, the benchmark showed a 4.00× overall speedup, with the largest payload improving by 8.21× (13.6 s → 1.7 s). Smaller payloads also benefitted proportionally. The three main optimizations were:
Replacing StreamingHttpResponse(json.dumps(...)) with HttpResponse(fast_dumps(...)) (largest gain).
Using .values() to avoid model instance creation.
Trimming unnecessary fields based on the fields parameter.
Key Takeaways
The investigation highlights the importance of measuring TTFB to distinguish between server‑side processing and output bottlenecks, and warns that misusing streaming APIs can introduce hidden performance penalties that scale with response size. Even seemingly harmless code can become a "fake streaming" nightmare, turning a fast JSON generation into a slow per‑character dump.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Tencent Architect
We share insights on storage, computing, networking and explore leading industry technologies together.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
