mirror of
https://github.com/tiennm99/java-design-patterns.git
synced 2026-05-18 21:25:52 +00:00
docs: add editorconfig + formatting
This commit is contained in:
+177
-177
@@ -3,9 +3,9 @@ title: Caching
|
||||
category: Performance optimization
|
||||
language: en
|
||||
tag:
|
||||
- Caching
|
||||
- Performance
|
||||
- Cloud distributed
|
||||
- Caching
|
||||
- Performance
|
||||
- Cloud distributed
|
||||
---
|
||||
|
||||
## Intent
|
||||
@@ -24,9 +24,9 @@ again.
|
||||
Real world example
|
||||
|
||||
> A team is working on a website that provides new homes for abandoned cats. People can post their cats on the website
|
||||
> after registering, but all the new posts require approval from one of the site moderators. The user accounts of the site
|
||||
> moderators contain a specific flag and the data is stored in a MongoDB database. Checking for the moderator flag each
|
||||
> time a post is viewed becomes expensive, and it's a good idea to utilize caching here.
|
||||
> after registering, but all the new posts require approval from one of the site moderators. The user accounts of the
|
||||
> site moderators contain a specific flag and the data is stored in a MongoDB database. Checking for the moderator flag
|
||||
> each time a post is viewed becomes expensive, and it's a good idea to utilize caching here.
|
||||
|
||||
In plain words
|
||||
|
||||
@@ -36,9 +36,9 @@ Wikipedia says:
|
||||
|
||||
> In computing, a cache is a hardware or software component that stores data so that future requests for that data can
|
||||
> be served faster; the data stored in a cache might be the result of an earlier computation or a copy of data stored
|
||||
> elsewhere. A cache hit occurs when the requested data can be found in a cache, while a cache miss occurs when it cannot.
|
||||
> Cache hits are served by reading data from the cache, which is faster than recomputing a result or reading from a slower
|
||||
> data store; thus, the more requests that can be served from the cache, the faster the system performs.
|
||||
> elsewhere. A cache hit occurs when the requested data can be found in a cache, while a cache miss occurs when it
|
||||
> cannot. Cache hits are served by reading data from the cache, which is faster than recomputing a result or reading
|
||||
> from a slower data store; thus, the more requests that can be served from the cache, the faster the system performs.
|
||||
|
||||
**Programmatic Example**
|
||||
|
||||
@@ -53,24 +53,24 @@ to/from database.
|
||||
@ToString
|
||||
@EqualsAndHashCode
|
||||
public class UserAccount {
|
||||
private String userId;
|
||||
private String userName;
|
||||
private String additionalInfo;
|
||||
private String userId;
|
||||
private String userName;
|
||||
private String additionalInfo;
|
||||
}
|
||||
|
||||
public interface DbManager {
|
||||
|
||||
void connect();
|
||||
void connect();
|
||||
|
||||
void disconnect();
|
||||
void disconnect();
|
||||
|
||||
UserAccount readFromDb(String userId);
|
||||
UserAccount readFromDb(String userId);
|
||||
|
||||
UserAccount writeToDb(UserAccount userAccount);
|
||||
UserAccount writeToDb(UserAccount userAccount);
|
||||
|
||||
UserAccount updateDb(UserAccount userAccount);
|
||||
UserAccount updateDb(UserAccount userAccount);
|
||||
|
||||
UserAccount upsertDb(UserAccount userAccount);
|
||||
UserAccount upsertDb(UserAccount userAccount);
|
||||
}
|
||||
```
|
||||
|
||||
@@ -96,73 +96,73 @@ always at the end of the list.
|
||||
@Slf4j
|
||||
public class LruCache {
|
||||
|
||||
static class Node {
|
||||
String userId;
|
||||
UserAccount userAccount;
|
||||
Node previous;
|
||||
Node next;
|
||||
static class Node {
|
||||
String userId;
|
||||
UserAccount userAccount;
|
||||
Node previous;
|
||||
Node next;
|
||||
|
||||
public Node(String userId, UserAccount userAccount) {
|
||||
this.userId = userId;
|
||||
this.userAccount = userAccount;
|
||||
public Node(String userId, UserAccount userAccount) {
|
||||
this.userId = userId;
|
||||
this.userAccount = userAccount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ... omitted details ... */
|
||||
/* ... omitted details ... */
|
||||
|
||||
public LruCache(int capacity) {
|
||||
this.capacity = capacity;
|
||||
}
|
||||
|
||||
public UserAccount get(String userId) {
|
||||
if (cache.containsKey(userId)) {
|
||||
var node = cache.get(userId);
|
||||
remove(node);
|
||||
setHead(node);
|
||||
return node.userAccount;
|
||||
public LruCache(int capacity) {
|
||||
this.capacity = capacity;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void set(String userId, UserAccount userAccount) {
|
||||
if (cache.containsKey(userId)) {
|
||||
var old = cache.get(userId);
|
||||
old.userAccount = userAccount;
|
||||
remove(old);
|
||||
setHead(old);
|
||||
} else {
|
||||
var newNode = new Node(userId, userAccount);
|
||||
if (cache.size() >= capacity) {
|
||||
LOGGER.info("# Cache is FULL! Removing {} from cache...", end.userId);
|
||||
cache.remove(end.userId); // remove LRU data from cache.
|
||||
remove(end);
|
||||
setHead(newNode);
|
||||
} else {
|
||||
setHead(newNode);
|
||||
}
|
||||
cache.put(userId, newNode);
|
||||
public UserAccount get(String userId) {
|
||||
if (cache.containsKey(userId)) {
|
||||
var node = cache.get(userId);
|
||||
remove(node);
|
||||
setHead(node);
|
||||
return node.userAccount;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean contains(String userId) {
|
||||
return cache.containsKey(userId);
|
||||
}
|
||||
public void set(String userId, UserAccount userAccount) {
|
||||
if (cache.containsKey(userId)) {
|
||||
var old = cache.get(userId);
|
||||
old.userAccount = userAccount;
|
||||
remove(old);
|
||||
setHead(old);
|
||||
} else {
|
||||
var newNode = new Node(userId, userAccount);
|
||||
if (cache.size() >= capacity) {
|
||||
LOGGER.info("# Cache is FULL! Removing {} from cache...", end.userId);
|
||||
cache.remove(end.userId); // remove LRU data from cache.
|
||||
remove(end);
|
||||
setHead(newNode);
|
||||
} else {
|
||||
setHead(newNode);
|
||||
}
|
||||
cache.put(userId, newNode);
|
||||
}
|
||||
}
|
||||
|
||||
public void remove(Node node) { /* ... */ }
|
||||
public boolean contains(String userId) {
|
||||
return cache.containsKey(userId);
|
||||
}
|
||||
|
||||
public void setHead(Node node) { /* ... */ }
|
||||
public void remove(Node node) { /* ... */ }
|
||||
|
||||
public void invalidate(String userId) { /* ... */ }
|
||||
public void setHead(Node node) { /* ... */ }
|
||||
|
||||
public boolean isFull() { /* ... */ }
|
||||
public void invalidate(String userId) { /* ... */ }
|
||||
|
||||
public UserAccount getLruData() { /* ... */ }
|
||||
public boolean isFull() { /* ... */ }
|
||||
|
||||
public void clear() { /* ... */ }
|
||||
public UserAccount getLruData() { /* ... */ }
|
||||
|
||||
public List<UserAccount> getCacheDataInListForm() { /* ... */ }
|
||||
public void clear() { /* ... */ }
|
||||
|
||||
public void setCapacity(int newCapacity) { /* ... */ }
|
||||
public List<UserAccount> getCacheDataInListForm() { /* ... */ }
|
||||
|
||||
public void setCapacity(int newCapacity) { /* ... */ }
|
||||
}
|
||||
```
|
||||
|
||||
@@ -173,58 +173,58 @@ The next layer we are going to look at is `CacheStore` which implements the diff
|
||||
@Slf4j
|
||||
public class CacheStore {
|
||||
|
||||
private static final int CAPACITY = 3;
|
||||
private static LruCache cache;
|
||||
private final DbManager dbManager;
|
||||
private static final int CAPACITY = 3;
|
||||
private static LruCache cache;
|
||||
private final DbManager dbManager;
|
||||
|
||||
/* ... details omitted ... */
|
||||
/* ... details omitted ... */
|
||||
|
||||
public UserAccount readThrough(final String userId) {
|
||||
if (cache.contains(userId)) {
|
||||
LOGGER.info("# Found in Cache!");
|
||||
return cache.get(userId);
|
||||
public UserAccount readThrough(final String userId) {
|
||||
if (cache.contains(userId)) {
|
||||
LOGGER.info("# Found in Cache!");
|
||||
return cache.get(userId);
|
||||
}
|
||||
LOGGER.info("# Not found in cache! Go to DB!!");
|
||||
UserAccount userAccount = dbManager.readFromDb(userId);
|
||||
cache.set(userId, userAccount);
|
||||
return userAccount;
|
||||
}
|
||||
LOGGER.info("# Not found in cache! Go to DB!!");
|
||||
UserAccount userAccount = dbManager.readFromDb(userId);
|
||||
cache.set(userId, userAccount);
|
||||
return userAccount;
|
||||
}
|
||||
|
||||
public void writeThrough(final UserAccount userAccount) {
|
||||
if (cache.contains(userAccount.getUserId())) {
|
||||
dbManager.updateDb(userAccount);
|
||||
} else {
|
||||
dbManager.writeToDb(userAccount);
|
||||
public void writeThrough(final UserAccount userAccount) {
|
||||
if (cache.contains(userAccount.getUserId())) {
|
||||
dbManager.updateDb(userAccount);
|
||||
} else {
|
||||
dbManager.writeToDb(userAccount);
|
||||
}
|
||||
cache.set(userAccount.getUserId(), userAccount);
|
||||
}
|
||||
cache.set(userAccount.getUserId(), userAccount);
|
||||
}
|
||||
|
||||
public void writeAround(final UserAccount userAccount) {
|
||||
if (cache.contains(userAccount.getUserId())) {
|
||||
dbManager.updateDb(userAccount);
|
||||
// Cache data has been updated -- remove older
|
||||
cache.invalidate(userAccount.getUserId());
|
||||
// version from cache.
|
||||
} else {
|
||||
dbManager.writeToDb(userAccount);
|
||||
public void writeAround(final UserAccount userAccount) {
|
||||
if (cache.contains(userAccount.getUserId())) {
|
||||
dbManager.updateDb(userAccount);
|
||||
// Cache data has been updated -- remove older
|
||||
cache.invalidate(userAccount.getUserId());
|
||||
// version from cache.
|
||||
} else {
|
||||
dbManager.writeToDb(userAccount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void clearCache() {
|
||||
if (cache != null) {
|
||||
cache.clear();
|
||||
public static void clearCache() {
|
||||
if (cache != null) {
|
||||
cache.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void flushCache() {
|
||||
LOGGER.info("# flushCache...");
|
||||
Optional.ofNullable(cache)
|
||||
.map(LruCache::getCacheDataInListForm)
|
||||
.orElse(List.of())
|
||||
.forEach(DbManager::updateDb);
|
||||
}
|
||||
public static void flushCache() {
|
||||
LOGGER.info("# flushCache...");
|
||||
Optional.ofNullable(cache)
|
||||
.map(LruCache::getCacheDataInListForm)
|
||||
.orElse(List.of())
|
||||
.forEach(DbManager::updateDb);
|
||||
}
|
||||
|
||||
/* ... omitted the implementation of other caching strategies ... */
|
||||
/* ... omitted the implementation of other caching strategies ... */
|
||||
|
||||
}
|
||||
```
|
||||
@@ -239,50 +239,50 @@ the appropriate function in the `CacheStore` class.
|
||||
@Slf4j
|
||||
public final class AppManager {
|
||||
|
||||
private static CachingPolicy cachingPolicy;
|
||||
private final DbManager dbManager;
|
||||
private final CacheStore cacheStore;
|
||||
private static CachingPolicy cachingPolicy;
|
||||
private final DbManager dbManager;
|
||||
private final CacheStore cacheStore;
|
||||
|
||||
private AppManager() {
|
||||
}
|
||||
|
||||
public void initDb() { /* ... */ }
|
||||
|
||||
public static void initCachingPolicy(CachingPolicy policy) { /* ... */ }
|
||||
|
||||
public static void initCacheCapacity(int capacity) { /* ... */ }
|
||||
|
||||
public UserAccount find(final String userId) {
|
||||
LOGGER.info("Trying to find {} in cache", userId);
|
||||
if (cachingPolicy == CachingPolicy.THROUGH
|
||||
|| cachingPolicy == CachingPolicy.AROUND) {
|
||||
return cacheStore.readThrough(userId);
|
||||
} else if (cachingPolicy == CachingPolicy.BEHIND) {
|
||||
return cacheStore.readThroughWithWriteBackPolicy(userId);
|
||||
} else if (cachingPolicy == CachingPolicy.ASIDE) {
|
||||
return findAside(userId);
|
||||
private AppManager() {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void save(final UserAccount userAccount) {
|
||||
LOGGER.info("Save record!");
|
||||
if (cachingPolicy == CachingPolicy.THROUGH) {
|
||||
cacheStore.writeThrough(userAccount);
|
||||
} else if (cachingPolicy == CachingPolicy.AROUND) {
|
||||
cacheStore.writeAround(userAccount);
|
||||
} else if (cachingPolicy == CachingPolicy.BEHIND) {
|
||||
cacheStore.writeBehind(userAccount);
|
||||
} else if (cachingPolicy == CachingPolicy.ASIDE) {
|
||||
saveAside(userAccount);
|
||||
public void initDb() { /* ... */ }
|
||||
|
||||
public static void initCachingPolicy(CachingPolicy policy) { /* ... */ }
|
||||
|
||||
public static void initCacheCapacity(int capacity) { /* ... */ }
|
||||
|
||||
public UserAccount find(final String userId) {
|
||||
LOGGER.info("Trying to find {} in cache", userId);
|
||||
if (cachingPolicy == CachingPolicy.THROUGH
|
||||
|| cachingPolicy == CachingPolicy.AROUND) {
|
||||
return cacheStore.readThrough(userId);
|
||||
} else if (cachingPolicy == CachingPolicy.BEHIND) {
|
||||
return cacheStore.readThroughWithWriteBackPolicy(userId);
|
||||
} else if (cachingPolicy == CachingPolicy.ASIDE) {
|
||||
return findAside(userId);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static String printCacheContent() {
|
||||
return CacheStore.print();
|
||||
}
|
||||
public void save(final UserAccount userAccount) {
|
||||
LOGGER.info("Save record!");
|
||||
if (cachingPolicy == CachingPolicy.THROUGH) {
|
||||
cacheStore.writeThrough(userAccount);
|
||||
} else if (cachingPolicy == CachingPolicy.AROUND) {
|
||||
cacheStore.writeAround(userAccount);
|
||||
} else if (cachingPolicy == CachingPolicy.BEHIND) {
|
||||
cacheStore.writeBehind(userAccount);
|
||||
} else if (cachingPolicy == CachingPolicy.ASIDE) {
|
||||
saveAside(userAccount);
|
||||
}
|
||||
}
|
||||
|
||||
/* ... details omitted ... */
|
||||
public static String printCacheContent() {
|
||||
return CacheStore.print();
|
||||
}
|
||||
|
||||
/* ... details omitted ... */
|
||||
}
|
||||
```
|
||||
|
||||
@@ -293,42 +293,42 @@ Here is what we do in the main class of the application.
|
||||
@Slf4j
|
||||
public class App {
|
||||
|
||||
public static void main(final String[] args) {
|
||||
boolean isDbMongo = isDbMongo(args);
|
||||
if (isDbMongo) {
|
||||
LOGGER.info("Using the Mongo database engine to run the application.");
|
||||
} else {
|
||||
LOGGER.info("Using the 'in Memory' database to run the application.");
|
||||
public static void main(final String[] args) {
|
||||
boolean isDbMongo = isDbMongo(args);
|
||||
if (isDbMongo) {
|
||||
LOGGER.info("Using the Mongo database engine to run the application.");
|
||||
} else {
|
||||
LOGGER.info("Using the 'in Memory' database to run the application.");
|
||||
}
|
||||
App app = new App(isDbMongo);
|
||||
app.useReadAndWriteThroughStrategy();
|
||||
String splitLine = "==============================================";
|
||||
LOGGER.info(splitLine);
|
||||
app.useReadThroughAndWriteAroundStrategy();
|
||||
LOGGER.info(splitLine);
|
||||
app.useReadThroughAndWriteBehindStrategy();
|
||||
LOGGER.info(splitLine);
|
||||
app.useCacheAsideStategy();
|
||||
LOGGER.info(splitLine);
|
||||
}
|
||||
App app = new App(isDbMongo);
|
||||
app.useReadAndWriteThroughStrategy();
|
||||
String splitLine = "==============================================";
|
||||
LOGGER.info(splitLine);
|
||||
app.useReadThroughAndWriteAroundStrategy();
|
||||
LOGGER.info(splitLine);
|
||||
app.useReadThroughAndWriteBehindStrategy();
|
||||
LOGGER.info(splitLine);
|
||||
app.useCacheAsideStategy();
|
||||
LOGGER.info(splitLine);
|
||||
}
|
||||
|
||||
public void useReadAndWriteThroughStrategy() {
|
||||
LOGGER.info("# CachingPolicy.THROUGH");
|
||||
appManager.initCachingPolicy(CachingPolicy.THROUGH);
|
||||
public void useReadAndWriteThroughStrategy() {
|
||||
LOGGER.info("# CachingPolicy.THROUGH");
|
||||
appManager.initCachingPolicy(CachingPolicy.THROUGH);
|
||||
|
||||
var userAccount1 = new UserAccount("001", "John", "He is a boy.");
|
||||
var userAccount1 = new UserAccount("001", "John", "He is a boy.");
|
||||
|
||||
appManager.save(userAccount1);
|
||||
LOGGER.info(appManager.printCacheContent());
|
||||
appManager.find("001");
|
||||
appManager.find("001");
|
||||
}
|
||||
appManager.save(userAccount1);
|
||||
LOGGER.info(appManager.printCacheContent());
|
||||
appManager.find("001");
|
||||
appManager.find("001");
|
||||
}
|
||||
|
||||
public void useReadThroughAndWriteAroundStrategy() { /* ... */ }
|
||||
public void useReadThroughAndWriteAroundStrategy() { /* ... */ }
|
||||
|
||||
public void useReadThroughAndWriteBehindStrategy() { /* ... */ }
|
||||
public void useReadThroughAndWriteBehindStrategy() { /* ... */ }
|
||||
|
||||
public void useCacheAsideStrategy() { /* ... */ }
|
||||
public void useCacheAsideStrategy() { /* ... */ }
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Reference in New Issue
Block a user