Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Experimental ability to cache References #8111

Merged
merged 3 commits into from
May 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
import io.smallrye.config.WithConverter;
import io.smallrye.config.WithDefault;
import io.smallrye.config.WithName;
import java.time.Duration;
import java.util.Optional;
import java.util.OptionalDouble;
import java.util.OptionalInt;
import org.projectnessie.versioned.storage.common.config.StoreConfig;
Expand Down Expand Up @@ -136,4 +138,12 @@ public interface QuarkusStoreConfig extends StoreConfig {
*/
@WithName(CONFIG_CACHE_CAPACITY_FRACTION_ADJUST_MB)
OptionalInt cacheCapacityFractionAdjustMB();

@WithName(CONFIG_REFERENCE_CACHE_TTL)
@Override
Optional<Duration> referenceCacheTtl();

@WithName(CONFIG_REFERENCE_NEGATIVE_CACHE_TTL)
@Override
Optional<Duration> referenceCacheNegativeTtl();
}
Original file line number Diff line number Diff line change
Expand Up @@ -146,12 +146,21 @@ public Persist producePersist(MeterRegistry meterRegistry) {

String cacheInfo;
if (effectiveCacheSizeMB > 0) {
CacheConfig cacheConfig =
CacheConfig.builder()
.capacityMb(effectiveCacheSizeMB)
.meterRegistry(meterRegistry)
.build();
CacheBackend cacheBackend = PersistCaches.newBackend(cacheConfig);
CacheConfig.Builder cacheConfig =
CacheConfig.builder().capacityMb(effectiveCacheSizeMB).meterRegistry(meterRegistry);

storeConfig
.referenceCacheTtl()
.ifPresent(
refTtl -> {
LOGGER.warn(
"Reference caching is an experimental feature but enabled with a TTL of {}",
refTtl);
cacheConfig.referenceTtl(refTtl);
});
storeConfig.referenceCacheNegativeTtl().ifPresent(cacheConfig::referenceNegativeTtl);

CacheBackend cacheBackend = PersistCaches.newBackend(cacheConfig.build());
persist = cacheBackend.wrap(persist);
cacheInfo = "with " + effectiveCacheSizeMB + " MB objects cache";
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -404,13 +404,27 @@ public Reference fetchReference(@Nonnull @javax.annotation.Nonnull String name)
return delegate().fetchReference(name);
}

@Override
@Nullable
@javax.annotation.Nullable
public Reference fetchReferenceForUpdate(@Nonnull @javax.annotation.Nonnull String name) {
return delegate().fetchReferenceForUpdate(name);
}

@Override
@Nonnull
@javax.annotation.Nonnull
public Reference[] fetchReferences(@Nonnull @javax.annotation.Nonnull String[] names) {
return delegate().fetchReferences(names);
}

@Override
@Nonnull
@javax.annotation.Nonnull
public Reference[] fetchReferencesForUpdate(@Nonnull @javax.annotation.Nonnull String[] names) {
return delegate().fetchReferencesForUpdate(names);
}

@Override
@Nonnull
@javax.annotation.Nonnull
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,15 +173,15 @@ public Reference addReference(@Nonnull Reference reference) throws RefAlreadyExi
.filter(FILTERS.family().exactMatch(FAMILY_REFS))
.filter(FILTERS.qualifier().exactMatch(QUALIFIER_REFS));

boolean success =
boolean failure =
backend
.client()
.checkAndMutateRow(
ConditionalRowMutation.create(backend.tableRefsId, key)
.condition(condition)
.otherwise(mutation));

if (success) {
if (failure) {
throw new RefAlreadyExistsException(fetchReference(reference.name()));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,24 @@
*/
package org.projectnessie.versioned.storage.cache;

import static org.projectnessie.versioned.storage.common.persist.ObjId.zeroLengthObjId;
import static org.projectnessie.versioned.storage.common.persist.Reference.reference;

import jakarta.annotation.Nonnull;
import org.projectnessie.versioned.storage.common.persist.Backend;
import org.projectnessie.versioned.storage.common.persist.Obj;
import org.projectnessie.versioned.storage.common.persist.ObjId;
import org.projectnessie.versioned.storage.common.persist.Persist;
import org.projectnessie.versioned.storage.common.persist.Reference;

/**
* Provides the cache primitives for a caching {@link Persist} facade, suitable for multiple
* repositories. It is adviseable to have one {@link CacheBackend} per {@link Backend}.
*/
public interface CacheBackend {
Reference NON_EXISTENT_REFERENCE_SENTINEL =
reference("NON_EXISTENT", zeroLengthObjId(), false, -1L, null);

Obj get(@Nonnull String repositoryId, @Nonnull ObjId id);

void put(@Nonnull String repositoryId, @Nonnull Obj obj);
Expand All @@ -34,5 +41,13 @@ public interface CacheBackend {

void clear(@Nonnull String repositoryId);

Persist wrap(@Nonnull Persist perist);
Persist wrap(@Nonnull Persist persist);

Reference getReference(@Nonnull String repositoryId, @Nonnull String name);

void removeReference(@Nonnull String repositoryId, @Nonnull String name);

void putReference(@Nonnull String repositoryId, @Nonnull Reference r);

void putNegative(@Nonnull String repositoryId, @Nonnull String name);
dimas-b marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,30 @@
*/
package org.projectnessie.versioned.storage.cache;

import static com.google.common.base.Preconditions.checkState;

import com.google.errorprone.annotations.CanIgnoreReturnValue;
import io.micrometer.core.instrument.MeterRegistry;
import java.time.Duration;
import java.util.Optional;
import java.util.function.LongSupplier;
import org.immutables.value.Value;

@Value.Immutable
public interface CacheConfig {

String INVALID_REFERENCE_NEGATIVE_TTL =
"Cache reference-negative-TTL must only be present, if reference-TTL is configured, and must only be positive.";
String INVALID_REFERENCE_TTL = "Cache reference-TTL must be positive, if present.";

long capacityMb();

Optional<MeterRegistry> meterRegistry();

Optional<Duration> referenceTtl();

Optional<Duration> referenceNegativeTtl();

@Value.Default
default LongSupplier clockNanos() {
return System::nanoTime;
Expand All @@ -36,13 +48,31 @@ static Builder builder() {
return ImmutableCacheConfig.builder();
}

@Value.Check
default void check() {
referenceTtl()
.ifPresent(ttl -> checkState(ttl.compareTo(Duration.ZERO) > 0, INVALID_REFERENCE_TTL));
referenceNegativeTtl()
.ifPresent(
ttl ->
checkState(
referenceTtl().isPresent() && ttl.compareTo(Duration.ZERO) > 0,
INVALID_REFERENCE_NEGATIVE_TTL));
}

interface Builder {
@CanIgnoreReturnValue
Builder capacityMb(long capacityMb);

@CanIgnoreReturnValue
Builder meterRegistry(MeterRegistry meterRegistry);

@CanIgnoreReturnValue
Builder referenceTtl(Duration referenceTtl);

@CanIgnoreReturnValue
Builder referenceNegativeTtl(Duration referenceNegativeTtl);

@CanIgnoreReturnValue
Builder clockNanos(LongSupplier clockNanos);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
*/
package org.projectnessie.versioned.storage.cache;

import static org.projectnessie.versioned.storage.cache.CacheBackend.NON_EXISTENT_REFERENCE_SENTINEL;

import jakarta.annotation.Nonnull;
import java.util.Set;
import org.projectnessie.versioned.storage.common.config.StoreConfig;
Expand Down Expand Up @@ -261,40 +263,148 @@ public String name() {
return persist.name();
}

// References

@Override
@Nonnull
public Reference addReference(@Nonnull Reference reference) throws RefAlreadyExistsException {
return persist.addReference(reference);
Reference r = null;
try {
return r = persist.addReference(reference);
} finally {
if (r != null) {
cache.putReference(r);
} else {
cache.removeReference(reference.name());
}
}
}

@Override
@Nonnull
public Reference markReferenceAsDeleted(@Nonnull Reference reference)
throws RefNotFoundException, RefConditionFailedException {
return persist.markReferenceAsDeleted(reference);
Reference r = null;
try {
return r = persist.markReferenceAsDeleted(reference);
} finally {
if (r != null) {
cache.putReference(r);
} else {
cache.removeReference(reference.name());
}
}
}

@Override
public void purgeReference(@Nonnull Reference reference)
throws RefNotFoundException, RefConditionFailedException {
persist.purgeReference(reference);
try {
persist.purgeReference(reference);
} finally {
cache.removeReference(reference.name());
}
}

@Override
@Nonnull
public Reference updateReferencePointer(@Nonnull Reference reference, @Nonnull ObjId newPointer)
throws RefNotFoundException, RefConditionFailedException {
return persist.updateReferencePointer(reference, newPointer);
Reference r = null;
try {
return r = persist.updateReferencePointer(reference, newPointer);
} finally {
if (r != null) {
cache.putReference(r);
} else {
cache.removeReference(reference.name());
}
}
}

@Override
public Reference fetchReference(@Nonnull String name) {
return persist.fetchReference(name);
return fetchReferenceInternal(name, false);
}

@Override
public Reference fetchReferenceForUpdate(@Nonnull String name) {
return fetchReferenceInternal(name, true);
}

private Reference fetchReferenceInternal(@Nonnull String name, boolean bypassCache) {
Reference r = null;
if (!bypassCache) {
r = cache.getReference(name);
if (r == NON_EXISTENT_REFERENCE_SENTINEL) {
return null;
}
}

if (r == null) {
r = persist.fetchReferenceForUpdate(name);
if (r == null) {
cache.putNegative(name);
} else {
cache.putReference(r);
}
}
return r;
}

@Override
@Nonnull
public Reference[] fetchReferences(@Nonnull String[] names) {
return persist.fetchReferences(names);
return fetchReferencesInternal(names, false);
}

@Override
@Nonnull
public Reference[] fetchReferencesForUpdate(@Nonnull String[] names) {
return fetchReferencesInternal(names, true);
}

private Reference[] fetchReferencesInternal(@Nonnull String[] names, boolean bypassCache) {
Reference[] r = new Reference[names.length];

String[] backend = null;
if (!bypassCache) {
for (int i = 0; i < names.length; i++) {
String name = names[i];
if (name != null) {
Reference cr = cache.getReference(name);
if (cr != null) {
if (cr != NON_EXISTENT_REFERENCE_SENTINEL) {
r[i] = cr;
}
} else {
if (backend == null) {
backend = new String[names.length];
}
backend[i] = name;
}
}
}
} else {
backend = names;
}

if (backend != null) {
Reference[] br = persist.fetchReferencesForUpdate(backend);
for (int i = 0; i < br.length; i++) {
String name = backend[i];
if (name != null) {
Reference ref = br[i];
if (ref != null) {
r[i] = ref;
cache.putReference(ref);
} else {
cache.putNegative(name);
}
}
}
}

return r;
}
}
Loading