Add short-term host/contact caching for high-QPS domain mutations

After investigating common domain create/update command usage
patterns by registrars, we noticed that it is frequent for a
given registrar to reuse both hosts (using a standardized set of
nameservers) as well as contacts (e.g. for privacy/proxy
services). With these usage patterns, potential per-registrar
throughput during high volume scenarios (i.e. first moments of
General Availability) suffers from hitting hot keys in Datastore.

The solution, implemented in this CL, is to add short-term
in-memory caching for contacts and hosts, analogous to how we are
already caching Registry and Registrar entities.  These new
cached paths are only used inside domain flows to determine
existence and deleted/pending delete status of contacts and
hosts. This is a potential loss of transactional consistency, but
in practice it's hard to imagine this having negative effects, as
contacts or hosts that are in use cannot be deleted, and caching
would primarily affect widely used contacts and hosts.

Note that this caching can be turned on or off through a
configuration option, and by default would be off. We'd only want
it on when we really needed it, i.e. during a big launch.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=187093378
This commit is contained in:
mcilwain 2018-02-26 15:42:45 -08:00 committed by jianglai
parent cd9bd35a08
commit ce136f9285
13 changed files with 461 additions and 16 deletions

View file

@ -19,7 +19,6 @@ import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.Iterables.getOnlyElement;
import static com.google.common.collect.Maps.transformValues;
import static com.google.common.collect.Sets.difference;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.util.CollectionUtils.difference;
import static google.registry.util.CollectionUtils.forceEmptyToNull;
import static google.registry.util.CollectionUtils.nullSafeImmutableCopy;
@ -391,7 +390,7 @@ public class DomainCommand {
clone.registrant = clone.registrantContactId == null
? null
: getOnlyElement(
loadByForeignKey(
loadByForeignKeys(
ImmutableSet.of(clone.registrantContactId), ContactResource.class, now)
.values());
return clone;
@ -420,7 +419,7 @@ public class DomainCommand {
return null;
}
return ImmutableSet.copyOf(
loadByForeignKey(fullyQualifiedHostNames, HostResource.class, now).values());
loadByForeignKeys(fullyQualifiedHostNames, HostResource.class, now).values());
}
private static Set<DesignatedContact> linkContacts(
@ -433,7 +432,7 @@ public class DomainCommand {
foreignKeys.add(contact.contactId);
}
ImmutableMap<String, Key<ContactResource>> loadedContacts =
loadByForeignKey(foreignKeys.build(), ContactResource.class, now);
loadByForeignKeys(foreignKeys.build(), ContactResource.class, now);
ImmutableSet.Builder<DesignatedContact> linkedContacts = new ImmutableSet.Builder<>();
for (ForeignKeyedDesignatedContact contact : contacts) {
linkedContacts.add(DesignatedContact.create(
@ -442,12 +441,11 @@ public class DomainCommand {
return linkedContacts.build();
}
/** Load keys to resources by their foreign keys. */
private static <T extends EppResource> ImmutableMap<String, Key<T>> loadByForeignKey(
/** Loads keys to EPP resources by their foreign keys. */
private static <T extends EppResource> ImmutableMap<String, Key<T>> loadByForeignKeys(
final Set<String> foreignKeys, final Class<T> clazz, final DateTime now)
throws InvalidReferencesException {
Map<String, ForeignKeyIndex<T>> fkis =
ofy().doTransactionless(() -> ForeignKeyIndex.load(clazz, foreignKeys, now));
Map<String, ForeignKeyIndex<T>> fkis = ForeignKeyIndex.loadCached(clazz, foreignKeys, now);
if (!fkis.keySet().equals(foreignKeys)) {
throw new InvalidReferencesException(
clazz, ImmutableSet.copyOf(difference(foreignKeys, fkis.keySet())));