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 to create
172      * an entry in the cache, before it is returned.
173      * </li>
174      * <li>
175      * The <tt>supplier</tt> implementation needs to be multi-thread safe as it may be called concurrently
176      * if multiple threads call this method.
177      * </li>
178      * </ul>
179      *
180      * @param key      the key uniquely identifying the value to be retrieved
181      * @param supplier used to generate the value, if one does not exist already for the key. The supplier may not
182      *                 return {@code null}.
183      * @return the value associated with the key.
184      */
185     CompletionStage<V> get(String key, Supplier<V> supplier);
186 
187     /**
188      * Returns the values that are associated with the specified keys. It is equivalent to calling
189      * {@link #getBulk(Iterable)} passing {@code Arrays.asList(keys)} as the parameter.
190      *
191      * @param keys the keys to check.
192      * @return A {@link Map} that is keyed on the {@code keys} specified. Each entry in the {@link Map} will have
193      * {@link Optional} which may contain the value associated with the key.
194      */
195     @SuppressWarnings("unchecked")
196     default CompletionStage<Map<String, Optional<V>>> getBulk(String... keys) {
197         return getBulk(Arrays.asList(keys));
198     }
199 
200     /**
201      * Returns the values that are associated with the specified keys.
202      *
203      * @param keys the keys to check.
204      * @return A {@link Map} that is keyed on the {@code keys} specified. Each entry in the {@link Map} will have
205      * {@link Optional} which may contain the value associated with the key.
206      */
207     CompletionStage<Map<String, Optional<V>>> getBulk(Iterable<String> keys);
208 
209     /**
210      * Returns the values that are associated with the specified keys. It is equivalent to calling
211      * {@link #getBulk(Function, Iterable)} passing {@code factory} and {@code Arrays.asList(keys)} as the parameters.
212      * <p>Notes:</p>
213      * <ul>
214      * <li>
215      * If no entry exists for a key (or keys), then the specified <tt>factory</tt> is called once
216      * using the current thread passing in the missing keys to create the missing entries in the cache, before it is returned.
217      * </li>
218      * </ul>
219      *
220      * @param factory used to generate the values for the keys that do not have entries. The factory must return a
221      *                map containing a non-null entry for each supplied key.
222      * @param keys    the keys to retrieve
223      * @return A {@link Map} that is keyed on the {@code keys} specified. Each entry in the {@link Map} will have
224      * the value associated with the key.
225      */
226     @SuppressWarnings("unchecked")
227     default CompletionStage<Map<String, V>> getBulk(Function<Set<String>, Map<String, V>> factory, String... keys) {
228         return getBulk(factory, Arrays.asList(keys));
229     }
230 
231     /**
232      * Returns the values that are associated with the specified keys.
233      * <p>Notes:</p>
234      * <ul>
235      * <li>
236      * If no entry exists for a key (or keys), then the specified <tt>factory</tt> is called once
237      * using the current thread passing in the missing keys to create the missing entries in the cache, before it is returned.
238      * </li>
239      * </ul>
240      *
241      * @param factory used to generate the values for the keys that do not have entries. The factory must return a
242      *                map containing a non-null entry for each supplied key.
243      * @param keys    the keys to retrieve
244      * @return A {@link Map} that is keyed on the {@code keys} specified. Each entry in the {@link Map} will have
245      * the value associated with the key.
246      */
247     CompletionStage<Map<String, V>> getBulk(Function<Set<String>, Map<String, V>> factory, Iterable<String> keys);
248 }