Problem description
First, describe the background of the story: (hope you can read the story patiently)
On the website, the page control in the webpage displays 10 data records each time. Each time you click the next page, the next 10 data records are retrieved again. As for how to do paging, there are many methods, and we believe that everyone knows this.
The process is as follows: when a user requests data (taking into account the user's operations and website traffic), I will retrieve 500 pieces of data for the first time and put the data in the cache. That is to say, I took 50 pages of data and put it in the cache. In this way, if the user requests the first page to 49th pages, the data will be taken directly from the cache.
For example:
The first data block:
Saved in the form of a key-Value Pair: Dictionary
If the user requests 49 pages, the next data block (including 501 to 1000 data records) will be retrieved from the database again. Then, there will be 1000 data records in the memory.
We will not talk about how long the cache will take, what data will expire, and what will happen after it expires. (The website runs well under this cache policy ).
The Code is as follows:
List <Product> products = GetDataFromCacheOrDatabase (condition, pageIndex, count ....);
The meaning of the Code is clear: get data from the cache. If there is no corresponding data in the cache, get 500 pieces of data from the database and then put it in the cache, finally, 10 data records are returned.
Later, due to the needs of some functions, we need to return the first 6 pages of data and the last 6 pages of data on the current page. For example, if the current page is 12th pages, then, we need to return the first 6 pages of the Product (that is, the data on pages 6, 7, 8, 9, 10, 11), and the Product (12th, 17, 18 pages of data ).
As follows:
Of course, if the current page is 5th pages, then all the data on the previous 5 pages will be returned, and 6 pages of data after the first 5th pages will be added.
This may involve cross-block data acquisition, for example:
If the current page is 48th pages, there is no problem in returning the first 6 pages of data, and the data on the last 6 pages is insufficient, because and 40 also obtain data from the cached data blocks, as for, pages of data, you need to read the data from the database again, then cache again (if not cached in advance ).
Finally, the data in the cache is as follows:
Then call the method: (pseudo code)
List <Product> products = GetDataFromCacheOrDatabase (condition, 42,126 ....);
The data imported above is the data starting from 42nd pages, that is, the data on the first 6 pages and the last 6 pages of the 48th page.
The internal implementation of this method is as follows:
1. First retrieve 42 pages to 50 pages of data from the first data block
Store the data in a List <Product> firstProductList;
2. retrieve 51 to 54 pages from the second data block (if the second data block does not exist in the cache, retrieve 50-entries from the database, and then put it in the second cached data block ).
Save it in the second List <Product> secondProductList
3. merge the two lists and return the results. For example
SecondProductList. Foreach (u => firstProductList. Add (u ));
The basic implementation is like this. It looks okay and reasonable, but this operation causes the server memory overflow.
Let's see why.
Importance of details
In fact, there are not many cached data, which is insufficient to overflow the server memory, but the server still suffers an out of memory exception. It has been running well before, but the problem occurs only after the code is changed.
In fact, this is caused by a basic error: reference type.
The following is an analysis:
First, extract data from the first data block, and then use
List <Product> firstProductList references the Retrieved Data
Then retrieve the data from the second data block and use
List <Product> secondProductList: Data Reference
For example
Use
SecondProductList. Foreach (u => firstProductList. Add (u ));
Add the data in secondProductList to firstProductList because it is a reference type. In fact, the actual operation result is: constantly changing the data in the first data block, gradually increase the data in the first data block.
Currently, the current page is 48 pages, and the above operation is used, resulting in 60 more data records in the first data block,
If you flip the page again to 49 pages, the number of data records in the first data block increases by 60.
In the end, the server memory is insufficient, causing the server to crash. The original "hero"-Cache has become the culprit.
To solve this problem, you only need to change the code a little bit:
List <Product> firstProductList;
List <Product> secondProductList;
Then
List <Product> resultProductList = new List <Product> (); then traverse firstProductList and secondProductList respectively and add them to resultProductList.
That's simple.
A small detail causes a big problem.
Do not ignore every detail. Do not perform operations such as loops without comparison. You must examine the code and refactor the code.
I wrote it here today, so I am so embarrassed that I hope to help you a little bit.