How to Diagnose and Fix Common API Performance Bottlenecks in Java Backend
This article walks through real‑world API performance complaints, identifies root causes such as slow MySQL queries, thread‑pool misconfiguration, lock contention and machine issues, and presents practical solutions ranging from pagination rewrites and index tuning to multithreading, caching and async callbacks.
Background
The system was completed in early 2021 and entered the promotion phase. As usage grew, many users praised the product but also complained about performance. Monitoring over a week revealed dozens of slow endpoints, several exceeding 5 seconds and one over 10 seconds, with overall stability below 99.8 %.
Which problems cause API performance issues?
Database slow queries
Deep pagination
Missing indexes
Index loss
Too many joins
Too many sub‑queries
Excessive IN list size
Large data volume
Complex business logic
Loop calls
Sequential calls
Thread‑pool design flaws
Lock design flaws
Machine problems (full GC, restarts, thread saturation)
Problem solutions
All slow‑query examples assume MySQL.
Deep pagination (MySQL)
Typical pagination: select name, code from student limit 100, 20 When the offset becomes large, MySQL scans many rows. A better approach is to use a primary‑key condition:
select name, code from student where id > 1000000 limit 20This forces index usage but requires the caller to pass the last max ID.
Missing indexes
Check a table’s indexes with: show create table xxxx; Add appropriate indexes, but avoid low‑cardinality columns and schedule ALTER statements during low‑traffic windows.
Index loss
When MySQL ignores an index, investigate reasons such as type conversion, functions on indexed columns, or low selectivity. Force an index if necessary:
select name, code from student force index(XXXXXX) where name = '天才';Too many joins or sub‑queries
Prefer reducing joins, splitting queries in code, and assembling results in memory when data sets are small. Excessive joins may cause temporary tables on disk, degrading performance.
Excessive IN list
If an IN clause contains thousands of values, split the query into batches or limit the size (e.g., 200 items):
if (ids.size() > 200) { throw new Exception("Batch size cannot exceed 200"); }Large data volume
When a single table grows to billions of rows, consider sharding, partitioning, or migrating to a database designed for big data.
Complex business logic
Loop calls
Initialize a list of 12 months sequentially:
List<Model> list = new ArrayList<>();
for (int i = 0; i < 12; i++) {
Model model = calOneMonthData(i);
list.add(model);
}Replace with a thread‑pool to compute months in parallel:
public static ExecutorService commonThreadPool = new ThreadPoolExecutor(
5, 5, 300L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(10), commonThreadFactory,
new ThreadPoolExecutor.DiscardPolicy());
List<Future<Model>> futures = new ArrayList<>();
for (int i = 0; i < 12; i++) {
futures.add(commonThreadPool.submit(() -> calOneMonthData(i)));
}
List<Model> list = new ArrayList<>();
for (Future<Model> f : futures) {
list.add(f.get());
}Sequential calls
When tasks are independent, use CompletableFuture to run them concurrently:
CompletableFuture<A> futureA = CompletableFuture.supplyAsync(() -> doA());
CompletableFuture<B> futureB = CompletableFuture.supplyAsync(() -> doB());
CompletableFuture.allOf(futureA, futureB).join();
C c = doC(futureA.join(), futureB.join());
CompletableFuture<D> futureD = CompletableFuture.supplyAsync(() -> doD(c));
CompletableFuture<E> futureE = CompletableFuture.supplyAsync(() -> doE(c));
CompletableFuture.allOf(futureD, futureE).join();
return doResult(futureD.join(), futureE.join());Thread‑pool design issues
Core threads, max threads and queue size must be tuned. An undersized core pool prevents parallelism; a saturated queue or max pool leads to task rejection.
Lock design issues
A coarse‑grained synchronized block can be split:
public void doSome() {
File f = null;
synchronized (this) {
f = calData();
}
uploadToS3(f);
sendSuccessMessage();
}Machine problems
Full GC, thread leaks, or resource exhaustion can cause latency spikes; monitor and isolate such issues.
General “silver‑bullet” solutions
Caching
Cache frequently read, rarely changed data using in‑memory maps, Guava, or external stores like Redis, Tair or Memcached.
Simple Map cache
Guava local cache
Redis / Tair / Memcached distributed cache
Callback / async verification
For slow downstream calls (e.g., payment gateway), return a fast “processing” response and notify the caller later via callback or Kafka.
Conclusion
The article summarizes practical performance‑optimization techniques encountered in real projects, inviting further discussion and sharing.
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.
Java Backend Technology
Focus on Java-related technologies: SSM, Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading. Occasionally cover DevOps tools like Jenkins, Nexus, Docker, and ELK. Also share technical insights from time to time, committed to Java full-stack development!
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.
