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 }