How We Boosted a Category Tree API from 100 QPS to 500+ with 5 Optimizations
This article recounts five successive performance optimizations applied to a SpringBoot‑Thymeleaf category‑tree API—adding Redis caching, scheduled async refresh jobs, local Caffeine memory cache, gzip compression via Nginx, and Redis data slimming—transforming query latency and scaling QPS from around 100 to over 500.
Background
The project started as a new SpringBoot service that used Thymeleaf for dynamic page rendering. The initial implementation queried the database directly for category data, assembled a JSON‑formatted classification tree, and returned it to the front‑end. This simple approach allowed rapid development but quickly exposed performance bottlenecks as the number of categories grew.
First Optimization – Redis Cache
To alleviate the database load, the team introduced a Redis cache layer. The workflow became:
When a client requests the classification tree, the service first checks Redis.
If the data exists in Redis, it is returned immediately.
If Redis misses, the service queries the database, builds the tree, stores the JSON string in Redis with a 5‑minute TTL, and returns the result.
This change eliminated most database hits in the development environment and stabilized the feature.
Second Optimization – Periodic Async Job
During testing in the staging environment, intermittent slow‑downs appeared. The team added a scheduled Job that runs every five minutes, queries the database, rebuilds the classification tree, and refreshes the Redis entry. The original on‑demand cache‑populate logic was retained as a fallback in case Redis became unavailable. The Redis TTL was changed from a short expiry to a permanent entry, relying on the periodic job to keep the data fresh.
Third Optimization – Local Memory Cache (Caffeine)
Load testing revealed that even reading from Redis for every request limited the homepage QPS to about 100. To further reduce latency, a local in‑process cache using Spring’s Caffeine library was introduced. The new flow:
Check the local Caffeine cache first.
If present, return the cached tree.
If absent, query Redis.
If Redis returns data, populate the local cache (with a 5‑minute expiration) and return the result.
If Redis also misses (e.g., Redis down), fall back to the database, update Redis, then update the local cache.
Remember to set an expiration (e.g., 5 minutes) on the local cache so stale data is eventually refreshed.
After this change, homepage QPS rose to over 500, meeting the launch requirements.
Fourth Optimization – Nginx Gzip Compression
Two years after launch, users reported occasional slowness. Investigation showed the classification tree payload had grown to over 1 MB. To shrink the response size, Nginx’s gzip compression was enabled. The server now compresses the JSON before transmission; browsers automatically decompress it. The payload dropped from ~1 MB to ~100 KB, a ten‑fold reduction, providing a modest but noticeable performance gain.
Fifth Optimization – Redis Data Slimming and Compression
Later, a Redis “big‑key” audit identified the classification tree as a large value. To reduce memory consumption, the team performed two actions:
Trim the stored object to only essential fields, removing metadata such as inDate, inUserId, and inUserName.
Rename JSON properties to short aliases (e.g., "i" for id, "l" for level, "n" for name, "p" for parentId, "c" for children) to reduce string overhead.
Finally, the JSON string is compressed with Gzip and stored as a byte array via RedisTemplate. On read, the byte array is decompressed back to JSON and deserialized into the tree structure. This compression shrank the Redis value by another ten‑fold, fully resolving the big‑key issue.
@AllArgsConstructor
@Data
public class Category {
private Long id;
private String name;
private Long parentId;
private Date inDate;
private Long inUserId;
private String inUserName;
private List<Category> children;
} @AllArgsConstructor
@Data
public class Category {
/** 分类编号 */
@JsonProperty("i")
private Long id;
/** 分类层级 */
@JsonProperty("l")
private Integer level;
/** 分类名称 */
@JsonProperty("n")
private String name;
/** 父分类编号 */
@JsonProperty("p")
private Long parentId;
/** 子分类列表 */
@JsonProperty("c")
private List<Category> children;
}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.
ITPUB
Official ITPUB account sharing technical insights, community news, and exciting events.
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.
