View Javadoc

1   package com.atlassian.vcache;
2   
3   import com.atlassian.annotations.PublicApi;
4   
5   import java.time.Duration;
6   import java.util.Arrays;
7   import java.util.Map;
8   import java.util.Optional;
9   import java.util.Set;
10  import java.util.concurrent.CompletionStage;
11  import java.util.function.BiFunction;
12  import java.util.function.Function;
13  import java.util.function.Supplier;
14  
15  /**
16   * Represents an external cache, where the data is ultimately stored in external cache systems
17   * like Memcached or Redis. The values are stored by value and are converted using a
18   * {@link com.atlassian.marshalling.api.MarshallingPair}.
19   * <p>
20   * The settings provided when creating a {@link ExternalCache} are only used within the JVM and are not
21   * centrally stored across JVMs.
22   * </p>
23   * <h1>Failures</h1>
24   * <p>
25   * Failures are expected to happen when communicating with the external system, and the caller is expected to
26   * deal with them. As such, all operations that involve communicating with the external system will return an
27   * {@link CompletionStage}, which encapsulates that failure may have occurred.
28   * </p>
29   * <h1>WorkContext Lifecycle</h1>
30   * <p>
31   * Any returned {@link CompletionStage} is guaranteed to complete by the end of the Atlassian WorkContext it was
32   * created within. There is no need to explicitly "join" on a {@link CompletionStage},
33   * using {@link VCacheUtils#fold(CompletionStage, Function, Function)} or similar. This means that any "connected"
34   * {@link CompletionStage}, via {@link CompletionStage#thenCombine(CompletionStage, BiFunction)} etc, will be
35   * performed.
36   * </p>
37   * <h1>Buffering</h1>
38   * <p>
39   * Buffering of communications to the external cache system may be applied to both read and write operations.
40   * The following three specialisations of the {@link ExternalCache} are provided to represent the possible
41   * buffering modes:
42   * </p>
43   * <ul>
44   * <li>{@link DirectExternalCache} - buffering is not applied for either read or write operations.</li>
45   * <li>{@link TransactionalExternalCache} - buffering is applied for both read and write operations.</li>
46   * <li>{@link StableReadExternalCache} - buffering is applied for only read operations</li>
47   * </ul>
48   * <p>
49   * Buffering of read operations means that the result of every read operation
50   * is stored in a private {@link RequestCache}. All read operations will first query the private
51   * {@link RequestCache} to determine whether the operation has been performed before, and if it has, return the
52   * <i>buffered</i> result.
53   * </p>
54   * <p>
55   * Buffering of write operations means that all write operations are stored
56   * in a private {@link RequestCache}, and at the end of the transaction they are applied to the
57   * external cache system.
58   * </p>
59   * <p>
60   * When buffering is used for write operations, the issue arises as to how to handle when there
61   * are conflicts when replaying the buffered operations. The guiding principles are:
62   * </p>
63   * <ul>
64   * <li>
65   * The implementation shall endeavour to detect when the external system state has changed since it
66   * was read and deal with it appropriately.
67   * </li>
68   * <li>
69   * If the implementation cannot determine a reliable and consistent way to deal with a conflict,
70   * it shall invalidate the entries affected, or the entire cache.
71   * </li>
72   * <li>
73   * Rather than attempt to return errors (the implementation should log them) to the application code at
74   * the end of a request, invalidation is used to recover.
75   * </li>
76   * </ul>
77   * <p>
78   * All read operations, even if buffered, may involve failures. As such, all read operations are encapsulated in
79   * this interface. However, buffered write operations will not expose failures as they are replayed later. Hence,
80   * the write operations are provided through the following two interfaces which are used by the
81   * four specialisations of this interface:
82   * </p>
83   * <ul>
84   * <li>{@link ExternalWriteOperationsBuffered} - encapsulates the buffered versions of the write operations.</li>
85   * <li>{@link ExternalWriteOperationsUnbuffered} - encapsulates the unbuffered versions of the write operations.</li>
86   * </ul>
87   * <h1>Supplier/Factory Generation</h1>
88   * <p>
89   * The operations that may create values using a supplied {@link Supplier} or {@link Function}
90   * make no guarantees about their atomicity. There is a possible race condition between detecting
91   * that a value is missing in the external cache system, and the generated value being stored
92   * in the external cache system. The implementations are free to override any (new) value that
93   * may have been stored in the external cache system. If this behaviour is unacceptable, then
94   * the caller is responsible for using a strategy to deal with this (such as global locking).
95   * </p>
96   * <h1>Time-to-live</h1>
97   * <ul>
98   * <li>
99   * A cache has an associated time-to-live (<tt>ttl</tt>), which is expressed as {@link Duration}.
100  * The implementations may round-up the supplied ttl to the nearest second. E.g. 500 milliseconds may be
101  * converted to 1 second.
102  * </li>
103  * <li>
104  * Implementations should implement ttl based on the time the entry was added or updated. However, do not assume that
105  * the associated ttl defines exactly when entries are evicted. The implementations are free
106  * to choose how the ttl is interpreted. It may expire entries in a timely manner, or it may be delayed until
107  * next interaction with the cache.
108  * </li>
109  * </ul>
110  * <h1>Data Considerations</h1>
111  * <p>
112  * As the data is stored externally to the JVM, the following needs to be considered:
113  * </p>
114  * <ul>
115  * <li>
116  * The data in the external system will persist longer than the JVM. Hence newer versions of the application
117  * will need to handle data that could have been written by an earlier version of the application. If the data
118  * format used is not compatible between the different versions of the application, problems will arise.
119  * It is required that the application deal with potential versioning issues. One approach would be to
120  * to use <a href="https://docs.oracle.com/javase/8/docs/platform/serialization/spec/version.html">
121  * Versioning of Serializable Objects</a>.
122  * </li>
123  * <li>
124  * Data is stored in the external system as byte arrays. The cache must be associated with
125  * a {@link com.atlassian.marshalling.api.MarshallingPair} instance for marshalling values.
126  * By extension, this means that an {@link ExternalCache} can only be used for data that can be marshalled
127  * to/from byte arrays.
128  * </li>
129  * </ul>
130  * <h1>Hints</h1>
131  * <p>
132  * Each cache is configured with the following hints to allow the implementation the opportunity to
133  * optimise performance.
134  * </p>
135  * <ul>
136  * <li>
137  * {@link ExternalCacheSettings#getDataChangeRateHint()} - this is a hint on the expected rate of change to the
138  * data held in the cache, including updating existing entries. A cache that holds values that are never
139  * updated after being populated should use {@link ChangeRate#NONE}. A cache that holds data that changed
140  * infrequently, like a cache of project details, should use {@link ChangeRate#LOW_CHANGE}.
141  * </li>
142  * <li>
143  * {@link ExternalCacheSettings#getEntryGrowthRateHint()} - this is a hint on the expected rate of change in
144  * the number of entries held in the cache. A cache that holds a fixed number of entries should
145  * use {@link ChangeRate#NONE}.
146  * </li>
147  * <li>
148  * {@link ExternalCacheSettings#getEntryCountHint()} - this is a hint on the desired number of entries in
149  * the cache. The implementation is free to use this as it sees fit.
150  * </li>
151  * </ul>
152  *
153  * @param <V> the value type
154  * @since 1.0
155  */
156 @PublicApi
157 public interface ExternalCache<V> extends VCache {
158     /**
159      * Returns a value that is associated with a specified key.
160      *
161      * @param key the key to check.
162      * @return an {@link Optional} which may contain the value associated with the key.
163      */
164     CompletionStage<Optional<V>> get(String key);
165 
166     /**
167      * Returns a value that may be associated with a specified key.
168      * <p>Notes:</p>
169      * <ul>
170      * <li>
171      * If no entry for the specified key exists, then the specified <tt>supplier</tt> is called. The value may be used
172      * to create an entry in the cache. However, the value returned may be that of the supplier to a concurrent
173      * call to this method, or that of the factory in a concurrent call to {@link #getBulk}.
174      * When the supplier is called, callers may not assume that the value returned from the supplier is returned
175      * by this method.
176      * </li>
177      * <li>
178      * The <tt>supplier</tt> implementation needs to be multi-thread safe as it may be called concurrently
179      * if multiple threads call this method.
180      * </li>
181      * </ul>
182      *
183      * @param key      the key uniquely identifying the value to be retrieved
184      * @param supplier used to generate the value, if one does not exist already for the key. The supplier may not
185      *                 return {@code null}.
186      * @return the value associated with the key.
187      */
188     CompletionStage<V> get(String key, Supplier<V> supplier);
189 
190     /**
191      * Returns the values that are associated with the specified keys. It is equivalent to calling
192      * {@link #getBulk(Iterable)} passing {@code Arrays.asList(keys)} as the parameter.
193      *
194      * @param keys the keys to check.
195      * @return A {@link Map} that is keyed on the {@code keys} specified. Each entry in the {@link Map} will have
196      * {@link Optional} which may contain the value associated with the key.
197      */
198     @SuppressWarnings("unchecked")
199     default CompletionStage<Map<String, Optional<V>>> getBulk(String... keys) {
200         return getBulk(Arrays.asList(keys));
201     }
202 
203     /**
204      * Returns the values that are associated with the specified keys.
205      *
206      * @param keys the keys to check.
207      * @return A {@link Map} that is keyed on the {@code keys} specified. Each entry in the {@link Map} will have
208      * {@link Optional} which may contain the value associated with the key.
209      */
210     CompletionStage<Map<String, Optional<V>>> getBulk(Iterable<String> keys);
211 
212     /**
213      * Returns the values that are associated with the specified keys. It is equivalent to calling
214      * {@link #getBulk(Function, Iterable)} passing {@code factory} and {@code Arrays.asList(keys)} as the parameters.
215      * <p>Notes:</p>
216      * <ul>
217      * <li>
218      * If no entry for a specified key (or keys) exists, then the specified <tt>factory</tt> is called. The values may
219      * be used to create entries in the cache. However, the values returned may be that of the <tt>factory</tt>
220      * to a concurrent call to this method, or that of the <tt>supplier</tt> in a concurrent call to {@link #get(String, Supplier)}.
221      * When the <tt>factory</tt> is called, callers may not assume that the values returned from the <tt>factory</tt>
222      * is returned by this method.
223      * </li>
224      * </ul>
225      *
226      * @param factory used to generate the values for the keys that do not have entries. The factory must return a
227      *                map containing a non-null entry for each supplied key.
228      * @param keys    the keys to retrieve
229      * @return A {@link Map} that is keyed on the {@code keys} specified. Each entry in the {@link Map} will have
230      * the value associated with the key.
231      */
232     @SuppressWarnings("unchecked")
233     default CompletionStage<Map<String, V>> getBulk(Function<Set<String>, Map<String, V>> factory, String... keys) {
234         return getBulk(factory, Arrays.asList(keys));
235     }
236 
237     /**
238      * Returns the values that are associated with the specified keys.
239      * <p>Notes:</p>
240      * <ul>
241      * <li>
242      * If no entry for a specified key (or keys) exists, then the specified <tt>factory</tt> is called. The values may
243      * be used to create entries in the cache. However, the values returned may be that of the <tt>factory</tt>
244      * to a concurrent call to this method, or that of the <tt>supplier</tt> in a concurrent call to {@link #get(String, Supplier)}.
245      * When the <tt>factory</tt> is called, callers may not assume that the values returned from the <tt>factory</tt>
246      * is returned by this method.
247      * </li>
248      * </ul>
249      *
250      * @param factory used to generate the values for the keys that do not have entries. The factory must return a
251      *                map containing a non-null entry for each supplied key.
252      * @param keys    the keys to retrieve
253      * @return A {@link Map} that is keyed on the {@code keys} specified. Each entry in the {@link Map} will have
254      * the value associated with the key.
255      */
256     CompletionStage<Map<String, V>> getBulk(Function<Set<String>, Map<String, V>> factory, Iterable<String> keys);
257 }