mirror of
https://github.com/google/nomulus.git
synced 2025-08-19 16:04:31 +02:00
Optimize RDAP entity event query (#1635)
* Optimize RDAP entity event query For each EPP entity, directly load the latest HistoryEntry per event type instead of loading all events through the HistoryEntryDao. Although most entities have a small number of history entries, there are a few entities with many entries, enough to cause OutOfMemory error.
This commit is contained in:
parent
dcaae78657
commit
50891e0e72
3 changed files with 71 additions and 15 deletions
|
@ -211,7 +211,7 @@ public class HistoryEntryDao {
|
||||||
jpaTm().criteriaQuery(criteriaQuery).getResultList());
|
jpaTm().criteriaQuery(criteriaQuery).getResultList());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Class<? extends HistoryEntry> getHistoryClassFromParent(
|
public static Class<? extends HistoryEntry> getHistoryClassFromParent(
|
||||||
Class<? extends EppResource> parent) {
|
Class<? extends EppResource> parent) {
|
||||||
if (!RESOURCE_TYPES_TO_HISTORY_TYPES.containsKey(parent)) {
|
if (!RESOURCE_TYPES_TO_HISTORY_TYPES.containsKey(parent)) {
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
|
@ -220,7 +220,7 @@ public class HistoryEntryDao {
|
||||||
return RESOURCE_TYPES_TO_HISTORY_TYPES.get(parent);
|
return RESOURCE_TYPES_TO_HISTORY_TYPES.get(parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String getRepoIdFieldNameFromHistoryClass(
|
public static String getRepoIdFieldNameFromHistoryClass(
|
||||||
Class<? extends HistoryEntry> historyClass) {
|
Class<? extends HistoryEntry> historyClass) {
|
||||||
if (!REPO_ID_FIELD_NAMES.containsKey(historyClass)) {
|
if (!REPO_ID_FIELD_NAMES.containsKey(historyClass)) {
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
|
|
|
@ -20,11 +20,14 @@ import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||||
import static com.google.common.collect.ImmutableSetMultimap.toImmutableSetMultimap;
|
import static com.google.common.collect.ImmutableSetMultimap.toImmutableSetMultimap;
|
||||||
import static google.registry.model.EppResourceUtils.isLinked;
|
import static google.registry.model.EppResourceUtils.isLinked;
|
||||||
|
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
|
||||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||||
import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
|
import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
|
||||||
import static google.registry.rdap.RdapIcannStandardInformation.CONTACT_REDACTED_VALUE;
|
import static google.registry.rdap.RdapIcannStandardInformation.CONTACT_REDACTED_VALUE;
|
||||||
import static google.registry.util.CollectionUtils.union;
|
import static google.registry.util.CollectionUtils.union;
|
||||||
|
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
import com.google.common.base.Strings;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
@ -81,6 +84,7 @@ import java.util.Set;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
import javax.persistence.Entity;
|
||||||
import org.joda.time.DateTime;
|
import org.joda.time.DateTime;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -150,6 +154,19 @@ public class RdapJsonFormatter {
|
||||||
INTERNAL
|
INTERNAL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JPQL query template for finding the latest history entry per event type for an EPP entity.
|
||||||
|
*
|
||||||
|
* <p>User should replace '%entityName%', '%repoIdField%', and '%repoIdValue%' with valid values.
|
||||||
|
* A DomainHistory query may look like below: {@code select e from DomainHistory e where
|
||||||
|
* domainRepoId = '17-Q9JYB4C' and modificationTime in (select max(modificationTime) from
|
||||||
|
* DomainHistory where domainRepoId = '17-Q9JYB4C' and type is not null group by type)}
|
||||||
|
*/
|
||||||
|
private static final String GET_LAST_HISTORY_BY_TYPE_JPQL_TEMPLATE =
|
||||||
|
"select e from %entityName% e where %repoIdField% = '%repoIdValue%' and modificationTime in "
|
||||||
|
+ " (select max(modificationTime) from %entityName% where "
|
||||||
|
+ " %repoIdField% = '%repoIdValue%' and type is not null group by type)";
|
||||||
|
|
||||||
/** Map of EPP status values to the RDAP equivalents. */
|
/** Map of EPP status values to the RDAP equivalents. */
|
||||||
private static final ImmutableMap<StatusValue, RdapStatus> STATUS_TO_RDAP_STATUS_MAP =
|
private static final ImmutableMap<StatusValue, RdapStatus> STATUS_TO_RDAP_STATUS_MAP =
|
||||||
new ImmutableMap.Builder<StatusValue, RdapStatus>()
|
new ImmutableMap.Builder<StatusValue, RdapStatus>()
|
||||||
|
@ -855,17 +872,8 @@ public class RdapJsonFormatter {
|
||||||
return rolesBuilder.build();
|
return rolesBuilder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@VisibleForTesting
|
||||||
* Creates the list of optional events to list in domain, nameserver, or contact replies.
|
ImmutableMap<EventAction, HistoryEntry> getLastHistoryEntryByType(EppResource resource) {
|
||||||
*
|
|
||||||
* <p>Only has entries for optional events that won't be shown in "SUMMARY" versions of these
|
|
||||||
* objects. These are either stated as optional in the RDAP Response Profile 15feb19, or not
|
|
||||||
* mentioned at all but thought to be useful anyway.
|
|
||||||
*
|
|
||||||
* <p>Any required event should be added elsewhere, preferably without using HistoryEntries (so
|
|
||||||
* that we don't need to load HistoryEntries for "summary" responses).
|
|
||||||
*/
|
|
||||||
private ImmutableList<Event> makeOptionalEvents(EppResource resource) {
|
|
||||||
HashMap<EventAction, HistoryEntry> lastEntryOfType = Maps.newHashMap();
|
HashMap<EventAction, HistoryEntry> lastEntryOfType = Maps.newHashMap();
|
||||||
// Events (such as transfer, but also create) can appear multiple times. We only want the last
|
// Events (such as transfer, but also create) can appear multiple times. We only want the last
|
||||||
// time they appeared.
|
// time they appeared.
|
||||||
|
@ -878,8 +886,26 @@ public class RdapJsonFormatter {
|
||||||
// 2.3.2.3 An event of *eventAction* type *transfer*, with the last date and time that the
|
// 2.3.2.3 An event of *eventAction* type *transfer*, with the last date and time that the
|
||||||
// domain was transferred. The event of *eventAction* type *transfer* MUST be omitted if the
|
// domain was transferred. The event of *eventAction* type *transfer* MUST be omitted if the
|
||||||
// domain name has not been transferred since it was created.
|
// domain name has not been transferred since it was created.
|
||||||
Iterable<? extends HistoryEntry> historyEntries =
|
Iterable<? extends HistoryEntry> historyEntries;
|
||||||
HistoryEntryDao.loadHistoryObjectsForResource(resource.createVKey());
|
if (tm().isOfy()) {
|
||||||
|
historyEntries = HistoryEntryDao.loadHistoryObjectsForResource(resource.createVKey());
|
||||||
|
} else {
|
||||||
|
VKey<? extends EppResource> resourceVkey = resource.createVKey();
|
||||||
|
Class<? extends HistoryEntry> historyClass =
|
||||||
|
HistoryEntryDao.getHistoryClassFromParent(resourceVkey.getKind());
|
||||||
|
String entityName = historyClass.getAnnotation(Entity.class).name();
|
||||||
|
if (Strings.isNullOrEmpty(entityName)) {
|
||||||
|
entityName = historyClass.getSimpleName();
|
||||||
|
}
|
||||||
|
String repoIdFieldName = HistoryEntryDao.getRepoIdFieldNameFromHistoryClass(historyClass);
|
||||||
|
String jpql =
|
||||||
|
GET_LAST_HISTORY_BY_TYPE_JPQL_TEMPLATE
|
||||||
|
.replace("%entityName%", entityName)
|
||||||
|
.replace("%repoIdField%", repoIdFieldName)
|
||||||
|
.replace("%repoIdValue%", resourceVkey.getSqlKey().toString());
|
||||||
|
historyEntries =
|
||||||
|
jpaTm().transact(() -> jpaTm().getEntityManager().createQuery(jpql).getResultList());
|
||||||
|
}
|
||||||
for (HistoryEntry historyEntry : historyEntries) {
|
for (HistoryEntry historyEntry : historyEntries) {
|
||||||
EventAction rdapEventAction =
|
EventAction rdapEventAction =
|
||||||
HISTORY_ENTRY_TYPE_TO_RDAP_EVENT_ACTION_MAP.get(historyEntry.getType());
|
HISTORY_ENTRY_TYPE_TO_RDAP_EVENT_ACTION_MAP.get(historyEntry.getType());
|
||||||
|
@ -889,6 +915,21 @@ public class RdapJsonFormatter {
|
||||||
}
|
}
|
||||||
lastEntryOfType.put(rdapEventAction, historyEntry);
|
lastEntryOfType.put(rdapEventAction, historyEntry);
|
||||||
}
|
}
|
||||||
|
return ImmutableMap.copyOf(lastEntryOfType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the list of optional events to list in domain, nameserver, or contact replies.
|
||||||
|
*
|
||||||
|
* <p>Only has entries for optional events that won't be shown in "SUMMARY" versions of these
|
||||||
|
* objects. These are either stated as optional in the RDAP Response Profile 15feb19, or not
|
||||||
|
* mentioned at all but thought to be useful anyway.
|
||||||
|
*
|
||||||
|
* <p>Any required event should be added elsewhere, preferably without using HistoryEntries (so
|
||||||
|
* that we don't need to load HistoryEntries for "summary" responses).
|
||||||
|
*/
|
||||||
|
private ImmutableList<Event> makeOptionalEvents(EppResource resource) {
|
||||||
|
ImmutableMap<EventAction, HistoryEntry> lastEntryOfType = getLastHistoryEntryByType(resource);
|
||||||
ImmutableList.Builder<Event> eventsBuilder = new ImmutableList.Builder<>();
|
ImmutableList.Builder<Event> eventsBuilder = new ImmutableList.Builder<>();
|
||||||
DateTime creationTime = resource.getCreationTime();
|
DateTime creationTime = resource.getCreationTime();
|
||||||
DateTime lastChangeTime =
|
DateTime lastChangeTime =
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
package google.registry.rdap;
|
package google.registry.rdap;
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
import static google.registry.rdap.RdapDataStructures.EventAction.TRANSFER;
|
||||||
import static google.registry.rdap.RdapTestHelper.assertThat;
|
import static google.registry.rdap.RdapTestHelper.assertThat;
|
||||||
import static google.registry.testing.DatabaseHelper.createTld;
|
import static google.registry.testing.DatabaseHelper.createTld;
|
||||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||||
|
@ -28,7 +29,9 @@ import static google.registry.testing.TestDataHelper.loadFile;
|
||||||
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
|
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
import google.registry.model.contact.ContactResource;
|
import google.registry.model.contact.ContactResource;
|
||||||
|
@ -51,6 +54,7 @@ import google.registry.testing.DualDatabaseTest;
|
||||||
import google.registry.testing.FakeClock;
|
import google.registry.testing.FakeClock;
|
||||||
import google.registry.testing.InjectExtension;
|
import google.registry.testing.InjectExtension;
|
||||||
import google.registry.testing.TestOfyAndSql;
|
import google.registry.testing.TestOfyAndSql;
|
||||||
|
import google.registry.testing.TestSqlOnly;
|
||||||
import org.joda.time.DateTime;
|
import org.joda.time.DateTime;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||||
|
@ -482,6 +486,17 @@ class RdapJsonFormatterTest {
|
||||||
.isEqualTo(loadJson("rdapjson_domain_summary.json"));
|
.isEqualTo(loadJson("rdapjson_domain_summary.json"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestSqlOnly
|
||||||
|
void testGetLastHistoryEntryByType() {
|
||||||
|
// Expected data are from "rdapjson_domain_summary.json"
|
||||||
|
assertThat(
|
||||||
|
Maps.transformValues(
|
||||||
|
rdapJsonFormatter.getLastHistoryEntryByType(domainBaseFull),
|
||||||
|
HistoryEntry::getModificationTime))
|
||||||
|
.containsExactlyEntriesIn(
|
||||||
|
ImmutableMap.of(TRANSFER, DateTime.parse("1999-12-01T00:00:00.000Z")));
|
||||||
|
}
|
||||||
|
|
||||||
@TestOfyAndSql
|
@TestOfyAndSql
|
||||||
void testDomain_logged_out() {
|
void testDomain_logged_out() {
|
||||||
rdapJsonFormatter.rdapAuthorization = RdapAuthorization.PUBLIC_AUTHORIZATION;
|
rdapJsonFormatter.rdapAuthorization = RdapAuthorization.PUBLIC_AUTHORIZATION;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue