Will Rewriting a Slow Python Web Service in Rust Make It Faster?

Half of each 100ms request in your Python service is spent waiting on the database. If a Rust rewrite makes the rest 10× faster, how fast does the request get?

Will Rewriting a Slow Python Web Service in Rust Make It Faster?
TLDR
  • A language rewrite can accelerate the work it changes. Unchanged database or API waits remain part of the request.
  • With 50ms of fixed waiting in a 100ms request, making the other half 10× faster produces a 55ms request—not a 10ms request.
  • Measure the bottleneck before choosing between less work, a native extension and a broader rewrite.

Faster application code cannot erase unchanged waiting time

Rewriting a Python web service in Rust can improve response time only to the extent that it accelerates work on the request's path. If a substantial part is waiting for an unchanged external operation, that portion remains.

In his October 23 essay, engineer Sean Goedecke argues for profiling and redesign before asking AI to translate a program into a faster language. His example puts half a web request's time in database queries. That is an illustrative assumption, not a measured share for every service.

Suppose the original request takes 100ms, including 50ms of database waiting that the rewrite does not change:

Application-code speedupTotal request timeOverall speedup
2×50 + 25 = 75msAbout 1.33×
10×50 + 5 = 55msAbout 1.82×
Processing time approaches zeroApproaches 50msCeiling of 2×

This is arithmetic, not a Rust benchmark. Reducing queries or changing caching and concurrency can also reduce waiting, but then the fixed-wait assumption no longer holds. A rewrite that changes both language and design cannot attribute every gain to language execution speed.

Follow the slow request into its calls

Start with the request that is actually slow. Separate its overall latency from time spent in application functions and database or external API calls. Assuming a web service must be I/O-bound is no substitute for that measurement.

Python's deterministic profiler records function calls and timing. Internal time and cumulative time answer different questions: a function can have a high cumulative cost because it calls expensive work elsewhere.

Python 3.15 exposes this through profiling.tracing, with cProfile also available for compatibility. Recording calls introduces overhead. Use a profile to locate work, then compare the changed service under equivalent ordinary execution conditions as well.

The native function is only part of the cost

A concentrated CPU-heavy function may be a useful native-extension target without moving the entire application. The comparison must include moving data into and out of that function.

The PyO3 performance guide explains a concrete example: turning a Rust string into a Python string allocates a Python object. It provides interning for strings repeatedly reused at the same call site. Faster computation can still be accompanied by object-allocation overhead at the boundary.

Likewise, moving a Python wrapper does not automatically accelerate computation already performed by a native library or GPU. Distinguish a slow Python loop from expensive conversions and time inside an existing native operation.

Choose the change that reaches the bottleneck

The measured workload should determine the scope of a rewrite. These are practical starting points:

Observed costChange to compare first
Repeated database or API workRemove unnecessary calls and assess caching.
A CPU-heavy Python loopReduce its work; compare a native implementation.
Python–Rust conversionsReduce conversion frequency and allocations.
Existing native computationDetermine whether a wrapper rewrite changes that path.

An AI task can begin with measuring a slow request and proposing a change to its dominant cost. Choose Rust when the proposed change addresses the measured problem. Afterward, compare both latency and output correctness with the same input and load.

FAQ

Is 2× the maximum possible Python-to-Rust speedup?

No. It is the ceiling in the example where half the original time cannot change. A different workload or a redesign has a different bound.

Does this rule out full rewrites?

No. Maintainability or portability can also justify one. If speed is the goal, establish how much of the current bottleneck the rewrite will address.