Skip to content

Caching pattern: Cache-Aside #495

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Oct 17, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions caching/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@ Use the Caching pattern(s) when

* [Write-through, write-around, write-back: Cache explained](http://www.computerweekly.com/feature/Write-through-write-around-write-back-Cache-explained)
* [Read-Through, Write-Through, Write-Behind, and Refresh-Ahead Caching](https://docs.oracle.com/cd/E15357_01/coh.360/e15723/cache_rtwtwbra.htm#COHDG5177)
* [Cache-Aside](https://msdn.microsoft.com/en-us/library/dn589799.aspx)
Binary file modified caching/etc/caching.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
190 changes: 87 additions & 103 deletions caching/etc/caching.ucls
Original file line number Diff line number Diff line change
@@ -1,106 +1,90 @@
<?xml version="1.0" encoding="UTF-8"?>
<class-diagram version="1.1.8" icons="true" automaticImage="PNG" always-add-relationships="false" generalizations="true"
realizations="true" associations="true" dependencies="false" nesting-relationships="true">
<class id="1" language="java" name="main.java.com.wssia.caching.App" project="CachingPatterns"
file="/CachingPatterns/src/main/java/com/wssia/caching/App.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="249" y="150"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
<class-diagram version="1.1.11" icons="true" automaticImage="PNG" always-add-relationships="false"
generalizations="true" realizations="true" associations="true" dependencies="false" nesting-relationships="true"
router="FAN">
<class id="1" language="java" name="com.iluwatar.caching.CacheStore" project="caching"
file="/caching/src/main/java/com/iluwatar/caching/CacheStore.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="322" y="444"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<class id="2" language="java" name="com.iluwatar.caching.LruCache" project="caching"
file="/caching/src/main/java/com/iluwatar/caching/LruCache.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="700" y="446"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<enumeration id="3" language="java" name="com.iluwatar.caching.CachingPolicy" project="caching"
file="/caching/src/main/java/com/iluwatar/caching/CachingPolicy.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="943" y="122"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</enumeration>
<class id="4" language="java" name="com.iluwatar.caching.DbManager" project="caching"
file="/caching/src/main/java/com/iluwatar/caching/DbManager.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="1011" y="426"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<class id="5" language="java" name="com.iluwatar.caching.App" project="caching"
file="/caching/src/main/java/com/iluwatar/caching/App.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="304" y="95"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<class id="6" language="java" name="com.iluwatar.caching.AppManager" project="caching"
file="/caching/src/main/java/com/iluwatar/caching/AppManager.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="604" y="122"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<class id="7" language="java" name="com.iluwatar.caching.UserAccount" project="caching"
file="/caching/src/main/java/com/iluwatar/caching/UserAccount.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="1161" y="139"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<class id="2" language="java" name="main.java.com.wssia.caching.AppManager" project="CachingPatterns"
file="/CachingPatterns/src/main/java/com/wssia/caching/AppManager.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="502" y="163"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<class id="3" language="java" name="main.java.com.wssia.caching.CacheStore" project="CachingPatterns"
file="/CachingPatterns/src/main/java/com/wssia/caching/CacheStore.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="537" y="436"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<enumeration id="4" language="java" name="main.java.com.wssia.caching.CachingPolicy" project="CachingPatterns"
file="/CachingPatterns/src/main/java/com/wssia/caching/CachingPolicy.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="789" y="162"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</enumeration>
<class id="5" language="java" name="main.java.com.wssia.caching.DBManager" project="CachingPatterns"
file="/CachingPatterns/src/main/java/com/wssia/caching/DBManager.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="1137" y="134"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<class id="6" language="java" name="main.java.com.wssia.caching.LRUCache" project="CachingPatterns"
file="/CachingPatterns/src/main/java/com/wssia/caching/LRUCache.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="884" y="435"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<class id="7" language="java" name="main.java.com.wssia.caching.UserAccount" project="CachingPatterns"
file="/CachingPatterns/src/main/java/com/wssia/caching/UserAccount.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="1140" y="405"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<class id="8" language="java" name="test.java.com.wssia.caching.AppTest" project="CachingPatterns"
file="/CachingPatterns/src/test/java/com/wssia/caching/AppTest.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="251" y="374"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<association id="9">
<end type="SOURCE" refId="2" navigable="false">
<attribute id="10" name="cachingPolicy"/>
<multiplicity id="11" minimum="0" maximum="1"/>
</end>
<end type="TARGET" refId="4" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<association id="12">
<end type="SOURCE" refId="8" navigable="false">
<attribute id="13" name="app"/>
<multiplicity id="14" minimum="0" maximum="1"/>
</end>
<end type="TARGET" refId="1" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<association id="15">
<end type="SOURCE" refId="3" navigable="false">
<attribute id="16" name="cache"/>
<multiplicity id="17" minimum="0" maximum="1"/>
</end>
<end type="TARGET" refId="6" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<classifier-display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</classifier-display>
<association id="8">
<end type="SOURCE" refId="1" navigable="false">
<attribute id="9" name="cache"/>
<multiplicity id="10" minimum="0" maximum="1"/>
</end>
<end type="TARGET" refId="2" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<association id="11">
<end type="SOURCE" refId="6" navigable="false">
<attribute id="12" name="cachingPolicy"/>
<multiplicity id="13" minimum="0" maximum="1"/>
</end>
<end type="TARGET" refId="3" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<classifier-display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</classifier-display>
<association-display labels="true" multiplicity="true"/>
</class-diagram>
</class-diagram>
46 changes: 35 additions & 11 deletions caching/src/main/java/com/iluwatar/caching/App.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,22 +26,23 @@
*
* The Caching pattern describes how to avoid expensive re-acquisition of resources by not releasing
* the resources immediately after their use. The resources retain their identity, are kept in some
* fast-access storage, and are re-used to avoid having to acquire them again. There are three main
* caching strategies/techniques in this pattern; each with their own pros and cons. They are:
* fast-access storage, and are re-used to avoid having to acquire them again. There are four main
* caching strategies/techniques in this pattern; each with their own pros and cons. They are;
* <code>write-through</code> which writes data to the cache and DB in a single transaction,
* <code>write-around</code> which writes data immediately into the DB instead of the cache, and
* <code>write-around</code> which writes data immediately into the DB instead of the cache,
* <code>write-behind</code> which writes data into the cache initially whilst the data is only
* written into the DB when the cache is full. The <code>read-through</code> strategy is also
* included in the mentioned three strategies -- returns data from the cache to the caller <b>if</b>
* it exists <b>else</b> queries from DB and stores it into the cache for future use. These
* strategies determine when the data in the cache should be written back to the backing store (i.e.
* Database) and help keep both data sources synchronized/up-to-date. This pattern can improve
* performance and also helps to maintain consistency between data held in the cache and the data in
* the underlying data store.
* written into the DB when the cache is full, and <code>cache-aside</code> which pushes the
* responsibility of keeping the data synchronized in both data sources to the application itself.
* The <code>read-through</code> strategy is also included in the mentioned four strategies --
* returns data from the cache to the caller <b>if</b> it exists <b>else</b> queries from DB and
* stores it into the cache for future use. These strategies determine when the data in the cache
* should be written back to the backing store (i.e. Database) and help keep both data sources
* synchronized/up-to-date. This pattern can improve performance and also helps to maintain
* consistency between data held in the cache and the data in the underlying data store.
* <p>
* In this example, the user account ({@link UserAccount}) entity is used as the underlying
* application data. The cache itself is implemented as an internal (Java) data structure. It adopts
* a Least-Recently-Used (LRU) strategy for evicting data from itself when its full. The three
* a Least-Recently-Used (LRU) strategy for evicting data from itself when its full. The four
* strategies are individually tested. The testing of the cache is restricted towards saving and
* querying of user accounts from the underlying data store ( {@link DbManager}). The main class (
* {@link App} is not aware of the underlying mechanics of the application (i.e. save and query) and
Expand Down Expand Up @@ -74,6 +75,7 @@ public static void main(String[] args) {
app.useReadAndWriteThroughStrategy();
app.useReadThroughAndWriteAroundStrategy();
app.useReadThroughAndWriteBehindStrategy();
app.useCacheAsideStategy();
}

/**
Expand Down Expand Up @@ -136,4 +138,26 @@ public void useReadThroughAndWriteBehindStrategy() {
AppManager.find("004");
System.out.println(AppManager.printCacheContent());
}

/**
* Cache-Aside
*/
public void useCacheAsideStategy() {
System.out.println("# CachingPolicy.ASIDE");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any reason for not using logging framework like SLF4J or LOG4J Or Common logging?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chinnachalam you are right, using a logging framework would give some advantages such as ability to redirect output and filter output based on logging levels. But I think we should handle this issue on a more general level since there are multiple patterns not using a logging framework at the moment. Would you raise an issue about this matter, please?

AppManager.initCachingPolicy(CachingPolicy.ASIDE);
System.out.println(AppManager.printCacheContent());

UserAccount userAccount3 = new UserAccount("003", "Adam", "He likes food.");
UserAccount userAccount4 = new UserAccount("004", "Rita", "She hates cats.");
UserAccount userAccount5 = new UserAccount("005", "Isaac", "He is allergic to mustard.");
AppManager.save(userAccount3);
AppManager.save(userAccount4);
AppManager.save(userAccount5);

System.out.println(AppManager.printCacheContent());
AppManager.find("003");
System.out.println(AppManager.printCacheContent());
AppManager.find("004");
System.out.println(AppManager.printCacheContent());
}
}
36 changes: 30 additions & 6 deletions caching/src/main/java/com/iluwatar/caching/AppManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,7 @@ public static void initDb(boolean useMongoDb) {
public static void initCachingPolicy(CachingPolicy policy) {
cachingPolicy = policy;
if (cachingPolicy == CachingPolicy.BEHIND) {
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
@Override
public void run() {
CacheStore.flushCache();
}
}));
Runtime.getRuntime().addShutdownHook(new Thread(CacheStore::flushCache));
}
CacheStore.clearCache();
}
Expand All @@ -86,6 +81,8 @@ public static UserAccount find(String userId) {
return CacheStore.readThrough(userId);
} else if (cachingPolicy == CachingPolicy.BEHIND) {
return CacheStore.readThroughWithWriteBackPolicy(userId);
} else if (cachingPolicy == CachingPolicy.ASIDE) {
return findAside(userId);
}
return null;
}
Expand All @@ -100,10 +97,37 @@ public static void save(UserAccount userAccount) {
CacheStore.writeAround(userAccount);
} else if (cachingPolicy == CachingPolicy.BEHIND) {
CacheStore.writeBehind(userAccount);
} else if (cachingPolicy == CachingPolicy.ASIDE) {
saveAside(userAccount);
}
}

public static String printCacheContent() {
return CacheStore.print();
}

/**
* Cache-Aside save user account helper
*/
private static void saveAside(UserAccount userAccount) {
DbManager.updateDb(userAccount);
CacheStore.invalidate(userAccount.getUserId());
}

/**
* Cache-Aside find user account helper
*/
private static UserAccount findAside(String userId) {
UserAccount userAccount = CacheStore.get(userId);
if (userAccount != null) {
return userAccount;
}

userAccount = DbManager.readFromDb(userId);
if (userAccount != null) {
CacheStore.set(userId, userAccount);
}

return userAccount;
}
}
Loading