Optimizing from 2s to 0.1s, this overtime is worth it!

Hello everyone, today we will continue to share a practical problem encountered during the project development process. Here we will also sort and summarize how we solved it. Continuous optimization, hope it can be helpful to everyone.

Classification tree query function can be seen everywhere in various business systems, especially in some e-commerce systems.

ca4ddc58865316363cb67a9ff39d9da1.pngBut for such a seemingly simple classification tree query function, we Optimized several times.

What exactly did you experience?

Background

One of our old projects uses the template engine recommended by SpringBoot: Thymeleaf for dynamic rendering.

It is an XML/XHTML/HTML5 template engine that can be used for application development in Web and non-Web environments.

It provides an optional module for integrating SpringMVC. In application development, we can use Thymeleaf to completely replace JSP or other template engines, such as Velocity\FreeMarker, etc.

The front-end developer writes the Thymeleaf template file, calls the back-end interface to obtain data, and performs dynamic binding to display the desired content to the user.

Since this was a new project from 0-1 very early at that time, in order to enable rapid development, the first version of the interface directly queried the classification data from the database and assembled it into a classification tree, and then return to the front end.

In this way, the data process is simplified and the functions of the entire page are quickly adjusted.

First optimization

We deployed the interface to the dev environment, and there was no problem at first.

As developers added more and more categories, performance bottlenecks were quickly exposed.

We have to optimize.

The first thing we thought of was: Add Redis cache.

The flow chart is as follows: [Recommended collection] Detailed description of Redis distributed locke913a170f4defeb6198d9739e4191d13.pngSo Temporarily optimized like this:

  1. When the user accesses the interface to obtain the classification tree, the data is first queried from Redis.

  2. If there is data in Redis, it is direct data.

  3. If there is no data in Redis, the data is queried from the database and spliced into a classification tree for return.

  4. Save the classification tree data found from the database to Redis and set the expiration time to 5 minutes.

  5. Return the classification tree to the user.

We define a key in Redis, and the value is a classification tree in json format converted into a string, and the data is saved in a simple key/value format.

After such optimization, the joint debugging and self-test of the dev environment were successfully completed.

Redis common commands and data types

Second optimization

We have deployed this function to the st environment.

At the beginning, the students did not find any problems during the test, but as they continued to conduct in-depth testing, the homepage access became very slow every once in a while.

So, we immediately carried out the second optimization.

We decided to use Job to regularly asynchronously update the classification tree into Redis. Before the system goes online, a copy of the data will be generated.

Of course, to be on the safe side and prevent Redis from suddenly crashing, the previous logic of synchronously writing the classification tree to Redis is still retained.

So, the flow chart was changed to this: 798c5a679f6829f51774afc265463808.pngAdded a job every other Executed once every 5 minutes, the classification data is queried from the database, encapsulated into a classification tree, and updated to the Redis cache.

The other processes remain the same. [Interviewer] How to solve the big key that appears in Redis?

In addition, the expiration time of Redis, which was previously set to 5 minutes, will now be changed to permanent.

After this optimization, the st environment no longer has performance problems with classification tree queries.

The third optimization

After testing for a period of time, the entire website function is about to go online.

To be on the safe side, we need to do a stress test on the homepage of the website.

Sure enough, a problem was detected. The maximum qps of the website homepage was more than 100. Finally, it was found that the performance bottleneck of the website homepage was caused by obtaining the classification tree from Redis every time.

We need to do the third optimization.

How to optimize it? Redis Core: The only fast and unbreakable secret

Answer: Add memory cache.

If a memory cache is added, data consistency issues need to be considered.

The memory cache is stored on the server node. The update frequency of different server nodes may be slightly different, which may cause data inconsistency.

However, the classification itself is data with a relatively low update frequency and is not very sensitive to users. Even if the classification trees seen by users are somewhat different within a short period of time, it will not have much impact on the users.

Therefore, business scenarios such as classification trees can use memory caching.

Therefore, we used caffine recommended by Spring as the memory cache.

The modified flow chart is as follows: b1d6305b05a00a52460330b898fa58dd.png

  1. When users access the interface, the data is first queried from the local cache classification number.

  2. If the local cache has it, it will be returned directly.

  3. If the local cache does not exist, the data is queried from Redis.

  4. If there is data in Redis, the data is updated into the local cache and then the data is returned.

  5. If there is no data in Redis (meaning Redis is down), query the data from the database, update it to Redis (in case Redis recovers), then update it to the local cache, and return the data.

It should be noted that you need to change the local cache to set an expiration time, which is set here to 5 minutes. Otherwise, there will be no way to obtain new data.

After this optimization, we conducted a stress test on the homepage of the website again, and the qps increased to more than 500, meeting the online requirements. Advanced Redis data management, efficient traversal of massive data

The 4th optimization

After that, this function was successfully launched.

Used it for a long time without any problems.

One day two years later, a user reported that the homepage of the website was a bit slow.

We investigated the cause and found that there was too much data in the classification tree, and tens of thousands of categories were returned at once.

It turns out that in the more than two years since the system was launched, the operation students have added many categories to the system background.

We need to do the fourth optimization.

How to optimize at this time?

Limit the number of classification trees?

Answer: It’s not very realistic. There are so many categories in the current business scenario, so users can’t choose the category they want, right?

At this time, we thought that the fastest way was to enable the GZip function of nginx.

Let the data be compressed before transmission, and then transmitted. In the user’s browser, it will be automatically decompressed and the real classification tree data will be displayed to the user.

The size of the classification tree returned by calling the interface before was 1MB. After optimization, the size of the classification tree returned by the interface was 100Kb, which was reduced by 10 times.

After such simple optimization, the performance has improved a bit.

The fifth optimization

After the above optimization, users did not report any performance issues for a long time.

But one day when colleagues in the company were investigating the big keys in Redis, they found the classification tree. The previous classification tree used the key/value structure to save data.

We had to do the 5th optimization.

In order to optimize the size of data stored in Redis, we first need to slim down the data.

Only save the fields you need.

For example:

@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;
}

The inDate, inUserId and inUserName fields in this classification object do not need to be saved.

Modify automatic name.

For example:

@AllArgsConstructor
@Data
public class Category {
    /**
     * Classification number
     */
    @JsonProperty("i")
    private Long id;

    /**
     * Classification level
     */
    @JsonProperty("l")
    private Integer level;

    /**
     * Category Name
     */
    @JsonProperty("n")
    private String name;

    /**
     * Parent category number
     */
    @JsonProperty("p")
    private Long parentId;

    /**
     * Subcategory list
     */
    @JsonProperty("c")
    private List<Category> children;
}

Since the field names of each piece of data among more than 10,000 pieces of data are fixed, their repetition rate is too high.

From this, you can change it to a short name during json serialization to return less data size.

This is not enough, the stored data needs to be compressed.

The key/value previously saved in Redis, where the value is a string in json format.

In fact, RedisTemplate supports, value saves byte array.

First, use the GZip tool class to compress the json string data into a byte array, and then save it to Redis.

When retrieving data, convert the byte array into a json string, and then convert it into a classification tree.

After this optimization, the data size of the classification tree saved in Redis was reduced by 10 times, and the large key problem of Redis was solved.

Small summary

So looking back, for such a seemingly uncomplicated functional requirement, if you want to make it stable, efficient, and usable, you still need to consider a lot of issues along the way. Once any of the problems encountered are solved and reviewed, they will be gathered into our experience. I hope this article can be helpful to everyone.

Okay, that’s it for today’s content sharing. Thank you all for watching. See you in the next article.

··········· END ················

Reading, liking, and forwarding are the greatest encouragement to me.