Why is your API slow? Common performance problems

In the cloud era, performance is also a matter of cost. The more CPU, memory, or bandwidth an API consumes, the more expensive it can become to run. A well-optimized API can therefore both reduce the resources used and provide a better user experience.
A poorly optimized SQL query, excessive calls to external services, or overly large JSON responses are among the most common causes.
In this article, we review the main performance problems that can slow down an API, as well as the most effective solutions to fix them.
SQL queries that are too slow
The database is often one of the main causes of an API slowdown. I have fixed a lot of performance problems related to SQL queries over the years. A query that takes only a few milliseconds when it returns a few rows can become very slow when it has to scan hundreds of thousands or millions of records.
For example:
SELECT *
FROM users
WHERE email = 'user@example.com';If email is not indexed, the database may have to scan the entire table. Creating an appropriate index can significantly improve performance:
CREATE INDEX idx_users_email ON users(email);However, be careful: creating indexes often improves the performance of SELECT queries, but too many indexes can have the opposite effect. With every INSERT, UPDATE, or DELETE, the database also has to update all the affected indexes. And the disk space used can also increase significantly...
For example, a table with 8 or 10 indexes can become much slower to write to than a properly optimized table.
A good practice is to analyze the queries that are actually executed and create the appropriate indexes, particularly for columns used in WHERE, JOIN, and ORDER BY clauses.
You should regularly analyze performance using EXPLAIN and the database statistics.
Too many queries to the database
Even fast queries can become problematic when an API executes hundreds of them to generate a single response. A common problem is the N+1 query problem. For example, the application first retrieves 100 users, then executes an additional query to retrieve the orders for each user. This can result in 101 queries instead of one or a few properly optimized queries.
These repetitive calls should be identified and, whenever possible, the data should be retrieved using joins, grouped queries, or an appropriate loading mechanism.
Responses That Are Much Too Large
An API that returns several thousand objects when the client only uses a dozen unnecessarily wastes bandwidth, processing time, and server resources (CPU, memory, and storage).
Pagination is essential for large collections. You can also consider limiting the number of records returned by default when no limit is specified in the request, to avoid unnecessarily loading thousands or even millions of records.
I have already had a client calling one of my APIs with very large amounts of rows, while only using the first 1,000. These calls were nevertheless causing significant slowdowns on my side.
Some APIs also allow the client to select only the fields it needs, which can considerably reduce the amount of data returned.
Finally, you can offer a more compact response format when the amount of data is large. However, this approach has some disadvantages: the format is generally less readable and can be more difficult to use or debug manually.
Too many calls to other services
Your API may be fast, but depend on many external services. Be particularly careful about excessive use of microservices, which can quickly increase latency and complexity.
Imagine that a request requires:
- a call to a payment service;
- a call to a geolocation API;
- a call to an email delivery service;
- a call to a data API.
If these calls are executed sequentially, their response times are added together. When the operations are independent, they can sometimes be executed in parallel.
For operations that are not required for the immediate response, a message queue or asynchronous processing can also be used, although this often makes the API more complex to use on the client side.
Lack of caching
Some data is requested very frequently even though it rarely changes. Recalculating it for every request is unnecessary and costly. A cache can temporarily store the result of an expensive operation.
Solutions such as Redis (I have already had latency issues with it under Windows...) can be used to store frequently accessed data. However, an appropriate expiration strategy must be defined to avoid serving outdated data for too long. The retention period should obviously be adapted to the type of data.
Lack of HTTP compression
A large JSON response can represent several hundred kilobytes or even several megabytes. HTTP compression can significantly reduce the amount of data transferred.
Mechanisms such as gzip and Brotli are widely supported. A 500 KB JSON response can, for example, be considerably reduced after compression. Compression is particularly useful for APIs that return a lot of text or JSON data.
Poorly managed connections
Establishing a new connection to the database or another service for every request can be costly. Applications generally use a connection pool to reuse existing connections rather than creating a new one for every request.
Without a properly configured pool, a heavily used API can quickly reach the limits of the database server. You should also make sure that connections are properly closed or released after use.
Conclusion
A slow API can have many causes. We have only covered a few of them, and there are many others! However, the most common problems are often quite familiar: inefficient SQL queries, lack of caching, overly large responses, excessive external calls, or overly heavy processing.
The best approach is not to optimize code at random. Start by measuring, identify the main bottleneck, and then fix it.
Alright, back to improving the performance of my API!