Skip to content
Open
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 @@ -32,6 +32,7 @@
import com.google.monitoring.metrics.LabelDescriptor;
import com.google.monitoring.metrics.MetricRegistryImpl;
import google.registry.cache.SimplifiedJedisClient;
import google.registry.config.RegistryConfig.Config;
import google.registry.model.EppResource;
import google.registry.model.common.Cursor;
import google.registry.model.domain.Domain;
Expand Down Expand Up @@ -87,13 +88,18 @@ public enum SyncStatus {
private final LockHandler lockHandler;
private final Response response;
private final Optional<SimplifiedJedisClient> jedisClient;
private final Duration domainExpiryAccessPeriodTotalLength;

@Inject
public SyncRemoteCacheAction(
LockHandler lockHandler, Response response, Optional<SimplifiedJedisClient> jedisClient) {
LockHandler lockHandler,
Response response,
Optional<SimplifiedJedisClient> jedisClient,
@Config("domainExpiryAccessPeriodTotalLength") Duration domainExpiryAccessPeriodTotalLength) {
this.lockHandler = lockHandler;
this.response = response;
this.jedisClient = jedisClient;
this.domainExpiryAccessPeriodTotalLength = domainExpiryAccessPeriodTotalLength;
}

@Override
Expand Down Expand Up @@ -187,10 +193,12 @@ private <T extends EppResource> void processResources(
ImmutableList.Builder<SimplifiedJedisClient.JedisResource<T>> toSaveBuilder =
new ImmutableList.Builder<>();

Instant now = tm().getTxTime();
for (T resource : resources) {
String key = getKeyFunction.apply(resource);
if (resource.getDeletionTime().isAfter(tm().getTxTime())) {
toSaveBuilder.add(new SimplifiedJedisClient.JedisResource<>(key, resource));
if (shouldSaveResourceInRemoteCache(resource, now)) {
toSaveBuilder.add(
new SimplifiedJedisClient.JedisResource<>(key, resource, getExpirationTime(resource)));
} else {
toDeleteBuilder.add(key);
}
Expand All @@ -204,6 +212,16 @@ private <T extends EppResource> void processResources(
logger.atInfo().log("Set %d in the remote cache", toSave.size());
}

private Optional<Instant> getExpirationTime(EppResource resource) {
return resource instanceof Domain domain
? Optional.of(domain.getDeletionTime().plus(domainExpiryAccessPeriodTotalLength))
: Optional.empty();
}

private boolean shouldSaveResourceInRemoteCache(EppResource resource, Instant now) {
return getExpirationTime(resource).orElseGet(resource::getDeletionTime).isAfter(now);
}

private Instant getPreviousCursorTime(Cursor.CursorType cursorType) {
return tm().loadByKeyIfPresent(Cursor.createGlobalVKey(cursorType))
.map(Cursor::getCursorTime)
Expand Down
23 changes: 19 additions & 4 deletions core/src/main/java/google/registry/cache/CacheModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.time.Duration;
import java.time.Instant;
import java.util.Optional;
import javax.net.ssl.SSLContext;
Expand Down Expand Up @@ -96,12 +97,26 @@ public static Optional<SimplifiedJedisClient> provideJedisClient(Optional<Unifie
@Provides
@Singleton
public static DomainCache provideDomainCache(
Optional<SimplifiedJedisClient> jedisClient, Clock clock, CacheMetrics cacheMetrics) {
Optional<SimplifiedJedisClient> jedisClient,
Clock clock,
CacheMetrics cacheMetrics,
@Config("domainExpiryAccessPeriodTotalLength") Duration domainExpiryAccessPeriodTotalLength) {
if (jedisClient.isEmpty()) {
return domainName ->
ForeignKeyUtils.loadResourceByCache(Domain.class, domainName, clock.now());
return new DomainCache() {
@Override
public Optional<Domain> loadByDomainName(String domainName) {
return ForeignKeyUtils.loadResourceByCache(Domain.class, domainName, clock.now());
}

@Override
public Optional<Domain> loadByDomainNameIncludingDeleted(String domainName) {
return ForeignKeyUtils.loadResourceByCacheIncludingDeleted(
Domain.class, domainName, clock.now());
}
};
}
return new MultilayerDomainCache(jedisClient.get(), clock, cacheMetrics);
return new MultilayerDomainCache(
jedisClient.get(), clock, cacheMetrics, domainExpiryAccessPeriodTotalLength);
}

@Provides
Expand Down
8 changes: 8 additions & 0 deletions core/src/main/java/google/registry/cache/DomainCache.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,13 @@

/** Interface for some type of cache that loads {@link Domain}s by domain name. */
public interface DomainCache {

/** Loads the active domain by domain name, filtering out soft-deleted domains. */
Optional<Domain> loadByDomainName(String domainName);

/**
* Loads the most recent domain by domain name from cache or database, including soft-deleted
* domains.
*/
Optional<Domain> loadByDomainNameIncludingDeleted(String domainName);
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,15 @@
package google.registry.cache;

import com.google.common.collect.ImmutableList;
import google.registry.config.RegistryConfig.Config;
import google.registry.model.ForeignKeyUtils;
import google.registry.model.domain.Domain;
import google.registry.model.tld.Tld;
import google.registry.model.tld.Tld.TldType;
import google.registry.util.Clock;
import jakarta.inject.Inject;
import java.time.Duration;
import java.time.Instant;
import java.util.Optional;

/**
Expand All @@ -29,16 +34,28 @@
public class MultilayerDomainCache extends MultilayerEppResourceCache<Domain>
implements DomainCache {

private final Duration domainExpiryAccessPeriodTotalLength;

@Inject
public MultilayerDomainCache(
SimplifiedJedisClient jedisClient, Clock clock, CacheMetrics cacheMetrics) {
SimplifiedJedisClient jedisClient,
Clock clock,
CacheMetrics cacheMetrics,
@Config("domainExpiryAccessPeriodTotalLength") Duration domainExpiryAccessPeriodTotalLength) {
super(jedisClient, clock, cacheMetrics);
this.domainExpiryAccessPeriodTotalLength = domainExpiryAccessPeriodTotalLength;
}

@Override
public Optional<Domain> loadByDomainName(String domainName) {
return loadFromCaches(Domain.class, domainName);
}

@Override
public Optional<Domain> loadByDomainNameIncludingDeleted(String domainName) {
return loadFromCachesIncludingDeleted(Domain.class, domainName);
}

@Override
protected Optional<Domain> loadFromDatabase(String domainName) {
// Don't use the cache (avoid caching the same domain twice). Do use the replica SQL instance.
Expand All @@ -50,6 +67,12 @@ protected Optional<Domain> loadFromDatabase(String domainName) {

@Override
protected boolean shouldPersistToRemoteCache(Domain domain) {
return Tld.get(domain.getTld()).getTldType().equals(Tld.TldType.REAL);
return Tld.get(domain.getTld()).getTldType().equals(TldType.REAL)
&& super.shouldPersistToRemoteCache(domain);
}

@Override
protected Optional<Instant> getExpirationTime(Domain domain) {
return Optional.of(domain.getDeletionTime().plus(domainExpiryAccessPeriodTotalLength));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public abstract class MultilayerEppResourceCache<V extends EppResource> {
.build();

private final SimplifiedJedisClient jedisClient;
private final Clock clock;
protected final Clock clock;
private final CacheMetrics cacheMetrics;

protected MultilayerEppResourceCache(
Expand All @@ -51,16 +51,23 @@ protected MultilayerEppResourceCache(
protected abstract Optional<V> loadFromDatabase(String key);

protected boolean shouldPersistToRemoteCache(V value) {
return true;
return getExpirationTime(value).orElseGet(value::getDeletionTime).isAfter(clock.now());
}

protected Optional<Instant> getExpirationTime(V resource) {
return Optional.empty();
}

@SuppressWarnings("unchecked")
protected Optional<V> loadFromCaches(Class<V> clazz, String key) {
Instant now = clock.now();
return (Optional<V>)
loadFromCachesInternal(clazz, key)
.filter(v -> now.isBefore(v.getDeletionTime()))
.map(v -> v.cloneProjectedAtTime(now));
return loadFromCachesIncludingDeleted(clazz, key)
.filter(v -> now.isBefore(v.getDeletionTime()));
}

@SuppressWarnings("unchecked")
protected Optional<V> loadFromCachesIncludingDeleted(Class<V> clazz, String key) {
Instant now = clock.now();
return (Optional<V>) loadFromCachesInternal(clazz, key).map(v -> v.cloneProjectedAtTime(now));
}

private Optional<V> loadFromCachesInternal(Class<V> clazz, String key) {
Expand All @@ -87,7 +94,8 @@ private Optional<V> loadFromCachesInternal(Class<V> clazz, String key) {
}
V value = possibleValue.get();
if (shouldPersistToRemoteCache(value)) {
jedisClient.set(new SimplifiedJedisClient.JedisResource<>(key, value));
jedisClient.set(
new SimplifiedJedisClient.JedisResource<>(key, value, getExpirationTime(value)));
}
localCache.put(key, value);
cacheMetrics.recordLookup(clazz.getSimpleName(), CacheMetrics.CacheHitType.MISS);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import java.net.Inet6Address;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.Optional;
import redis.clients.jedis.AbstractPipeline;
import redis.clients.jedis.UnifiedJedis;
Expand All @@ -55,7 +56,27 @@
*/
public class SimplifiedJedisClient {

public record JedisResource<V extends EppResource>(String key, V value) {}
public record JedisResource<V extends EppResource>(
String key, V value, Optional<Instant> expirationTime) {

public JedisResource {
checkNotNull(key, "Key cannot be null");
checkNotNull(value, "Value cannot be null");
checkNotNull(expirationTime, "expirationTime cannot be null");
}

public JedisResource(String key, V value) {
this(key, value, Optional.empty());
}

public JedisResource(String key, V value, Instant expirationTime) {
this(key, value, Optional.ofNullable(expirationTime));
}

public Instant getExpirationTime() {
return expirationTime.orElseGet(() -> value.getDeletionTime());
}
}

private static final ImmutableMap<Class<? extends EppResource>, String> TYPE_PREFIXES =
ImmutableMap.of(
Expand Down Expand Up @@ -101,7 +122,7 @@ public <V extends EppResource> void set(JedisResource<V> resource) {
jedis.set(
convertKey(resource.value.getClass(), resource.key),
serialize(resource.value),
new SetParams().pxAt(resource.value.getDeletionTime().toEpochMilli()));
new SetParams().pxAt(resource.getExpirationTime().toEpochMilli()));
}

/** Sets multiple values in the remote cache using a Jedis {@link AbstractPipeline}. */
Expand All @@ -114,12 +135,33 @@ public <V extends EppResource> void setAll(ImmutableCollection<JedisResource<V>>
pipeline.set(
convertKey(resource.value.getClass(), resource.key),
serialize(resource.value),
new SetParams().pxAt(resource.value.getDeletionTime().toEpochMilli())));
new SetParams().pxAt(resource.getExpirationTime().toEpochMilli())));
pipeline.sync();
}
}
}

/**
* Deletes the value associated with the given key in Valkey.
*
* <p>If the given key does not exist, it does nothing.
*/
public void delete(Class<? extends EppResource> resourceClass, String key) {
checkNotNull(resourceClass, "resourceClass cannot be null");
checkNotNull(key, "Key cannot be null");
jedis.unlink(getRedisKey(resourceClass, key));
}

/** Deletes the given resource in Valkey. */
public void delete(JedisResource<?> resource) {
checkNotNull(resource, "resource cannot be null");
delete(resource.value.getClass().asSubclass(EppResource.class), resource.key);
}

byte[] getRedisKey(Class<?> clazz, String key) {
return convertKey(clazz, key);
}

/**
* Deletes all values associated with the given keys in Valkey.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@
import static google.registry.flows.domain.DomainFlowUtils.getReservationTypes;
import static google.registry.flows.domain.DomainFlowUtils.handleFeeRequest;
import static google.registry.flows.domain.DomainFlowUtils.isAnchorTenant;
import static google.registry.flows.domain.DomainFlowUtils.isDomainEligibleForXap;
import static google.registry.flows.domain.DomainFlowUtils.isRegisterBsaCreate;
import static google.registry.flows.domain.DomainFlowUtils.isReserved;
import static google.registry.flows.domain.DomainFlowUtils.isValidReservedCreate;
import static google.registry.flows.domain.DomainFlowUtils.loadDomainIfInXap;
import static google.registry.flows.domain.DomainFlowUtils.validateDomainName;
import static google.registry.flows.domain.DomainFlowUtils.validateDomainNameWithIdnTables;
import static google.registry.flows.domain.DomainFlowUtils.verifyNotInPredelegation;
Expand Down Expand Up @@ -259,13 +259,6 @@ private Optional<String> getMessageForCheck(
idn, existingDomains, bsaBlockedDomainNames, tldStates, token, now);
}

private static Optional<Domain> loadDomainIfInXap(
String domainName, Instant now, Duration domainExpiryAccessPeriodTotalLength) {
return ForeignKeyUtils.loadResource(
Domain.class, domainName, now.minus(domainExpiryAccessPeriodTotalLength))
.filter(domain -> isDomainEligibleForXap(domain, Tld.get(domain.getTld()), now));
}

private Optional<String> getMessageForCheckWithToken(
InternetDomainName domainName,
ImmutableMap<String, VKey<Domain>> existingDomains,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,14 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.net.InternetDomainName;
import google.registry.cache.SimplifiedJedisClient;
import google.registry.config.RegistryConfig;
import google.registry.config.RegistryConfig.Config;
import google.registry.flows.EppException;
import google.registry.flows.EppException.CommandUseErrorException;
import google.registry.flows.EppException.ParameterValuePolicyErrorException;
import google.registry.flows.ExtensionManager;
import google.registry.flows.FlowModule.DryRun;
import google.registry.flows.FlowModule.RegistrarId;
import google.registry.flows.FlowModule.Superuser;
import google.registry.flows.FlowModule.TargetId;
Expand Down Expand Up @@ -224,6 +226,8 @@ public final class DomainCreateFlow implements MutatingFlow {
@Inject DomainFlowTmchUtils tmchUtils;
@Inject DomainPricingLogic pricingLogic;
@Inject DomainDeletionTimeCache domainDeletionTimeCache;
@Inject Optional<SimplifiedJedisClient> jedisClient;
@Inject @DryRun boolean isDryRun;

@Inject
@Config("domainExpiryAccessPeriodTotalLength")
Expand All @@ -232,6 +236,10 @@ public final class DomainCreateFlow implements MutatingFlow {
@Inject
DomainCreateFlow() {}

public String getTargetId() {
return targetId;
}

@Override
public EppResponse run() throws EppException {
extensionManager.register(
Expand Down Expand Up @@ -461,6 +469,9 @@ public EppResponse run() throws EppException {
.setYears(years)
.build());
persistEntityChanges(entityChanges);
if (!isDryRun) {
jedisClient.ifPresent(client -> client.delete(Domain.class, getTargetId()));
}

// If the registrar is participating in tiered pricing promos, return the standard price in the
// response (even if the actual charged price is less)
Expand Down
Loading
Loading