mirror of
https://github.com/google/nomulus.git
synced 2025-07-03 09:43:30 +02:00
Make EppResource.loadCached() use batched fetch (#652)
* Make EppResource.loadCached() use batched fetch Use a batched fetch (ofy().load().keys(...)) from datastore in EppResource.loadCached(). To support this, convert TransactionManager.load(Iterable<VKey>) to accept the more flexible generic parameters and return a map. * Simplify datastore key streaming * Changes requested in review.
This commit is contained in:
parent
c7e9faea6b
commit
10e82953ae
8 changed files with 68 additions and 39 deletions
|
@ -16,7 +16,6 @@ package google.registry.model;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkArgument;
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
import static com.google.common.base.Preconditions.checkState;
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
import static com.google.common.collect.ImmutableMap.toImmutableMap;
|
|
||||||
import static com.google.common.collect.Sets.difference;
|
import static com.google.common.collect.Sets.difference;
|
||||||
import static com.google.common.collect.Sets.union;
|
import static com.google.common.collect.Sets.union;
|
||||||
import static google.registry.config.RegistryConfig.getEppResourceCachingDuration;
|
import static google.registry.config.RegistryConfig.getEppResourceCachingDuration;
|
||||||
|
@ -47,7 +46,6 @@ import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.stream.StreamSupport;
|
|
||||||
import javax.persistence.Access;
|
import javax.persistence.Access;
|
||||||
import javax.persistence.AccessType;
|
import javax.persistence.AccessType;
|
||||||
import javax.persistence.Column;
|
import javax.persistence.Column;
|
||||||
|
@ -357,7 +355,7 @@ public abstract class EppResource extends BackupGroupRoot implements Buildable {
|
||||||
@Override
|
@Override
|
||||||
public Map<VKey<? extends EppResource>, EppResource> loadAll(
|
public Map<VKey<? extends EppResource>, EppResource> loadAll(
|
||||||
Iterable<? extends VKey<? extends EppResource>> keys) {
|
Iterable<? extends VKey<? extends EppResource>> keys) {
|
||||||
return tm().doTransactionless(() -> loadAsMap(keys));
|
return tm().doTransactionless(() -> tm().load(keys));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -397,7 +395,7 @@ public abstract class EppResource extends BackupGroupRoot implements Buildable {
|
||||||
public static ImmutableMap<VKey<? extends EppResource>, EppResource> loadCached(
|
public static ImmutableMap<VKey<? extends EppResource>, EppResource> loadCached(
|
||||||
Iterable<VKey<? extends EppResource>> keys) {
|
Iterable<VKey<? extends EppResource>> keys) {
|
||||||
if (!RegistryConfig.isEppResourceCachingEnabled()) {
|
if (!RegistryConfig.isEppResourceCachingEnabled()) {
|
||||||
return loadAsMap(keys);
|
return tm().load(keys);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return cacheEppResources.getAll(keys);
|
return cacheEppResources.getAll(keys);
|
||||||
|
@ -425,17 +423,4 @@ public abstract class EppResource extends BackupGroupRoot implements Buildable {
|
||||||
throw new RuntimeException("Error loading cached EppResources", e.getCause());
|
throw new RuntimeException("Error loading cached EppResources", e.getCause());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ImmutableMap<VKey<? extends EppResource>, EppResource> loadAsMap(
|
|
||||||
Iterable<? extends VKey<? extends EppResource>> keys) {
|
|
||||||
return StreamSupport.stream(keys.spliterator(), false)
|
|
||||||
// It's possible for us to receive the same key more than once which causes
|
|
||||||
// the immutable map build to break with a duplicate key, so we have to ensure key
|
|
||||||
// uniqueness.
|
|
||||||
.distinct()
|
|
||||||
// We have to use "key -> key" here instead of the identity() function, because
|
|
||||||
// the latter breaks the fairly complicated generic type checking required by the
|
|
||||||
// caching interface.
|
|
||||||
.collect(toImmutableMap(key -> key, key -> tm().load(key)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,15 +15,18 @@
|
||||||
package google.registry.model.ofy;
|
package google.registry.model.ofy;
|
||||||
|
|
||||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||||
|
import static com.google.common.collect.ImmutableMap.toImmutableMap;
|
||||||
import static google.registry.model.ofy.ObjectifyService.ofy;
|
import static google.registry.model.ofy.ObjectifyService.ofy;
|
||||||
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||||
|
|
||||||
|
import com.google.common.base.Functions;
|
||||||
import com.google.common.collect.ImmutableCollection;
|
import com.google.common.collect.ImmutableCollection;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.googlecode.objectify.Key;
|
import com.googlecode.objectify.Key;
|
||||||
import google.registry.persistence.VKey;
|
import google.registry.persistence.VKey;
|
||||||
import google.registry.persistence.transaction.TransactionManager;
|
import google.registry.persistence.transaction.TransactionManager;
|
||||||
import java.util.Iterator;
|
import java.util.Map.Entry;
|
||||||
import java.util.NoSuchElementException;
|
import java.util.NoSuchElementException;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
@ -156,12 +159,15 @@ public class DatastoreTransactionManager implements TransactionManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T> ImmutableList<T> load(Iterable<VKey<T>> keys) {
|
public <T> ImmutableMap<VKey<? extends T>, T> load(Iterable<? extends VKey<? extends T>> keys) {
|
||||||
Iterator<Key<T>> iter =
|
// Keep track of the Key -> VKey mapping so we can translate them back.
|
||||||
StreamSupport.stream(keys.spliterator(), false).map(VKey::getOfyKey).iterator();
|
ImmutableMap<Key<T>, VKey<? extends T>> keyMap =
|
||||||
|
StreamSupport.stream(keys.spliterator(), false)
|
||||||
|
.distinct()
|
||||||
|
.collect(toImmutableMap(key -> (Key<T>) key.getOfyKey(), Functions.identity()));
|
||||||
|
|
||||||
// The lambda argument to keys() effectively converts Iterator -> Iterable.
|
return getOfy().load().keys(keyMap.keySet()).entrySet().stream()
|
||||||
return ImmutableList.copyOf(getOfy().load().keys(() -> iter).values());
|
.collect(ImmutableMap.toImmutableMap(entry -> keyMap.get(entry.getKey()), Entry::getValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -15,18 +15,21 @@
|
||||||
package google.registry.persistence.transaction;
|
package google.registry.persistence.transaction;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkArgument;
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
import static com.google.common.collect.ImmutableMap.toImmutableMap;
|
||||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||||
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||||
|
import static java.util.AbstractMap.SimpleEntry;
|
||||||
import static java.util.stream.Collectors.joining;
|
import static java.util.stream.Collectors.joining;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableCollection;
|
import com.google.common.collect.ImmutableCollection;
|
||||||
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.flogger.FluentLogger;
|
import com.google.common.flogger.FluentLogger;
|
||||||
import google.registry.persistence.VKey;
|
import google.registry.persistence.VKey;
|
||||||
import google.registry.util.Clock;
|
import google.registry.util.Clock;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.Map.Entry;
|
||||||
import java.util.NoSuchElementException;
|
import java.util.NoSuchElementException;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
@ -253,20 +256,18 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T> ImmutableList<T> load(Iterable<VKey<T>> keys) {
|
public <T> ImmutableMap<VKey<? extends T>, T> load(Iterable<? extends VKey<? extends T>> keys) {
|
||||||
checkArgumentNotNull(keys, "keys must be specified");
|
checkArgumentNotNull(keys, "keys must be specified");
|
||||||
assertInTransaction();
|
assertInTransaction();
|
||||||
return StreamSupport.stream(keys.spliterator(), false)
|
return StreamSupport.stream(keys.spliterator(), false)
|
||||||
|
// Accept duplicate keys.
|
||||||
|
.distinct()
|
||||||
.map(
|
.map(
|
||||||
key -> {
|
key ->
|
||||||
T entity = getEntityManager().find(key.getKind(), key.getSqlKey());
|
new SimpleEntry<VKey<? extends T>, T>(
|
||||||
if (entity == null) {
|
key, getEntityManager().find(key.getKind(), key.getSqlKey())))
|
||||||
throw new NoSuchElementException(
|
.filter(entry -> entry.getValue() != null)
|
||||||
key.getKind().getName() + " with key " + key.getSqlKey() + " not found.");
|
.collect(toImmutableMap(Entry::getKey, Entry::getValue));
|
||||||
}
|
|
||||||
return entity;
|
|
||||||
})
|
|
||||||
.collect(toImmutableList());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -16,6 +16,7 @@ package google.registry.persistence.transaction;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableCollection;
|
import com.google.common.collect.ImmutableCollection;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
import google.registry.persistence.VKey;
|
import google.registry.persistence.VKey;
|
||||||
import java.util.NoSuchElementException;
|
import java.util.NoSuchElementException;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
@ -119,7 +120,7 @@ public interface TransactionManager {
|
||||||
*
|
*
|
||||||
* @throws NoSuchElementException if any of the keys are not found.
|
* @throws NoSuchElementException if any of the keys are not found.
|
||||||
*/
|
*/
|
||||||
<T> ImmutableList<T> load(Iterable<VKey<T>> keys);
|
<T> ImmutableMap<VKey<? extends T>, T> load(Iterable<? extends VKey<? extends T>> keys);
|
||||||
|
|
||||||
/** Loads all entities of the given type, returns empty if there is no such entity. */
|
/** Loads all entities of the given type, returns empty if there is no such entity. */
|
||||||
<T> ImmutableList<T> loadAll(Class<T> clazz);
|
<T> ImmutableList<T> loadAll(Class<T> clazz);
|
||||||
|
|
|
@ -342,7 +342,7 @@ public class RdapJsonFormatter {
|
||||||
// Kick off the database loads of the nameservers that we will need, so it can load
|
// Kick off the database loads of the nameservers that we will need, so it can load
|
||||||
// asynchronously while we load and process the contacts.
|
// asynchronously while we load and process the contacts.
|
||||||
ImmutableSet<HostResource> loadedHosts =
|
ImmutableSet<HostResource> loadedHosts =
|
||||||
ImmutableSet.copyOf(tm().load(domainBase.getNameservers()));
|
ImmutableSet.copyOf(tm().load(domainBase.getNameservers()).values());
|
||||||
// Load the registrant and other contacts and add them to the data.
|
// Load the registrant and other contacts and add them to the data.
|
||||||
Map<Key<ContactResource>, ContactResource> loadedContacts =
|
Map<Key<ContactResource>, ContactResource> loadedContacts =
|
||||||
ofy()
|
ofy()
|
||||||
|
|
|
@ -149,7 +149,7 @@ final class UniformRapidSuspensionCommand extends MutatingEppToolCommand {
|
||||||
|
|
||||||
private ImmutableSortedSet<String> getExistingNameservers(DomainBase domain) {
|
private ImmutableSortedSet<String> getExistingNameservers(DomainBase domain) {
|
||||||
ImmutableSortedSet.Builder<String> nameservers = ImmutableSortedSet.naturalOrder();
|
ImmutableSortedSet.Builder<String> nameservers = ImmutableSortedSet.naturalOrder();
|
||||||
for (HostResource host : tm().load(domain.getNameservers())) {
|
for (HostResource host : tm().load(domain.getNameservers()).values()) {
|
||||||
nameservers.add(host.getForeignKey());
|
nameservers.add(host.getForeignKey());
|
||||||
}
|
}
|
||||||
return nameservers.build();
|
return nameservers.build();
|
||||||
|
|
|
@ -214,7 +214,7 @@ public class GenerateZoneFilesAction implements Runnable, JsonActionRunner.JsonA
|
||||||
private void emitForSubordinateHosts(DomainBase domain) {
|
private void emitForSubordinateHosts(DomainBase domain) {
|
||||||
ImmutableSet<String> subordinateHosts = domain.getSubordinateHosts();
|
ImmutableSet<String> subordinateHosts = domain.getSubordinateHosts();
|
||||||
if (!subordinateHosts.isEmpty()) {
|
if (!subordinateHosts.isEmpty()) {
|
||||||
for (HostResource unprojectedHost : tm().load(domain.getNameservers())) {
|
for (HostResource unprojectedHost : tm().load(domain.getNameservers()).values()) {
|
||||||
HostResource host = loadAtPointInTime(unprojectedHost, exportTime).now();
|
HostResource host = loadAtPointInTime(unprojectedHost, exportTime).now();
|
||||||
// A null means the host was deleted (or not created) at this time.
|
// A null means the host was deleted (or not created) at this time.
|
||||||
if ((host != null) && subordinateHosts.contains(host.getHostName())) {
|
if ((host != null) && subordinateHosts.contains(host.getHostName())) {
|
||||||
|
@ -283,7 +283,7 @@ public class GenerateZoneFilesAction implements Runnable, JsonActionRunner.JsonA
|
||||||
Duration dnsDefaultDsTtl) {
|
Duration dnsDefaultDsTtl) {
|
||||||
StringBuilder result = new StringBuilder();
|
StringBuilder result = new StringBuilder();
|
||||||
String domainLabel = stripTld(domain.getDomainName(), domain.getTld());
|
String domainLabel = stripTld(domain.getDomainName(), domain.getTld());
|
||||||
for (HostResource nameserver : tm().load(domain.getNameservers())) {
|
for (HostResource nameserver : tm().load(domain.getNameservers()).values()) {
|
||||||
result.append(
|
result.append(
|
||||||
String.format(
|
String.format(
|
||||||
NS_FORMAT,
|
NS_FORMAT,
|
||||||
|
|
|
@ -21,6 +21,7 @@ import static google.registry.persistence.transaction.TransactionManagerFactory.
|
||||||
import static org.junit.Assert.assertThrows;
|
import static org.junit.Assert.assertThrows;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
import com.googlecode.objectify.Key;
|
import com.googlecode.objectify.Key;
|
||||||
import com.googlecode.objectify.annotation.Entity;
|
import com.googlecode.objectify.annotation.Entity;
|
||||||
import com.googlecode.objectify.annotation.Id;
|
import com.googlecode.objectify.annotation.Id;
|
||||||
|
@ -35,6 +36,7 @@ import google.registry.testing.InjectRule;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.NoSuchElementException;
|
import java.util.NoSuchElementException;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.stream.Stream;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.TestTemplate;
|
import org.junit.jupiter.api.TestTemplate;
|
||||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||||
|
@ -272,6 +274,40 @@ public class TransactionManagerTest {
|
||||||
assertAllEntitiesNotExist(moreEntities);
|
assertAllEntitiesNotExist(moreEntities);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestTemplate
|
||||||
|
void load_multi() {
|
||||||
|
assertAllEntitiesNotExist(moreEntities);
|
||||||
|
tm().transact(() -> tm().saveAllNew(moreEntities));
|
||||||
|
List<VKey<TestEntity>> keys =
|
||||||
|
moreEntities.stream().map(TestEntity::key).collect(toImmutableList());
|
||||||
|
assertThat(tm().transact(() -> tm().load(keys)))
|
||||||
|
.isEqualTo(Maps.uniqueIndex(moreEntities, TestEntity::key));
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestTemplate
|
||||||
|
void load_multiWithDuplicateKeys() {
|
||||||
|
assertAllEntitiesNotExist(moreEntities);
|
||||||
|
tm().transact(() -> tm().saveAllNew(moreEntities));
|
||||||
|
ImmutableList<VKey<TestEntity>> keys =
|
||||||
|
moreEntities.stream().map(TestEntity::key).collect(toImmutableList());
|
||||||
|
ImmutableList<VKey<TestEntity>> doubleKeys =
|
||||||
|
Stream.concat(keys.stream(), keys.stream()).collect(toImmutableList());
|
||||||
|
assertThat(tm().transact(() -> tm().load(doubleKeys)))
|
||||||
|
.isEqualTo(Maps.uniqueIndex(moreEntities, TestEntity::key));
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestTemplate
|
||||||
|
void load_multiMissingKeys() {
|
||||||
|
assertAllEntitiesNotExist(moreEntities);
|
||||||
|
tm().transact(() -> tm().saveAllNew(moreEntities));
|
||||||
|
List<VKey<TestEntity>> keys =
|
||||||
|
Stream.concat(moreEntities.stream(), Stream.of(new TestEntity("dark", "matter")))
|
||||||
|
.map(TestEntity::key)
|
||||||
|
.collect(toImmutableList());
|
||||||
|
assertThat(tm().transact(() -> tm().load(keys)))
|
||||||
|
.isEqualTo(Maps.uniqueIndex(moreEntities, TestEntity::key));
|
||||||
|
}
|
||||||
|
|
||||||
private static void assertEntityExists(TestEntity entity) {
|
private static void assertEntityExists(TestEntity entity) {
|
||||||
assertThat(tm().transact(() -> tm().checkExists(entity))).isTrue();
|
assertThat(tm().transact(() -> tm().checkExists(entity))).isTrue();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue