Optimizing the performance of an API
It’s important to note that optimization should not be the first step in your process. Optimization is powerful, but can lead to unnecessary complexity if done prematurely
The first step should always be to Identify the actual bottlenecks through load testing, monitoring and tracing. Only begin optimization. Once you’re confirmed that an API endpoint has performance issues
Load Testing: Perform load testing of the API to identify areas that may be causing performance issues and find ways to address them. Use tools like vegeta or hey.
Monitoring and Tracing: Use monitoring and tracing tools like opentelemetry with Jaeger, Prometheus, Grafana, or the ELK Stack to track API activity and identify performance issues.
Then go somes tips
Caching: Use a caching system to store the results of common API calls and return them without recomputation or database queries. Caching can reduce server load and improve response times. Authorization data should be always cached.
Paging: If your API response returns a large amount of data, it can slow things down. Instead, break the response into smaller using limit and offset parameters. This can speed up data transfer and reduce load on the client side.
There are many way to paging. I will list some common paging:
Offset-Based Paging: SELECT * FROM table_name OFFSET 0 LIMIT 10
Advantages:
Simple Implementation
Offset-based paging allows for direct access to any page within the dataset
Suitable for Small Datasets
Disadvantages
Offset-based paging can become inefficient and slow when dealing with large datasets. As the offset increases, the database needs to scan and skip over a large number of records before returning the desired data, resulting in performance degradation
Cursor-Based Paging: SELECT * FROM table_name WHERE key > last_key ORDER BY key LIMIT 10;
Advantages
Cursor-based paging is generally more efficient than offset-based paging for large datasets. Instead of calculating offsets and scanning through records, cursor-based paging relies on the use of cursors or tokens to bookmark the last fetched record. This makes it more suitable for datasets with millions of records or more.Unlike offset-based paging, which slows down as the offset increases, cursor-based paging typically offers consistent performance for pagination queries.
Recommended by LinkedIn
Disadvantages
Complex Implementation
Unlike offset-based paging, cursor-based paging doesn't offer direct random access to specific pages within the dataset. Users can only navigate forward or backward from the current cursor position, which may not be suitable for all use cases.
Database Optimization: Ensure your database is properly designed and optimized. Use indexes, optimize SQL queries, and monitor database load. Ensuring your most frequent queries end up using index scan instead of full table scan. Partition big table (>2gb). avoid N+1 Query pattern (loop for query)
Optimize Encoding: Use efficient and fast encoding for transmitting data over the network, such as JSON or Protocol Buffers. Ensure that encoding and decoding do not introduce unnecessary overhead.
Connection pool: Instead of opening a new database connection for each API call. reuse connections can greatly improve throughput because Creating a new connection each time involves a lot of handshake protocols and setup which can slow down your API
Use gRPC for communication between services
Compression data when transferred as gzip, Snappy. By enabling compression on large API response payloads, you can reduce the amount of data transferred over the network. The client then decompresses the data . Also, many content delivery Networks like Cloudflare can handle compression for you
Result Caching: Cache results on the client side when possible to reduce the number of API calls.
Backend responds with only the necessary data for the client Use techniques like GraphQL to allow clients to request only data they need.
While a single user request can hit multiple services but those services should not in turn hit another set of services and so on
We have asynchronous logging. In many applications, the time it take to write logs is negligible. However, in high throughput systems where every millisecond counts, the time taken to write logs can add up. In such cases, asynchronous logging can help. This involves the main application thread quickly placing the log entry into an in-memory placing the log entry into an in-memory buffer, while a separate logging thread writes the log entries to the file or sends them to the logging service