Skip to content
This repository was archived by the owner on Jul 22, 2021. It is now read-only.

Commit b44ae37

Browse files
committed
Merge pull request #16 from cloudant/13-revision-doc
13 revision doc
2 parents b5dfe4d + 8ab7a8a commit b44ae37

3 files changed

Lines changed: 275 additions & 8 deletions

File tree

cloudant-client-cache-in-process/overview.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@
3333

3434
<P>
3535
For additional information see the javadoc for
36-
<a href="http://www.javadoc.io/doc/com.cloudant/cloudant-client-cache/">cloudant-client-cache
36+
<a href="http://www.javadoc.io/doc/com.cloudant/cloudant-client-cache/" target="_blank">
37+
cloudant-client-cache
3738
</a>.
3839
</P>
3940
</body>

cloudant-client-cache-redis/overview.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@
3232

3333
<P>
3434
For additional information see the javadoc for
35-
<a href="http://www.javadoc.io/doc/com.cloudant/cloudant-client-cache/">cloudant-client-cache
35+
<a href="http://www.javadoc.io/doc/com.cloudant/cloudant-client-cache/" target="_blank">
36+
cloudant-client-cache
3637
</a>.
3738
</P>
3839
</body>

cloudant-client-cache/overview.html

Lines changed: 271 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,27 +23,292 @@
2323
<P>
2424
This project provides functionality for using a local cache for Cloudant databases with the
2525
java-cloudant cloudant-client.
26+
By using client-side caching, data (that can be cached without violating consistency
27+
constraints) can be served directly from the client, eliminating the need to fetch the data from
28+
the server. This can considerably reduce the latency for data accesses.
2629
</P>
2730

31+
<h1>Basics</h1>
32+
2833
<P>
2934
Caches implement the {@link com.cloudant.client.cache.Cache} interface (or one of its
3035
sub-interfaces). An example implementation is given in
3136
{@link com.cloudant.client.cache.LRUCache}, but the
32-
<a href="http://www.javadoc.io/doc/com.cloudant/cloudant-client-cache-in-process/">
37+
<a href="http://www.javadoc.io/doc/com.cloudant/cloudant-client-cache-in-process/"
38+
target="_blank">
3339
cloudant-client-cache-in-process</a> and
34-
<a href="http://www.javadoc.io/doc/com.cloudant/cloudant-client-cache-redis/">
40+
<a href="http://www.javadoc.io/doc/com.cloudant/cloudant-client-cache-redis/" target="_blank">
3541
cloudant-client-cache-redis</a> projects provide alternative implementations.
3642
</P>
3743

