View Javadoc

1   package com.atlassian.vcache;
2   
3   import java.time.Duration;
4   import java.util.Arrays;
5   import java.util.Collection;
6   import java.util.Map;
7   import java.util.Optional;
8   import java.util.concurrent.CompletableFuture;
9   import java.util.function.Function;
10  import java.util.function.Supplier;
11  import javax.annotation.Nonnull;
12  
13  import com.atlassian.annotations.PublicApi;
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 {@link Marshaller}.
18   *
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   *
24   * <h1>Failures</h1>
25   * <p>
26   *     Failures are expected to happen when communicating with the external system, and the caller is expected to
27   *     deal with them. As such, all operations that involve communicating with the external system will return an
28   *     {@link CompletableFuture}, which encapsulates that failure may have occurred.
29   * </p>
30   *
31   * <h1>Buffering</h1>
32   * <p>
33   *     Buffering of communications to the external cache system may be applied to both read and write operations.
34   *     The following three specialisations of the {@link ExternalCache} are provided to represent the possible
35   *     buffering modes:
36   * </p>
37   * <ul>
38   *     <li>{@link DirectExternalCache} - buffering is not applied for either read or write operations.</li>
39   *     <li>{@link TransactionalExternalCache} - buffering is applied for both read and write operations.</li>
40   *     <li>{@link StableReadExternalCache} - buffering is applied for only read operations</li>
41   * </ul>
42   *
43   * <p>
44   *     Buffering of read operations means that the result of every read operation
45   *     is stored in a private {@link RequestCache}. All read operations will first query the private
46   *     {@link RequestCache} to determine whether the operation has been performed before, and if it has, return the
47   *     <i>buffered</i> result.
48   * </p>
49   *
50   * <p>
51   *     Buffering of write operations means that all write operations are stored
52   *     in a private {@link RequestCache}, and at the end of the transaction they are applied to the
53   *     external cache system.
54   * </p>
55   *
56   * <p>
57   *     When buffering is used for write operations, the issue arises as to how to handle when there
58   *     are conflicts when replaying the buffered operations. The guiding principles are:
59   * </p>
60   * <ul>
61   *     <li>
62   *         The implementation shall endeavour to detect when the external system state has changed since it
63   *         was read and deal with it appropriately.
64   *     </li>
65   *     <li>
66   *         If the implementation cannot determine a reliable and consistent way to deal with a conflict,
67   *         it shall invalidate the entries affected, or the entire cache.
68   *     </li>
69   *     <li>
70   *         Rather than attempt to return errors (the implementation should log them) to the application code at
71   *         the end of a request, invalidation is used to recover.
72   *     </li>
73   * </ul>
74   *
75   * <p>
76   *     All read operations, even if buffered, may involve failures. As such, all read operations are encapsulated in
77   *     this interface. However, buffered write operations will not expose failures as they are replayed later. Hence,
78   *     the write operations are provided through the following two interfaces which are used by the
79   *     four specialisations of this interface:
80   * </p>
81   * <ul>
82   *     <li>{@link ExternalWriteOperationsBuffered} - encapsulates the buffered versions of the write operations.</li>
83   *     <li>{@link ExternalWriteOperationsUnbuffered} - encapsulates the unbuffered versions of the write operations.</li>
84   * </ul>
85   *
86   * <h1>Supplier/Factory Generation</h1>
87   * <p>
88   *     The operations that may create values using a supplied {@link Supplier} or {@link Function}
89   *     make no guarantees about their atomicity. There is a possible race condition between detecting
90   *     that a value is missing in the external cache system, and the generated value being stored
91   *     in the external cache system. The implementations are free to override any (new) value that
92   *     may have been stored in the external cache system. If this behaviour is unacceptable, then
93   *     the caller is responsible for using a strategy to deal with this (such as global locking).
94   * </p>
95   *
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 are 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  *         Do not assume that the associated ttl defines exactly when entries are evicted. The implementation is free
105  *         to choose how the ttl is interpreted. It may expire entries in a timely manner, or it may be delayed until
106  *         next interaction with the cache.
107  *     </li>
108  * </ul>
109  *
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 Marshaller} 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  *
131  * <h1>Hints</h1>
132  * <p>
133  *     Each cache is configured with the following hints to allow the implementation the opportunity to
134  *     optimise performance.
135  * </p>
136  * <ul>
137  *     <li>
138  *         {@link ExternalCacheSettings#getDataChangeRateHint()} - this is a hint on the expected rate of change to the
139  *         data held in the cache, including updating existing entries. A cache that holds values that are never
140  *         updated after being populated should use {@link ChangeRate#NONE}. A cache that holds data that changed
141  *         infrequently, like a cache of project details, should use {@link ChangeRate#LOW_CHANGE}.
142  *     </li>
143  *     <li>
144  *         {@link ExternalCacheSettings#getEntryGrowthRateHint()} - this is a hint on the expected rate of change in
145  *         the number of entries held in the cache. A cache that holds a fixed number of entries should
146  *         use {@link ChangeRate#NONE}.
147  *     </li>
148  *     <li>
149  *         {@link ExternalCacheSettings#getEntryCountHint()} - this is a hint on the desired number of entries in
150  *         the cache. The implementation is free to use this as it sees fit.
151  *     </li>
152  * </ul>
153  *
154  * @param <V> the value type
155  *
156  * @since 1.0
157  */
158 @PublicApi
159 public interface ExternalCache<V> extends VCache
160 {
161     /**
162      * Returns a value that is associated with a specified key.
163      *
164      * @param key the key to check.
165      * @return an {@link Optional} which may contain the value associated with the key.
166      */
167     @Nonnull
168     CompletableFuture<Optional<V>> get(String key);
169 
170     /**
171      * Returns a value that may be associated with a specified key.
172      * <p>Notes:</p>
173      * <ul>
174      *     <li>
175      *         If no entry for the specified key exists, then the specified <tt>supplier</tt> is called to create
176      *         an entry in the cache, before it is returned.
177      *     </li>
178      *     <li>
179      *         The <tt>supplier</tt> implementation needs to be multi-thread safe as it may be called concurrently
180      *         if multiple threads call this method.
181      *     </li>
182      * </ul>
183      *
184      * @param key the key uniquely identifying the value to be retrieved
185      * @param supplier used to generate the value, if one does not exist already for the key. The supplier may not
186      *                 return {@code null}.
187      * @return the value associated with the key.
188      */
189     @Nonnull
190     CompletableFuture<V> get(String key, Supplier<V> supplier);
191 
192     /**
193      * Returns the values that are associated with the specified keys. It is equivalent to calling
194      * {@link #getBulk(Iterable)} passing {@code Arrays.asList(keys)} as the parameter.
195      *
196      * @param keys the keys to check.
197      * @return A {@link Map} that is keyed on the {@code keys} specified. Each entry in the {@link Map} will have
198      * {@link Optional} which may contain the value associated with the key.
199      */
200     @SuppressWarnings("unchecked")
201     @Nonnull
202     default CompletableFuture<Map<String, Optional<V>>> getBulk(String... keys)
203     {
204         return getBulk(Arrays.asList(keys));
205     }
206 
207     /**
208      * Returns the values that are associated with the specified keys.
209      *
210      * @param keys the keys to check.
211      * @return A {@link Map} that is keyed on the {@code keys} specified. Each entry in the {@link Map} will have
212      *         {@link Optional} which may contain the value associated with the key.
213      */
214     @Nonnull
215     CompletableFuture<Map<String, Optional<V>>> getBulk(Iterable<String> keys);
216 
217     /**
218      * Returns the values that are associated with the specified keys. It is equivalent to calling
219      * {@link #getBulk(Function, Iterable)} passing {@code factory} and {@code Arrays.asList(keys)} as the parameters.
220      * <p>Notes:</p>
221      * <ul>
222      *   <li>
223      *       If no entry exists for a key (or keys), then the specified <tt>factory</tt> is called once
224      *       using the current thread passing in the missing keys to create the missing entries in the cache, before it is returned.
225      *   </li>
226      * </ul>
227      *
228      * @param factory used to generate the values for the keys that do not have entries. The factory must return a
229      *                map containing a non-null entry for each supplied key.
230      * @param keys     the keys to retrieve
231      * @return A {@link Map} that is keyed on the {@code keys} specified. Each entry in the {@link Map} will have
232      * the value associated with the key.
233      */
234     @SuppressWarnings("unchecked")
235     @Nonnull
236     default CompletableFuture<Map<String, V>> getBulk(Function<Collection<String>, Map<String, V>> factory, String... keys)
237     {
238         return getBulk(factory, Arrays.asList(keys));
239     }
240 
241     /**
242      * Returns the values that are associated with the specified keys.
243      * <p>Notes:</p>
244      * <ul>
245      *   <li>
246      *       If no entry exists for a key (or keys), then the specified <tt>factory</tt> is called once
247      *       using the current thread passing in the missing keys to create the missing entries in the cache, before it is returned.
248      *   </li>
249      * </ul>
250      *
251      * @param factory used to generate the values for the keys that do not have entries. The factory must return a
252      *                map containing a non-null entry for each supplied key.
253      * @param keys     the keys to retrieve
254      * @return A {@link Map} that is keyed on the {@code keys} specified. Each entry in the {@link Map} will have
255      * the value associated with the key.
256      */
257     @Nonnull
258     CompletableFuture<Map<String, V>> getBulk(Function<Collection<String>, Map<String, V>> factory, Iterable<String> keys);
259 }