This is an automated email from the ASF dual-hosted git repository. kturner pushed a commit to branch 2.1 in repository https://gitbox.apache.org/repos/asf/accumulo.git
The following commit(s) were added to refs/heads/2.1 by this push: new 26bc040ed1 lowers memory burden of listing external compactions in monitor (#4867) 26bc040ed1 is described below commit 26bc040ed1ace7cc216e995040e694b05d82f7ee Author: Keith Turner <ktur...@apache.org> AuthorDate: Fri Sep 13 12:59:56 2024 -0400 lowers memory burden of listing external compactions in monitor (#4867) Each request to the monitor for the list of external compactions would create a copy of the per compaction information in the o.a.a.m.r.c.RunningCompaction constructor. For many concurrent request when there are lots of external compactions running this could cause memory problems on the monitor. This commit changes the code to only create a single RunningCompaction object every 30 seconds that is used by all request. This should lower the amount of memory used as there are concurrent request or even refreshing the page really frequently. --- server/monitor/pom.xml | 4 ++ .../java/org/apache/accumulo/monitor/Monitor.java | 55 ++++++++++++++++------ .../rest/compactions/external/ECResource.java | 17 +++---- 3 files changed, 50 insertions(+), 26 deletions(-) diff --git a/server/monitor/pom.xml b/server/monitor/pom.xml index 5ca84506d8..cb2e899597 100644 --- a/server/monitor/pom.xml +++ b/server/monitor/pom.xml @@ -48,6 +48,10 @@ <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> </dependency> + <dependency> + <groupId>com.google.guava</groupId> + <artifactId>guava</artifactId> + </dependency> <dependency> <groupId>jakarta.inject</groupId> <artifactId>jakarta.inject-api</artifactId> diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/Monitor.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/Monitor.java index bc57f62ff7..23ffb5391f 100644 --- a/server/monitor/src/main/java/org/apache/accumulo/monitor/Monitor.java +++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/Monitor.java @@ -38,10 +38,10 @@ import java.util.Map.Entry; import java.util.Optional; import java.util.Set; import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Supplier; import jakarta.inject.Singleton; @@ -62,6 +62,7 @@ import org.apache.accumulo.core.manager.thrift.ManagerClientService; import org.apache.accumulo.core.manager.thrift.ManagerMonitorInfo; import org.apache.accumulo.core.master.thrift.TableInfo; import org.apache.accumulo.core.master.thrift.TabletServerStatus; +import org.apache.accumulo.core.metadata.schema.ExternalCompactionId; import org.apache.accumulo.core.metrics.MetricsInfo; import org.apache.accumulo.core.rpc.ThriftUtil; import org.apache.accumulo.core.rpc.clients.ThriftClientTypes; @@ -78,6 +79,8 @@ import org.apache.accumulo.core.util.ServerServices.Service; import org.apache.accumulo.core.util.compaction.ExternalCompactionUtil; import org.apache.accumulo.core.util.threads.Threads; import org.apache.accumulo.monitor.rest.compactions.external.ExternalCompactionInfo; +import org.apache.accumulo.monitor.rest.compactions.external.RunningCompactions; +import org.apache.accumulo.monitor.rest.compactions.external.RunningCompactorDetails; import org.apache.accumulo.monitor.util.logging.RecentLogs; import org.apache.accumulo.server.AbstractServer; import org.apache.accumulo.server.HighlyAvailableService; @@ -101,6 +104,8 @@ import org.glassfish.jersey.servlet.ServletContainer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.base.Suppliers; + /** * Serve manager statistics with an embedded web server. */ @@ -612,12 +617,23 @@ public class Monitor extends AbstractServer implements HighlyAvailableService { private final Map<HostAndPort,CompactionStats> allCompactions = new HashMap<>(); private final RecentLogs recentLogs = new RecentLogs(); private final ExternalCompactionInfo ecInfo = new ExternalCompactionInfo(); - private final Map<String,TExternalCompaction> ecRunningMap = new ConcurrentHashMap<>(); + private long scansFetchedNanos = 0L; private long compactsFetchedNanos = 0L; private long ecInfoFetchedNanos = 0L; private final long fetchTimeNanos = TimeUnit.MINUTES.toNanos(1); private final long ageOffEntriesMillis = TimeUnit.MINUTES.toMillis(15); + // When there are a large amount of external compactions running the list of external compactions + // could consume a lot of memory. The purpose of this memoizing supplier is to try to avoid + // creating the list of running external compactions in memory per web request. If multiple + // request come in around the same time they should use the same list. It is still possible to + // have multiple list in memory if one request obtains a copy and then another request comes in + // after the timeout and the supplier recomputes the list. The longer the timeout on the supplier + // is the less likely we are to have multiple list of external compactions in memory, however + // increasing the timeout will make the monitor less responsive. + private final Supplier<ExternalCompactionsSnapshot> extCompactionSnapshot = + Suppliers.memoizeWithExpiration(() -> computeExternalCompactionsSnapshot(), fetchTimeNanos, + TimeUnit.NANOSECONDS); /** * Fetch the active scans but only if fetchTimeNanos has elapsed. @@ -667,12 +683,17 @@ public class Monitor extends AbstractServer implements HighlyAvailableService { return ecInfo; } - /** - * Fetch running compactions from Compaction Coordinator. Chose not to restrict the frequency of - * user fetches since RPC calls are going to the coordinator. This allows for fine grain updates - * of external compaction progress. - */ - public synchronized Map<String,TExternalCompaction> fetchRunningInfo() { + private static class ExternalCompactionsSnapshot { + public final RunningCompactions runningCompactions; + public final Map<String,TExternalCompaction> ecRunningMap; + + private ExternalCompactionsSnapshot(Map<String,TExternalCompaction> ecRunningMap) { + this.ecRunningMap = Collections.unmodifiableMap(ecRunningMap); + this.runningCompactions = new RunningCompactions(ecRunningMap); + } + } + + private ExternalCompactionsSnapshot computeExternalCompactionsSnapshot() { if (coordinatorHost.isEmpty()) { throw new IllegalStateException(coordinatorMissingMsg); } @@ -686,16 +707,20 @@ public class Monitor extends AbstractServer implements HighlyAvailableService { throw new IllegalStateException("Unable to get running compactions from " + ccHost, e); } - ecRunningMap.clear(); - if (running.getCompactions() != null) { - ecRunningMap.putAll(running.getCompactions()); - } + return new ExternalCompactionsSnapshot(running.getCompactions()); + } - return ecRunningMap; + public RunningCompactions getRunnningCompactions() { + return extCompactionSnapshot.get().runningCompactions; } - public Map<String,TExternalCompaction> getEcRunningMap() { - return ecRunningMap; + public RunningCompactorDetails getRunningCompactorDetails(ExternalCompactionId ecid) { + TExternalCompaction extCompaction = + extCompactionSnapshot.get().ecRunningMap.get(ecid.canonical()); + if (extCompaction == null) { + return null; + } + return new RunningCompactorDetails(extCompaction); } private CompactionCoordinatorService.Client getCoordinator(HostAndPort address) { diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/rest/compactions/external/ECResource.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/rest/compactions/external/ECResource.java index c6eab1868c..72d54d70a4 100644 --- a/server/monitor/src/main/java/org/apache/accumulo/monitor/rest/compactions/external/ECResource.java +++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/rest/compactions/external/ECResource.java @@ -60,7 +60,7 @@ public class ECResource { @Path("running") @GET public RunningCompactions getRunning() { - return new RunningCompactions(monitor.fetchRunningInfo()); + return monitor.getRunnningCompactions(); } @Path("details") @@ -68,16 +68,11 @@ public class ECResource { public RunningCompactorDetails getDetails(@QueryParam("ecid") @NotNull String ecid) { // make parameter more user-friendly by ensuring the ecid prefix is present ecid = ExternalCompactionId.from(ecid).canonical(); - var ecMap = monitor.getEcRunningMap(); - var externalCompaction = ecMap.get(ecid); - if (externalCompaction == null) { - // map could be old so fetch all running compactions and try again - ecMap = monitor.fetchRunningInfo(); - externalCompaction = ecMap.get(ecid); - if (externalCompaction == null) { - throw new IllegalStateException("Failed to find details for ECID: " + ecid); - } + var runningCompactorDetails = + monitor.getRunningCompactorDetails(ExternalCompactionId.from(ecid)); + if (runningCompactorDetails == null) { + throw new IllegalStateException("Failed to find details for ECID: " + ecid); } - return new RunningCompactorDetails(externalCompaction); + return runningCompactorDetails; } }