3844
<P>
39-
{@link com.cloudant.client.cache.DatabaseCache} and its sub-classes implement the java-cloudant
40-
client's {@link com.cloudant.client.api.Database} interface, preferentially using the cache and
45+
{@link com.cloudant.client.cache.DatabaseCache} and its sub-classes extend the java-cloudant
46+
client's {@link com.cloudant.client.api.Database} class, preferentially using the cache and
4147
then delegating to the remote database.
4248
</P>
49+
<pre>
50+
{@code
51+
// Instantiate a cache, in this example an LRUCache with a maximum capacity of 100 objects
52+
Cache<String, Object> cache = new LRUCache<>(100);
53+
54+
// Get the Cloudant client instance and database object for the desired database.
55+
CloudantClient client = ClientBuilder.account("example").build();
56+
Database db = client.database("example-database", false);
57+
58+
// Create a new DatabaseCache with the cache and your com.cloudant.client.api.Database
59+
// instance.
60+
Database cachedDb = new DatabaseCache(db, cache);
61+
// Use this cachedDb instance in place of your normal db instance to utilise the cache.
62+
// It may be worth keeping references to both the cached and un-cached instances if you want to
63+
// switch between cached and un-cached access to the database.
64+
65+
// Example 1: Get document with ID "abcdef" from the cache if available, or from the database if
66+
// not yet cached.
67+
MyDocument abc = cachedDb.find(MyDocument.class, "abcdef");
68+
69+
// Example 2: Use the original Database instance, db, to get document "abcdef" direct from the
70+
// remote database, bypassing the cache.
71+
MyDocument abc = db.find(MyDocument.class, "abcdef");
72+
}
73+
</pre>
74+
75+
<P>
76+
See the <a href="http://www.javadoc.io/doc/com.cloudant/cloudant-client/" target="_blank">
77+
java-cloudant client javadoc</a> for more information about the client.
78+
</P>
79+
80+
<h1>Cache types</h1>
81+
82+
<UL>
83+
<LI><b>In-process cache</b>
84+
85+
<P>
86+
In-process caches store data within the process corresponding to the application. That
87+
way, there is no interprocess communication required for storing the data. For these
88+
implementations of in-process caches, Java objects can directly be cached. Data
89+
serialization is not required. In order to reduce overhead when the object is cached,
90+
the object (or a reference to it) can be stored directly in the cache. This means that
91+
changes to the object from the application could affect changes to the cached object
92+
itself. In order to prevent the value of a cached object from being modified by changes
93+
to the object being made in the application, a copy of the object can be made before the
94+
object is cached. This results in overhead for copying the object.
95+
</P>
96+
<UL>
97+
<LI>Implementations</LI>
98+
<UL>
99+
<LI>{@link com.cloudant.client.cache.LRUCache}</LI>
100+
<LI>
101+
<a href="http://www.javadoc.io/doc/com.cloudant/cloudant-client-cache-in-process/"
102+
target="_blank">
103+
cloudant-client-cache-in-process</a></LI>
104+
</UL>
105+
<LI>Advantages</LI>
106+
<UL>
107+
<LI>Extremely fast</LI>
108+
<LI>Cached objects do not have to be serialized</LI>
109+
</UL>
110+
<LI>Disadvantages</LI>
111+
<UL>
112+
<LI>Not shared by multiple clients</LI>
113+
</UL>
114+
</UL>
115+
</LI>
116+
<BR/>
117+
<LI><b>Remote process cache</b>
118+
119+
<P>
120+
In this approach, the cache runs in one or more separate processes from the application.
121+
A remote process cache can run on a separate node from the application as well. There is
122+
some overhead for communication with a remote process cache. In addition, data often has
123+
to be serialized before being cached. However, remote process caches also have some
124+
advantages over in-process caches. A remote process cache can be shared by multiple
125+
clients, and this feature is often desirable. Remote process caches can often be scaled
126+
across multiple processes and nodes to handle high request rates and increase
127+
availability.
128+
</P>
129+
<UL>
130+
<LI>Implementations</LI>
131+
<UL>
132+
<LI><a href="http://www.javadoc.io/doc/com.cloudant/cloudant-client-cache-redis/"
133+
target="_blank">
134+
cloudant-client-cache-redis</a> for use with a running <a
135+
href="http://redis.io" target="_blank">
136+
Redis</a> server
137+
</LI>
138+
</UL>
139+
<LI>Advantages</LI>
140+
<UL>
141+
<LI>Multiple clients can share cache</LI>
142+
<LI>Cache can scale to many processes</LI>
143+
</UL>
144+
<LI>Disadvantages</LI>
145+
<UL>
146+
<LI>Overhead of interprocess communication</LI>
147+
<LI>Cached objects may need to be serialized</LI>
148+
</UL>
149+
</UL>
150+
</LI>
151+
</UL>
152+
153+
<h1>Guidance on document revisions</h1>
154+
155+
<P>
156+
The revision information from a cached object has the potential to be out-of-date or missing.
157+
The reasons for this are set out at length below, but it is preceded by the short version.
158+
</P>
159+
160+
<h2>TL;DR version</h2>
161+
162+
<P>
163+
At the simplest level the revision token ({@code _rev} field) on objects retrieved using
164+
{@link com.cloudant.client.cache.DatabaseCache#find(java.lang.Class, java.lang.String)} may be
165+
obsolete or non-existent because the server side object may have been updated or the object was
166+
cached with no revision information.
167+
To get a suitable revision token for an update or a delete it is generally better to use
168+
{@link com.cloudant.client.api.Database#find(java.lang.Class, java.lang.String)} to bypass the
169+
cache or perform a HTTP HEAD request to get the document revision, which is returned in the
170+
{@code ETag} header field, without retrieving the entire document, for example:
171+
</P>
172+
<pre>
173+
{@code
174+
client.executeRequest(Http.HEAD(new URL(db.getDBUri().toString() + "/" + id)))
175+
.getConnection().getHeaderField("ETag");
176+
}
177+
</pre>
178+
179+
<h2>Detailed version</h2>
180+
181+
<P>
182+
Object instances are added to the cache unmodified. This behaviour is consistent with the
183+
<a href="http://static.javadoc.io/com.cloudant/cloudant-client/2.3.0/overview-summary.html#Document%20revisions"
184+
target="_blank">behaviour of the java-cloudant client</a>, which does not modify objects
185+
after a write to the database. Consequently an object instance created from the deserialization
186+
of a document retrieved from the database will not be equal to the object instance that was
187+
saved because the server has added a revision token.
188+
It is the user's responsibility to retrieve and store the revision token when it is necessary
189+
for the application, for example to perform an update or delete.
190+
It is possible to set the retrieved revision token on a cached object reference to bring the
191+
cached instance up-to-date with the server document at that point in time.
192+
</P>
193+
<P>
194+
Here are some examples using the java-cloudant client alone or with the cache and the resulting
195+
revision token expectations.
196+
</P>
197+
<pre>
198+
{@code
199+
// Using java-cloudant only.
200+
201+
// Create a new Foo with an _id, but no revision.
202+
Foo f = new Foo();
203+
// Save the foo in the Database.
204+
Response response = db.save(f);
205+
206+
// This example assumes no further changes have been made to the database document.
207+
// Sometime later get the Foo from the database.
208+
Foo f2 = db.find(response.getId()); // Returns deserialized document from the database.
209+
210+
f2.getRev().equals(response.getRev()); // True, f2 was deserialized from the database document
211+
f2.equals(f); // False, f has not been modified by the database write and has no revision set
212+
213+
// Set revision on f
214+
f.setRev(response.getRev());
215+
f2.equals(f); // True as now f has the same revision as f2
216+
}
217+
</pre>
218+
<P>
219+
When using a java-cloudant-cache the attempt to retrieve an object can return the original
220+
object directly from the cache without deserializing a document from the database.
221+
This results in a behaviour difference from using java-cloudant alone.
222+
</P>
223+
<pre>
224+
{@code
225+
// Using a java-cloudant-cache.
226+
227+
// Create a new Foo with an _id, but no revision.
228+
Foo f = new Foo();
229+
// Save the foo in the Database.
230+
Response response = cachedDb.save(f);
231+
232+
// This example assumes no further changes have been made to the database document.
233+
// Sometime later get the Foo from the cached database.
234+
Foo f2 = cachedDb.find(response.getId()); // Returns the cached object.
235+
236+
f2.getRev().equals(response.getRev()); // False, f2 has no revision information
237+
f2.equals(f); // True, neither f or f2 have a revision set (note f == f2 is also True)
238+
239+
// Set revision on f
240+
f.setRev(response.getRev()); // Also updates the instance of f in the cache (and hence f2)
241+
f2.equals(f); // True, as before because f == f2 and both objects now have revision information.
242+
}
243+
</pre>
244+
<P>
245+
This behaviour difference is well defined, but can be confusing, particularly if using a cache
246+
with lifetimes that may purge expired objects from the cache. Consider this scenario.
247+
</P>
248+
<pre>
249+
{@code
250+
// Using a java-cloudant-cache with lifetimes.
251+
252+
// Create a new Foo with an _id, but no revision.
253+
Foo f = new Foo();
254+
// Save the foo in the Database.
255+
Response response = lifetimeCachedDb.save(f);
256+
257+
// This example assumes no further changes have been made to the database document.
258+
// Sometime later get the Foo from the cached database.
259+
Foo f2 = lifetimeCachedDb.find(response.getId()); // Returns the cached object.
260+
261+
f2.getRev().equals(response.getRev()); // False, f2 has no revision information
262+
f2.equals(f); // True, neither f or f2 have a revision set (note f == f2 is also True)
263+
264+
// Wait a while longer, until object expires in cache
265+
Foo f3 = lifetimeCachedDb.find(response.getId());
266+
// Expired so cache miss and object f3 is now deserialized from remote document
267+
f3.getRev().equals(response.getRev()); // True
268+
f3.equals(f); // False, f has no revision set
269+
270+
// Note that the object returned from the same method call
271+
// lifetimeCachedDb.find(response.getId()) has different behaviour at different times because of
272+
// cache expiry.
273+
}
274+
</pre>
275+
<P>
276+
Setting the revision information after a write can to some extent provide more consistent
277+
behaviour.
278+
</P>
279+
<pre>
280+
{@code
281+
// Using a java-cloudant-cache with lifetimes.
282+
283+
// Create a new Foo with an _id, but no revision.
284+
Foo f = new Foo();
285+
// Save the foo in the Database.
286+
Response response = lifetimeCachedDb.save(f);
287+
// Set the revision information
288+
f.setRev(response.getRev());
289+
290+
// This example assumes no further changes have been made to the database document.
291+
// Sometime later get the Foo from the cached database.
292+
Foo f2 = lifetimeCachedDb.find(response.getId()); // Returns the cached object.
293+
294+
f2.getRev().equals(response.getRev()); // True
295+
f2.equals(f); // True, f == f2
43296

297+
// Wait a while longer, until object expires in cache
298+
Foo f3 = lifetimeCachedDb.find(response.getId());
299+
// Expired so cache miss and object f3 is now deserialized from remote document
300+
f3.getRev().equals(response.getRev()); // True
301+
f3.equals(f); // True, f and f3 have the same revision information
302+
}
303+
</pre>
44304
<P>
45-
See the <a href="http://www.javadoc.io/doc/com.cloudant/cloudant-client/">java-cloudant client
46-
javadoc</a> for more information about the client.
305+
However, all of these examples have considered only cases where the server side document has not
306+
changed. Depending on the application scenario documents on the server side might be updated
307+
from other sources. Since providing the latest revision information is a requirement for a
308+
successful update or delete operation some care must be taken. Generally an application should
309+
have appropriate mechanisms in place to handle the potential
310+
{@link com.cloudant.client.org.lightcouch.DocumentConflictException}s or to inspect the
311+
{@code error} field of each {@link com.cloudant.client.api.model.Response} for bulk operations.
47312
</P>
48313
</body>
49314
</html>

0 commit comments

Comments
 (0)