Load foreign keys more efficiently for xml marshalling.

Before this CL, each contact and host was independently
loaded via the ReferenceUnion adapter. Since fields are
processed serially by JAXB, this means worst-case there
were 17 loads, best case 3 (the 3 required contacts) and
usual case 5-6 (some hosts). This CL reduces that to 1
datastore roundtrip in all cases.

A side effect of this CL is the further hollowing-out of
ReferenceUnion, since it no longer is involved in
marshalling at all.
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=123712842
This commit is contained in:
cgoldfeder 2016-05-31 20:41:22 -07:00 committed by Ben McIlwain
parent 5fb06de203
commit 23b66b0bb4
8 changed files with 140 additions and 112 deletions

View file

@ -14,11 +14,15 @@
package google.registry.model.domain;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Predicates.not;
import static com.google.common.base.Strings.emptyToNull;
import static com.google.common.collect.Iterables.all;
import static com.google.common.collect.Iterables.any;
import static com.google.common.collect.Sets.difference;
import static com.google.common.collect.Sets.union;
import static google.registry.model.domain.DesignatedContact.Type.REGISTRANT;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.util.CollectionUtils.forceEmptyToNull;
import static google.registry.util.CollectionUtils.nullToEmpty;
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
import static google.registry.util.CollectionUtils.nullToEmptyImmutableSortedCopy;
@ -27,20 +31,20 @@ import static google.registry.util.DomainNameUtils.getTldFromDomainName;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.googlecode.objectify.Ref;
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Ignore;
import com.googlecode.objectify.annotation.IgnoreSave;
import com.googlecode.objectify.annotation.Index;
import com.googlecode.objectify.annotation.OnLoad;
import com.googlecode.objectify.condition.IfNull;
import google.registry.model.EppResource;
import google.registry.model.contact.ContactResource;
import google.registry.model.domain.DesignatedContact.Type;
import google.registry.model.domain.launch.LaunchNotice;
import google.registry.model.domain.secdns.DelegationSignerData;
import google.registry.model.eppcommon.AuthInfo;
@ -76,40 +80,18 @@ public abstract class DomainBase extends EppResource {
String tld;
/** References to hosts that are the nameservers for the domain. */
@XmlElementWrapper(name = "ns")
@XmlElement(name = "hostObj")
@XmlTransient
//TODO(b/28713909): Make this a Set<Ref<HostResource>>.
Set<ReferenceUnion<HostResource>> nameservers;
/**
* Associated contacts for the domain (other than registrant).
* The union of the contacts visible via {@link #getContacts} and {@link #getRegistrant}.
*
* <p>This field is marked with {@literal @}Ignore so that {@link DomainBase} subclasses won't
* persist it. Instead, the data in this field and in the {@link #registrant} are both stored in
* {@link DomainBase#allContacts} to allow for more efficient queries.
*/
@Ignore
@XmlElement(name = "contact")
Set<DesignatedContact> contacts;
/**
* The union of the contacts in {@link #contacts} and {@link #registrant}. This is so we can query
* across all contacts at once. It is maintained by the builder and by {@link #unpackageContacts}.
* <p>These are stored in one field so that we can query across all contacts at once.
*/
@XmlTransient
Set<DesignatedContact> allContacts;
/**
* A reference to the registrant who registered this domain.
*
* <p>This field is marked with {@literal @}Ignore so that {@link DomainBase} subclasses won't
* persist it. Instead, the data in this field and in the {@link DomainBase#contacts} are both
* stored in {@link DomainBase#allContacts} to allow for more efficient queries.
*/
@Ignore
//TODO(b/28713909): Make this a Ref<ContactResource>.
ReferenceUnion<ContactResource> registrant;
/** Authorization info (aka transfer secret) of the domain. */
DomainAuthInfo authInfo;
@ -139,6 +121,49 @@ public abstract class DomainBase extends EppResource {
@XmlTransient
String idnTableName;
/**
* Synchronously load all referenced contacts and hosts into the Objectify session cache.
*
* <p>This saves an extra datastore roundtrip on marshalling, since contacts, hosts, and the
* registrant will all be in the session cache when their respective methods are called.
*/
private void preMarshal() {
// Calling values() blocks until loading is done.
ofy().load().values(union(getNameservers(), getReferencedContacts())).values();
}
/** JAXB java beans property to marshal nameserver hostnames. */
@XmlElementWrapper(name = "ns")
@XmlElement(name = "hostObj")
private ImmutableSet<String> getMarshalledNameservers() {
preMarshal();
// If there are no nameservers we must return null, or an empty "ns" element will be marshalled.
return forceEmptyToNull(loadNameserverFullyQualifiedHostNames());
}
/** JAXB java beans property to marshal non-registrant contact ids. */
@XmlElement(name = "contact")
private ImmutableSet<ForeignKeyedDesignatedContact> getMarshalledContacts() {
preMarshal();
return FluentIterable.from(getContacts())
.transform(
new Function<DesignatedContact, ForeignKeyedDesignatedContact>() {
@Override
public ForeignKeyedDesignatedContact apply(DesignatedContact designated) {
return ForeignKeyedDesignatedContact.create(
designated.getType(),
designated.getContactRef().get().getContactId());
}})
.toSet();
}
/** JAXB java beans property to marshal nameserver hostnames. */
@XmlElement(name = "registrant")
private String getMarshalledRegistrant() {
preMarshal();
return getRegistrant().get().getContactId();
}
public String getFullyQualifiedDomainName() {
return fullyQualifiedDomainName;
}
@ -175,12 +200,21 @@ public abstract class DomainBase extends EppResource {
.toSet();
}
/** A reference to the registrant who registered this domain. */
public Ref<ContactResource> getRegistrant() {
return registrant.getLinked();
return FluentIterable
.from(nullToEmpty(allContacts))
.filter(IS_REGISTRANT)
.getOnlyElement()
.getContactRef();
}
/** Associated contacts for the domain (other than registrant). */
public ImmutableSet<DesignatedContact> getContacts() {
return nullToEmptyImmutableCopy(contacts);
return FluentIterable
.from(nullToEmpty(allContacts))
.filter(not(IS_REGISTRANT))
.toSet();
}
public AuthInfo getAuthInfo() {
@ -201,22 +235,13 @@ public abstract class DomainBase extends EppResource {
return tld;
}
/**
* On load, unpackage the {@link #allContacts} field, placing the registrant into
* {@link #registrant} field and all other contacts into {@link #contacts}.
*/
@OnLoad
void unpackageContacts() {
ImmutableSet.Builder<DesignatedContact> contactsBuilder = new ImmutableSet.Builder<>();
for (DesignatedContact contact : nullToEmptyImmutableCopy(allContacts)) {
if (REGISTRANT.equals(contact.getType())){
registrant = ReferenceUnion.create(contact.getContactRef());
} else {
contactsBuilder.add(contact);
}
}
contacts = contactsBuilder.build();
}
/** Predicate to determine if a given {@link DesignatedContact} is the registrant. */
private static final Predicate<DesignatedContact> IS_REGISTRANT =
new Predicate<DesignatedContact>() {
@Override
public boolean apply(DesignatedContact contact) {
return DesignatedContact.Type.REGISTRANT.equals(contact.type);
}};
/** An override of {@link EppResource#asBuilder} with tighter typing. */
@Override
@ -225,6 +250,7 @@ public abstract class DomainBase extends EppResource {
/** A builder for constructing {@link DomainBase}, since it is immutable. */
public abstract static class Builder<T extends DomainBase, B extends Builder<?, ?>>
extends EppResource.Builder<T, B> {
protected Builder() {}
protected Builder(T instance) {
@ -236,11 +262,8 @@ public abstract class DomainBase extends EppResource {
T instance = getInstance();
checkArgumentNotNull(
emptyToNull(instance.fullyQualifiedDomainName), "Missing fullyQualifiedDomainName");
checkArgumentNotNull(instance.registrant, "Missing registrant");
checkArgument(any(instance.allContacts, IS_REGISTRANT), "Missing registrant");
instance.tld = getTldFromDomainName(instance.fullyQualifiedDomainName);
instance.allContacts = union(
instance.getContacts(),
DesignatedContact.create(REGISTRANT, instance.registrant.getLinked()));
return super.build();
}
@ -255,7 +278,10 @@ public abstract class DomainBase extends EppResource {
}
public B setRegistrant(Ref<ContactResource> registrant) {
getInstance().registrant = ReferenceUnion.create(registrant);
// Replace the registrant contact inside allContacts.
getInstance().allContacts = union(
getInstance().getContacts(),
DesignatedContact.create(Type.REGISTRANT, checkArgumentNotNull(registrant)));
return thisCastToDerived();
}
@ -284,7 +310,13 @@ public abstract class DomainBase extends EppResource {
}
public B setContacts(ImmutableSet<DesignatedContact> contacts) {
getInstance().contacts = contacts;
checkArgument(all(contacts, not(IS_REGISTRANT)), "Registrant cannot be a contact");
// Replace the non-registrant contacts inside allContacts.
getInstance().allContacts = FluentIterable
.from(nullToEmpty(getInstance().allContacts))
.filter(IS_REGISTRANT)
.append(contacts)
.toSet();
return thisCastToDerived();
}