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 }