@@ -10,20 +10,326 @@ tags:
10
10
---
11
11
12
12
## Intent
13
- To avoid expensive re-acquisition of resources by not releasing
14
- the resources immediately after their use. The resources retain their identity, are kept in some
15
- fast-access storage, and are re-used to avoid having to acquire them again.
13
+
14
+ The caching pattern avoids expensive re-acquisition of resources by not releasing them immediately
15
+ after use. The resources retain their identity, are kept in some fast-access storage, and are
16
+ re-used to avoid having to acquire them again.
17
+
18
+ ## Explanation
19
+
20
+ Real world example
21
+
22
+ > A team is working on a website that provides new homes for abandoned cats. People can post their
23
+ > cats on the website after registering, but all the new posts require approval from one of the
24
+ > site moderators. The user accounts of the site moderators contain a specific flag and the data
25
+ > is stored in a MongoDB database. Checking for the moderator flag each time a post is viewed
26
+ > becomes expensive and it's a good idea to utilize caching here.
27
+
28
+ In plain words
29
+
30
+ > Caching pattern keeps frequently needed data in fast-access storage to improve performance.
31
+
32
+ Wikipedia says:
33
+
34
+ > In computing, a cache is a hardware or software component that stores data so that future
35
+ > requests for that data can be served faster; the data stored in a cache might be the result of
36
+ > an earlier computation or a copy of data stored elsewhere. A cache hit occurs when the requested
37
+ > data can be found in a cache, while a cache miss occurs when it cannot. Cache hits are served by
38
+ > reading data from the cache, which is faster than recomputing a result or reading from a slower
39
+ > data store; thus, the more requests that can be served from the cache, the faster the system
40
+ > performs.
41
+
42
+ ** Programmatic Example**
43
+
44
+ Let's first look at the data layer of our application. The interesting classes are ` UserAccount `
45
+ which is a simple Java object containing the user account details, and ` DbManager ` which handles
46
+ reading and writing of these objects to/from MongoDB database.
47
+
48
+ ``` java
49
+ @Setter
50
+ @Getter
51
+ @AllArgsConstructor
52
+ @ToString
53
+ public class UserAccount {
54
+ private String userId;
55
+ private String userName;
56
+ private String additionalInfo;
57
+ }
58
+
59
+ @Slf4j
60
+ public final class DbManager {
61
+
62
+ private static MongoClient mongoClient;
63
+ private static MongoDatabase db;
64
+
65
+ private DbManager () { /* ...*/ }
66
+
67
+ public static void createVirtualDb () { /* ...*/ }
68
+
69
+ public static void connect () throws ParseException { /* ...*/ }
70
+
71
+ public static UserAccount readFromDb (String userId ) { /* ...*/ }
72
+
73
+ public static void writeToDb (UserAccount userAccount ) { /* ...*/ }
74
+
75
+ public static void updateDb (UserAccount userAccount ) { /* ...*/ }
76
+
77
+ public static void upsertDb (UserAccount userAccount ) { /* ...*/ }
78
+ }
79
+ ```
80
+
81
+ In the example, we are demonstrating various different caching policies
82
+
83
+ * Write-through writes data to the cache and DB in a single transaction
84
+ * Write-around writes data immediately into the DB instead of the cache
85
+ * Write-behind writes data into the cache initially whilst the data is only written into the DB
86
+ when the cache is full
87
+ * Cache-aside pushes the responsibility of keeping the data synchronized in both data sources to
88
+ the application itself
89
+ * Read-through strategy is also included in the aforementioned strategies and it returns data from
90
+ the cache to the caller if it exists, otherwise queries from DB and stores it into the cache for
91
+ future use.
92
+
93
+ The cache implementation in ` LruCache ` is a hash table accompanied by a doubly
94
+ linked-list. The linked-list helps in capturing and maintaining the LRU data in the cache. When
95
+ data is queried (from the cache), added (to the cache), or updated, the data is moved to the front
96
+ of the list to depict itself as the most-recently-used data. The LRU data is always at the end of
97
+ the list.
98
+
99
+ ``` java
100
+ @Slf4j
101
+ public class LruCache {
102
+
103
+ static class Node {
104
+ String userId;
105
+ UserAccount userAccount;
106
+ Node previous;
107
+ Node next;
108
+
109
+ public Node (String userId , UserAccount userAccount ) {
110
+ this . userId = userId;
111
+ this . userAccount = userAccount;
112
+ }
113
+ }
114
+
115
+ /* ... omitted details ... */
116
+
117
+ public LruCache (int capacity ) {
118
+ this . capacity = capacity;
119
+ }
120
+
121
+ public UserAccount get (String userId ) {
122
+ if (cache. containsKey(userId)) {
123
+ var node = cache. get(userId);
124
+ remove(node);
125
+ setHead(node);
126
+ return node. userAccount;
127
+ }
128
+ return null ;
129
+ }
130
+
131
+ public void set (String userId , UserAccount userAccount ) {
132
+ if (cache. containsKey(userId)) {
133
+ var old = cache. get(userId);
134
+ old. userAccount = userAccount;
135
+ remove(old);
136
+ setHead(old);
137
+ } else {
138
+ var newNode = new Node (userId, userAccount);
139
+ if (cache. size() >= capacity) {
140
+ LOGGER . info(" # Cache is FULL! Removing {} from cache..." , end. userId);
141
+ cache. remove(end. userId); // remove LRU data from cache.
142
+ remove(end);
143
+ setHead(newNode);
144
+ } else {
145
+ setHead(newNode);
146
+ }
147
+ cache. put(userId, newNode);
148
+ }
149
+ }
150
+
151
+ public boolean contains (String userId ) {
152
+ return cache. containsKey(userId);
153
+ }
154
+
155
+ public void remove (Node node ) { /* ... */ }
156
+ public void setHead (Node node ) { /* ... */ }
157
+ public void invalidate (String userId ) { /* ... */ }
158
+ public boolean isFull () { /* ... */ }
159
+ public UserAccount getLruData () { /* ... */ }
160
+ public void clear () { /* ... */ }
161
+ public List<UserAccount > getCacheDataInListForm () { /* ... */ }
162
+ public void setCapacity (int newCapacity ) { /* ... */ }
163
+ }
164
+ ```
165
+
166
+ The next layer we are going to look at is ` CacheStore ` which implements the different caching
167
+ strategies.
168
+
169
+ ``` java
170
+ @Slf4j
171
+ public class CacheStore {
172
+
173
+ private static LruCache cache;
174
+
175
+ /* ... details omitted ... */
176
+
177
+ public static UserAccount readThrough (String userId ) {
178
+ if (cache. contains(userId)) {
179
+ LOGGER . info(" # Cache Hit!" );
180
+ return cache. get(userId);
181
+ }
182
+ LOGGER . info(" # Cache Miss!" );
183
+ UserAccount userAccount = DbManager . readFromDb(userId);
184
+ cache. set(userId, userAccount);
185
+ return userAccount;
186
+ }
187
+
188
+ public static void writeThrough (UserAccount userAccount ) {
189
+ if (cache. contains(userAccount. getUserId())) {
190
+ DbManager . updateDb(userAccount);
191
+ } else {
192
+ DbManager . writeToDb(userAccount);
193
+ }
194
+ cache. set(userAccount. getUserId(), userAccount);
195
+ }
196
+
197
+ public static void clearCache () {
198
+ if (cache != null ) {
199
+ cache. clear();
200
+ }
201
+ }
202
+
203
+ public static void flushCache () {
204
+ LOGGER . info(" # flushCache..." );
205
+ Optional . ofNullable(cache)
206
+ .map(LruCache :: getCacheDataInListForm)
207
+ .orElse(List . of())
208
+ .forEach(DbManager :: updateDb);
209
+ }
210
+
211
+ /* ... omitted the implementation of other caching strategies ... */
212
+
213
+ }
214
+ ```
215
+
216
+ ` AppManager ` helps to bridge the gap in communication between the main class and the application's
217
+ back-end. DB connection is initialized through this class. The chosen caching strategy/policy is
218
+ also initialized here. Before the cache can be used, the size of the cache has to be set. Depending
219
+ on the chosen caching policy, ` AppManager ` will call the appropriate function in the ` CacheStore `
220
+ class.
221
+
222
+ ``` java
223
+ @Slf4j
224
+ public final class AppManager {
225
+
226
+ private static CachingPolicy cachingPolicy;
227
+
228
+ private AppManager () {
229
+ }
230
+
231
+ public static void initDb (boolean useMongoDb ) { /* ... */ }
232
+
233
+ public static void initCachingPolicy (CachingPolicy policy ) { /* ... */ }
234
+
235
+ public static void initCacheCapacity (int capacity ) { /* ... */ }
236
+
237
+ public static UserAccount find (String userId ) {
238
+ if (cachingPolicy == CachingPolicy . THROUGH || cachingPolicy == CachingPolicy . AROUND ) {
239
+ return CacheStore . readThrough(userId);
240
+ } else if (cachingPolicy == CachingPolicy . BEHIND ) {
241
+ return CacheStore . readThroughWithWriteBackPolicy(userId);
242
+ } else if (cachingPolicy == CachingPolicy . ASIDE ) {
243
+ return findAside(userId);
244
+ }
245
+ return null ;
246
+ }
247
+
248
+ public static void save (UserAccount userAccount ) {
249
+ if (cachingPolicy == CachingPolicy . THROUGH ) {
250
+ CacheStore . writeThrough(userAccount);
251
+ } else if (cachingPolicy == CachingPolicy . AROUND ) {
252
+ CacheStore . writeAround(userAccount);
253
+ } else if (cachingPolicy == CachingPolicy . BEHIND ) {
254
+ CacheStore . writeBehind(userAccount);
255
+ } else if (cachingPolicy == CachingPolicy . ASIDE ) {
256
+ saveAside(userAccount);
257
+ }
258
+ }
259
+
260
+ public static String printCacheContent () {
261
+ return CacheStore . print();
262
+ }
263
+
264
+ /* ... details omitted ... */
265
+ }
266
+ ```
267
+
268
+ Here is what we do in the main class of the application.
269
+
270
+ ``` java
271
+ @Slf4j
272
+ public class App {
273
+
274
+ public static void main (String [] args ) {
275
+ AppManager . initDb(false );
276
+ AppManager . initCacheCapacity(3 );
277
+ var app = new App ();
278
+ app. useReadAndWriteThroughStrategy();
279
+ app. useReadThroughAndWriteAroundStrategy();
280
+ app. useReadThroughAndWriteBehindStrategy();
281
+ app. useCacheAsideStategy();
282
+ }
283
+
284
+ public void useReadAndWriteThroughStrategy () {
285
+ LOGGER . info(" # CachingPolicy.THROUGH" );
286
+ AppManager . initCachingPolicy(CachingPolicy . THROUGH );
287
+ var userAccount1 = new UserAccount (" 001" , " John" , " He is a boy." );
288
+ AppManager . save(userAccount1);
289
+ LOGGER . info(AppManager . printCacheContent());
290
+ AppManager . find(" 001" );
291
+ AppManager . find(" 001" );
292
+ }
293
+
294
+ public void useReadThroughAndWriteAroundStrategy () { /* ... */ }
295
+
296
+ public void useReadThroughAndWriteBehindStrategy () { /* ... */ }
297
+
298
+ public void useCacheAsideStategy () { /* ... */ }
299
+ }
300
+ ```
301
+
302
+ Finally, here is some of the console output from the program.
303
+
304
+ ```
305
+ 12:32:53.845 [main] INFO com.iluwatar.caching.App - # CachingPolicy.THROUGH
306
+ 12:32:53.900 [main] INFO com.iluwatar.caching.App -
307
+ --CACHE CONTENT--
308
+ UserAccount(userId=001, userName=John, additionalInfo=He is a boy.)
309
+ ----
310
+ ```
16
311
17
312
## Class diagram
313
+
18
314
![ alt text] ( ./etc/caching.png " Caching ")
19
315
20
316
## Applicability
317
+
21
318
Use the Caching pattern(s) when
22
319
23
- * Repetitious acquisition, initialization, and release of the same resource causes unnecessary performance overhead.
320
+ * Repetitious acquisition, initialization, and release of the same resource cause unnecessary
321
+ performance overhead.
322
+
323
+ ## Related patterns
324
+
325
+ * [ Proxy] ( https://java-design-patterns.com/patterns/proxy/ )
24
326
25
327
## Credits
26
328
27
329
* [ Write-through, write-around, write-back: Cache explained] ( http://www.computerweekly.com/feature/Write-through-write-around-write-back-Cache-explained )
28
330
* [ Read-Through, Write-Through, Write-Behind, and Refresh-Ahead Caching] ( https://docs.oracle.com/cd/E15357_01/coh.360/e15723/cache_rtwtwbra.htm#COHDG5177 )
29
331
* [ Cache-Aside pattern] ( https://docs.microsoft.com/en-us/azure/architecture/patterns/cache-aside )
332
+ * [ Java EE 8 High Performance: Master techniques such as memory optimization, caching, concurrency, and multithreading to achieve maximum performance from your enterprise applications] ( https://www.amazon.com/gp/product/178847306X/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=178847306X&linkId=e948720055599f248cdac47da9125ff4 )
333
+ * [ Java Performance: In-Depth Advice for Tuning and Programming Java 8, 11, and Beyond] ( https://www.amazon.com/gp/product/1492056111/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=1492056111&linkId=7e553581559b9ec04221259e52004b08 )
334
+ * [ Effective Java] ( https://www.amazon.com/gp/product/B078H61SCH/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=B078H61SCH&linkId=f06607a0b48c76541ef19c5b8b9e7882 )
335
+ * [ Java Performance: The Definitive Guide: Getting the Most Out of Your Code] ( https://www.amazon.com/gp/product/1449358454/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=1449358454&linkId=475c18363e350630cc0b39ab681b2687 )
0 commit comments