26.1 Understanding Backend Performance
Table of Contents
Why Backend Performance Matters
Backend performance is about how fast and efficiently your server can handle work. It affects:
- How quickly users see responses.
- How many users you can serve at the same time.
- How much hardware (and money) you need.
Two backends that behave the same from a feature point of view can be very different in cost and user experience because of performance.
Key questions about performance:
- How long does each request take?
- How many requests per second can we handle?
- How much CPU and memory does the system use at load?
- What happens when traffic spikes?
You will see these concepts in later chapters such as caching, scaling, and load balancing. Here we focus on understanding performance at a basic, practical level.
Important: Performance is not only about speed, it is about capacity under constraints such as CPU, memory, network, and database limits.
Core Performance Metrics
Backend performance is usually described with a few standard metrics.
Latency
Latency is how long a single operation takes.
Examples:
- Time from client sending an HTTP request until it gets a response.
- Time to run a particular SQL query.
- Time to perform a cache lookup.
If a request starts at time $t\_0$ and finishes at time $t\_1$, the latency is:
$$\text{latency} = t\_1 - t\_0$$
Often measured in milliseconds (ms).
Common latency statistics:
- Average (mean) latency
- Median (p50), 50% of requests are faster than this
- Tail latencies such as p95, p99 (95% or 99% of requests are faster than this)
Tail latencies are very important. A service with:
- p50 = 50 ms
- p99 = 5,000 ms
feels slow to users, even though the median looks good.
Throughput
Throughput is how many operations you can process per unit time, for example:
- Requests per second (RPS)
- Jobs processed per minute
- Queries per second (QPS)
Informally:
$$\text{throughput} \approx \frac{\text{number of completed operations}}{\text{duration}}$$
Throughput is limited by your slowest bottleneck, often the database, network, or CPU.
Latency and throughput are related but not the same. You can:
- Have low latency but low throughput (one super-fast worker).
- Have higher latency but high throughput (many workers in parallel).
Resource Utilization
Resource utilization tells you how much of a resource is in use:
- CPU usage (for example 75%)
- Memory usage (for example 4 GB of 8 GB)
- Disk I/O (reads/writes per second, MB/s)
- Network I/O (bandwidth usage)
When a resource is close to 100%, it becomes a bottleneck, and performance often degrades sharply.
A simple view:
| Resource | Symptom of saturation |
|---|---|
| CPU | High CPU % on all cores, slow code |
| Memory | Swapping, out-of-memory errors |
| Disk | High I/O wait time |
| Network | High latency, timeouts, packet loss |
Concurrency and Parallelism
These two terms are often confused but describe different ideas.
- Concurrency: Handling multiple tasks in overlapping time.
- Parallelism: Actually running multiple tasks at the same instant on different CPU cores.
In backend servers:
- Concurrency is about handling many requests without blocking.
- Parallelism is about using multiple CPU cores to process work simultaneously.
You can have concurrency without parallelism. For example, an async server on a single CPU core can juggle many I/O-bound requests concurrently.
The Lifecycle of a Request
To understand performance, break a request into stages. A typical web request:
- Client sends a request.
- Network transfer from client to server.
- Reverse proxy or load balancer receives it and forwards to your app.
- Application server parses HTTP and routes to the correct handler.
- Handler executes:
- Reads data from cache or database.
- Applies business logic.
- Possibly calls other services.
- Response is created and sent back through the stack to the client.
A simplified time breakdown for one request:
| Stage | Time (example) |
|---|---|
| Network (client to server) | 40 ms |
| Reverse proxy + routing | 5 ms |
| App logic (no DB) | 5 ms |
| Database query | 80 ms |
| Response serialization | 5 ms |
| Network (server to client) | 40 ms |
| Total latency | 175 ms |
Knowing these stages helps you ask: Where is the time really going?
If you only optimize application code that takes 5 ms while the DB query takes 80 ms, you will not see much improvement.
Bottlenecks and the Slowest Part Rule
A backend system behaves a bit like a pipeline. The part with the lowest capacity limits the whole system.
Imagine:
- Web server can handle 2,000 requests per second.
- Database can safely handle 200 queries per second.
Even if your app server is very fast, your maximum safe throughput will be around what the database can do.
Rule: Your system throughput cannot exceed the throughput of its slowest critical component.
Common backend bottlenecks:
- Database (slow queries, missing indexes, too many concurrent connections)
- External APIs (slow payment provider, email provider)
- Disk I/O (reading or writing large files)
- Network (limited bandwidth, high latency between services)
- CPU (expensive computations like encryption, image processing, or JSON encoding for huge payloads)
Example: Identifying a Bottleneck
Suppose these measurements for handling one request:
| Component | Latency per request |
|---|---|
| Application code | 5 ms |
| Redis cache | 2 ms |
| Database | 70 ms |
| External API | 230 ms |
Total is about 5 + 2 + 70 + 230 = 307 ms.
The external API dominates. Even if you reduce DB time from 70 ms to 7 ms, the total only drops to about 244 ms. Real gains will come from:
- Caching external API results.
- Making external calls asynchronous.
- Reducing how often you call the external API.
This idea is closely related to Amdahl’s Law, which says that speeding up a small part of a system gives a limited overall improvement.
Performance, Capacity, and Scalability
Performance is not only about speed for a single request. It is also about what happens under load.
- Performance: How fast an operation completes.
- Capacity: How many operations you can handle before things break or slow down too much.
- Scalability: How well performance and capacity improve when you add more resources.
Response Time under Load
As load increases, response time usually looks like this in practice:
- At low load, latency is stable and low.
- As load increases, latency slowly grows.
- After a point (the capacity limit), latency grows rapidly and errors appear.
You might see:
| Requests per second | Average latency | Error rate |
|---|---|---|
| 50 | 40 ms | 0% |
| 100 | 60 ms | 0% |
| 200 | 120 ms | 0% |
| 400 | 500 ms | 2% |
| 800 | 2000 ms | 20% |
Understanding where this “knee” occurs helps with capacity planning.
Vertical and Horizontal Scaling
Scaling is how you increase capacity when traffic grows.
- Vertical scaling: Use a bigger machine (more CPU, more RAM).
- Horizontal scaling: Use more machines and distribute load.
Each approach has tradeoffs, which you will explore later in the course. Here the main idea is:
- Vertical scaling improves performance of a single instance.
- Horizontal scaling improves total throughput by adding more instances.
Performance Tradeoffs
You rarely improve everything at the same time. You trade:
- Latency vs throughput.
- Throughput vs resource usage.
- Consistency vs availability (especially in distributed systems).
- Correctness or clarity vs raw speed.
Common tradeoffs:
Caching vs Freshness
Caching can make responses very fast by reusing previous results, but:
- Data can be slightly outdated.
- Cache invalidation increases complexity.
Precomputation vs Flexibility
Precomputing statistics or denormalizing data can make reads fast, but:
- Writes become more expensive.
- The system becomes more complex to maintain.
Simplicity vs Performance
Highly optimized code or architecture can be harder to read and maintain. For most beginner projects:
- Prefer simple, clear code.
- Optimize only when you have a real performance issue.
Guideline: Start with clear and correct code. Measure. Then optimize actual bottlenecks instead of guessing.
Measuring and Observing Performance
To improve performance, you must measure it. Guessing is almost always wrong.
Key Things to Measure
At minimum, for each endpoint or operation:
- Latency (p50, p95, p99)
- Throughput (requests per second)
- Error rate (% of failed requests)
- Resource usage (CPU, memory, database connections, I/O wait)
Later chapters cover detailed logging and monitoring tools such as Prometheus and Grafana. For now, focus on the idea that these measurements drive your decisions.
Simple Timing Example in Python
For a beginner-friendly illustration:
import time
def handle_request():
start = time.perf_counter()
# Simulate application work
time.sleep(0.05) # 50 ms
end = time.perf_counter()
latency_ms = (end - start) * 1000
print(f"Request took {latency_ms:.2f} ms")
handle_request()This is not production-grade monitoring, but it shows how you can measure time to understand your code’s behavior.
Common Performance Patterns in Backends
Several patterns appear again and again when you examine backend performance.
Small, Fast Operations vs Large, Slow Ones
It is usually better to do:
- Many small, fast database queries than a few extremely complex ones, or
- One properly optimized query instead of dozens of inefficient ones.
You will see database optimization in later chapters, but the performance mindset is:
- Each expensive operation has a cost per request.
- Avoid doing heavy work on every request if possible.
Synchrony vs Asynchrony
Synchronous (blocking) code:
- Waits for each I/O operation to finish.
- Limits concurrency, because each worker is stuck while waiting.
Asynchronous (non-blocking) code:
- Does not block on I/O operations.
- Lets one worker juggle many connections.
Async is especially helpful for I/O-bound workloads such as:
- Waiting for database or cache.
- Calling external APIs.
- Reading or writing files or sockets.
This is covered in more detail in the asynchronous programming chapters. For performance understanding, the main point is:
- Async increases concurrency without needing a thread per request.
- It does not automatically make CPU-heavy tasks faster.
Queues and Background Jobs
If some work is slow but not needed immediately in the response, you can:
- Put it on a queue.
- Let a worker process it later.
Example:
- User signs up.
- HTTP response returns quickly.
- Email confirmation is sent in the background.
This improves perceived performance for the user, even if total server work is the same.
Performance Mindset for Beginners
As you build backend projects, keep these habits:
- Break down requests
Ask: which parts touch the database, external APIs, filesystem, CPU? - Expect bottlenecks
Assume something is limiting you: DB, CPU, or network. Try to identify it. - Measure first
Do simple measurements before rewriting code. - Optimize the right thing
Spend effort on operations that are frequently called or very slow. - Watch real behavior under load
A function that looks fast for one call might behave badly with 1,000 concurrent users.
These ideas will connect directly to later chapters on asynchronous programming, database optimization, caching, scalability, load balancing, and performance testing.
Views: 7
KAHIBARO