Skip to content

Commit 388c4fd

Browse files
authored
Merge pull request #109 from graphql-java/improve_memory_usage_of_no_op_value_cache
Short circuit value cache look up to avoid object allocations
2 parents dd96e13 + 66f1dbe commit 388c4fd

File tree

3 files changed

+76
-18
lines changed

3 files changed

+76
-18
lines changed

src/main/java/org/dataloader/DataLoaderHelper.java

+30-10
Original file line numberDiff line numberDiff line change
@@ -331,22 +331,33 @@ CompletableFuture<List<V>> invokeLoader(List<K> keys, List<Object> keyContexts,
331331
CompletableFuture<List<Try<V>>> cacheCallCF = getFromValueCache(keys);
332332
return cacheCallCF.thenCompose(cachedValues -> {
333333

334-
assertState(keys.size() == cachedValues.size(), () -> "The size of the cached values MUST be the same size as the key list");
335-
336334
// the following is NOT a Map because keys in data loader can repeat (by design)
337335
// and hence "a","b","c","b" is a valid set of keys
338336
List<Try<V>> valuesInKeyOrder = new ArrayList<>();
339337
List<Integer> missedKeyIndexes = new ArrayList<>();
340338
List<K> missedKeys = new ArrayList<>();
341339
List<Object> missedKeyContexts = new ArrayList<>();
342-
for (int i = 0; i < keys.size(); i++) {
343-
Try<V> cacheGet = cachedValues.get(i);
344-
valuesInKeyOrder.add(cacheGet);
345-
if (cacheGet.isFailure()) {
340+
341+
// if they return a ValueCachingNotSupported exception then we insert this special marker value, and it
342+
// means it's a total miss, we need to get all these keys via the batch loader
343+
if (cachedValues == NOT_SUPPORTED_LIST) {
344+
for (int i = 0; i < keys.size(); i++) {
345+
valuesInKeyOrder.add(ALWAYS_FAILED);
346346
missedKeyIndexes.add(i);
347347
missedKeys.add(keys.get(i));
348348
missedKeyContexts.add(keyContexts.get(i));
349349
}
350+
} else {
351+
assertState(keys.size() == cachedValues.size(), () -> "The size of the cached values MUST be the same size as the key list");
352+
for (int i = 0; i < keys.size(); i++) {
353+
Try<V> cacheGet = cachedValues.get(i);
354+
valuesInKeyOrder.add(cacheGet);
355+
if (cacheGet.isFailure()) {
356+
missedKeyIndexes.add(i);
357+
missedKeys.add(keys.get(i));
358+
missedKeyContexts.add(keyContexts.get(i));
359+
}
360+
}
350361
}
351362
if (missedKeys.isEmpty()) {
352363
//
@@ -442,9 +453,16 @@ int dispatchDepth() {
442453
}
443454
}
444455

456+
private final List<Try<V>> NOT_SUPPORTED_LIST = emptyList();
457+
private final CompletableFuture<List<Try<V>>> NOT_SUPPORTED = CompletableFuture.completedFuture(NOT_SUPPORTED_LIST);
458+
private final Try<V> ALWAYS_FAILED = Try.alwaysFailed();
459+
445460
private CompletableFuture<List<Try<V>>> getFromValueCache(List<K> keys) {
446461
try {
447462
return nonNull(valueCache.getValues(keys), () -> "Your ValueCache.getValues function MUST return a non null CompletableFuture");
463+
} catch (ValueCache.ValueCachingNotSupported ignored) {
464+
// use of a final field prevents CF object allocation for this special purpose
465+
return NOT_SUPPORTED;
448466
} catch (RuntimeException e) {
449467
return CompletableFutureKit.failedFuture(e);
450468
}
@@ -456,16 +474,18 @@ private CompletableFuture<List<V>> setToValueCache(List<V> assembledValues, List
456474
if (completeValueAfterCacheSet) {
457475
return nonNull(valueCache
458476
.setValues(missedKeys, missedValues), () -> "Your ValueCache.setValues function MUST return a non null CompletableFuture")
459-
// we dont trust the set cache to give us the values back - we have them - lets use them
460-
// if the cache set fails - then they wont be in cache and maybe next time they will
477+
// we don't trust the set cache to give us the values back - we have them - lets use them
478+
// if the cache set fails - then they won't be in cache and maybe next time they will
461479
.handle((ignored, setExIgnored) -> assembledValues);
462480
} else {
463481
// no one is waiting for the set to happen here so if its truly async
464-
// it will happen eventually but no result will be dependant on it
482+
// it will happen eventually but no result will be dependent on it
465483
valueCache.setValues(missedKeys, missedValues);
466484
}
485+
} catch (ValueCache.ValueCachingNotSupported ignored) {
486+
// ok no set caching is fine if they say so
467487
} catch (RuntimeException ignored) {
468-
// if we cant set values back into the cache - so be it - this must be a faulty
488+
// if we can't set values back into the cache - so be it - this must be a faulty
469489
// ValueCache implementation
470490
}
471491
return CompletableFuture.completedFuture(assembledValues);

src/main/java/org/dataloader/ValueCache.java

+21-3
Original file line numberDiff line numberDiff line change
@@ -74,13 +74,18 @@ static <K, V> ValueCache<K, V> defaultValueCache() {
7474
* a successful Try contain the cached value is returned.
7575
* <p>
7676
* You MUST return a List that is the same size as the keys passed in. The code will assert if you do not.
77+
* <p>
78+
* If your cache does not have anything in it at all, and you want to quickly short-circuit this method and avoid any object allocation
79+
* then throw {@link ValueCachingNotSupported} and the code will know there is nothing in cache at this time.
7780
*
7881
* @param keys the list of keys to get cached values for.
7982
*
8083
* @return a future containing a list of {@link Try} cached values for each key passed in.
84+
*
85+
* @throws ValueCachingNotSupported if this cache wants to short-circuit this method completely
8186
*/
82-
default CompletableFuture<List<Try<V>>> getValues(List<K> keys) {
83-
List<CompletableFuture<Try<V>>> cacheLookups = new ArrayList<>();
87+
default CompletableFuture<List<Try<V>>> getValues(List<K> keys) throws ValueCachingNotSupported {
88+
List<CompletableFuture<Try<V>>> cacheLookups = new ArrayList<>(keys.size());
8489
for (K key : keys) {
8590
CompletableFuture<Try<V>> cacheTry = Try.tryFuture(get(key));
8691
cacheLookups.add(cacheTry);
@@ -106,8 +111,10 @@ default CompletableFuture<List<Try<V>>> getValues(List<K> keys) {
106111
* @param values the values to store
107112
*
108113
* @return a future containing the stored values for fluent composition
114+
*
115+
* @throws ValueCachingNotSupported if this cache wants to short-circuit this method completely
109116
*/
110-
default CompletableFuture<List<V>> setValues(List<K> keys, List<V> values) {
117+
default CompletableFuture<List<V>> setValues(List<K> keys, List<V> values) throws ValueCachingNotSupported {
111118
List<CompletableFuture<V>> cacheSets = new ArrayList<>();
112119
for (int i = 0; i < keys.size(); i++) {
113120
K k = keys.get(i);
@@ -140,4 +147,15 @@ default CompletableFuture<List<V>> setValues(List<K> keys, List<V> values) {
140147
* @return a void future for error handling and fluent composition
141148
*/
142149
CompletableFuture<Void> clear();
150+
151+
152+
/**
153+
* This special exception can be used to short-circuit a caching method
154+
*/
155+
class ValueCachingNotSupported extends UnsupportedOperationException {
156+
@Override
157+
public Throwable fillInStackTrace() {
158+
return this;
159+
}
160+
}
143161
}

src/main/java/org/dataloader/impl/NoOpValueCache.java

+25-5
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package org.dataloader.impl;
22

33

4+
import org.dataloader.Try;
45
import org.dataloader.ValueCache;
56
import org.dataloader.annotations.Internal;
67

8+
import java.util.List;
79
import java.util.concurrent.CompletableFuture;
810

911
/**
@@ -20,37 +22,55 @@
2022
@Internal
2123
public class NoOpValueCache<K, V> implements ValueCache<K, V> {
2224

23-
public static NoOpValueCache<?, ?> NOOP = new NoOpValueCache<>();
25+
/**
26+
* a no op value cache instance
27+
*/
28+
public static final NoOpValueCache<?, ?> NOOP = new NoOpValueCache<>();
29+
30+
// avoid object allocation by using a final field
31+
private final ValueCachingNotSupported NOT_SUPPORTED = new ValueCachingNotSupported();
32+
private final CompletableFuture<V> NOT_SUPPORTED_CF = CompletableFutureKit.failedFuture(NOT_SUPPORTED);
33+
private final CompletableFuture<Void> NOT_SUPPORTED_VOID_CF = CompletableFuture.completedFuture(null);
2434

2535
/**
2636
* {@inheritDoc}
2737
*/
2838
@Override
2939
public CompletableFuture<V> get(K key) {
30-
return CompletableFutureKit.failedFuture(new UnsupportedOperationException());
40+
return NOT_SUPPORTED_CF;
41+
}
42+
43+
@Override
44+
public CompletableFuture<List<Try<V>>> getValues(List<K> keys) throws ValueCachingNotSupported {
45+
throw NOT_SUPPORTED;
3146
}
3247

3348
/**
3449
* {@inheritDoc}
3550
*/
3651
@Override
3752
public CompletableFuture<V> set(K key, V value) {
38-
return CompletableFuture.completedFuture(value);
53+
return NOT_SUPPORTED_CF;
54+
}
55+
56+
@Override
57+
public CompletableFuture<List<V>> setValues(List<K> keys, List<V> values) throws ValueCachingNotSupported {
58+
throw NOT_SUPPORTED;
3959
}
4060

4161
/**
4262
* {@inheritDoc}
4363
*/
4464
@Override
4565
public CompletableFuture<Void> delete(K key) {
46-
return CompletableFuture.completedFuture(null);
66+
return NOT_SUPPORTED_VOID_CF;
4767
}
4868

4969
/**
5070
* {@inheritDoc}
5171
*/
5272
@Override
5373
public CompletableFuture<Void> clear() {
54-
return CompletableFuture.completedFuture(null);
74+
return NOT_SUPPORTED_VOID_CF;
5575
}
5676
}

0 commit comments

Comments
 (0)