Skip to content

Commit 68dd29b

Browse files
authored
refactor(server): add logs for load-based request rejection (#2972)
* fix(filter): enhance load detection logging and memory management * fix(api): refine low-memory rejection handling
1 parent bcaa5f1 commit 68dd29b

3 files changed

Lines changed: 477 additions & 20 deletions

File tree

hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/filter/LoadDetectFilter.java

Lines changed: 76 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,16 @@
1919

2020
import java.util.List;
2121
import java.util.Set;
22+
import java.util.function.BooleanSupplier;
23+
import java.util.function.LongSupplier;
2224

2325
import org.apache.hugegraph.config.HugeConfig;
2426
import org.apache.hugegraph.config.ServerOptions;
2527
import org.apache.hugegraph.define.WorkLoad;
2628
import org.apache.hugegraph.util.Bytes;
2729
import org.apache.hugegraph.util.E;
30+
import org.apache.hugegraph.util.Log;
31+
import org.slf4j.Logger;
2832

2933
import com.google.common.collect.ImmutableSet;
3034
import com.google.common.util.concurrent.RateLimiter;
@@ -43,6 +47,8 @@
4347
@PreMatching
4448
public class LoadDetectFilter implements ContainerRequestFilter {
4549

50+
private static final Logger LOG = Log.logger(LoadDetectFilter.class);
51+
4652
private static final Set<String> WHITE_API_LIST = ImmutableSet.of(
4753
"",
4854
"apis",
@@ -54,10 +60,44 @@ public class LoadDetectFilter implements ContainerRequestFilter {
5460
private static final RateLimiter GC_RATE_LIMITER =
5561
RateLimiter.create(1.0 / 30);
5662

63+
// Log at most 1 request per second to avoid too many logs when server is under heavy load
64+
private static final RateLimiter BUSY_REJECT_LOG_RATE_LIMITER =
65+
RateLimiter.create(1.0);
66+
private static final RateLimiter MEMORY_REJECT_LOG_RATE_LIMITER =
67+
RateLimiter.create(1.0);
68+
5769
@Context
5870
private jakarta.inject.Provider<HugeConfig> configProvider;
5971
@Context
6072
private jakarta.inject.Provider<WorkLoad> loadProvider;
73+
private BooleanSupplier gcTrigger = LoadDetectFilter::triggerGcIfNeeded;
74+
private BooleanSupplier busyRejectLogPermit =
75+
BUSY_REJECT_LOG_RATE_LIMITER::tryAcquire;
76+
private BooleanSupplier memoryRejectLogPermit =
77+
MEMORY_REJECT_LOG_RATE_LIMITER::tryAcquire;
78+
private LongSupplier freeMemorySupplier = LoadDetectFilter::currentFreeMemoryInMB;
79+
80+
public static boolean isWhiteAPI(ContainerRequestContext context) {
81+
List<PathSegment> segments = context.getUriInfo().getPathSegments();
82+
E.checkArgument(!segments.isEmpty(), "Invalid request uri '%s'",
83+
context.getUriInfo().getPath());
84+
String rootPath = segments.get(0).getPath();
85+
return WHITE_API_LIST.contains(rootPath);
86+
}
87+
88+
private static boolean triggerGcIfNeeded() {
89+
if (GC_RATE_LIMITER.tryAcquire(1)) {
90+
System.gc();
91+
return true;
92+
}
93+
return false;
94+
}
95+
96+
private static long currentFreeMemoryInMB() {
97+
long allocatedMem = Runtime.getRuntime().totalMemory() -
98+
Runtime.getRuntime().freeMemory();
99+
return (Runtime.getRuntime().maxMemory() - allocatedMem) / Bytes.MB;
100+
}
61101

62102
@Override
63103
public void filter(ContainerRequestContext context) {
@@ -70,20 +110,50 @@ public void filter(ContainerRequestContext context) {
70110
int maxWorkerThreads = config.get(ServerOptions.MAX_WORKER_THREADS);
71111
WorkLoad load = this.loadProvider.get();
72112
// There will be a thread doesn't work, dedicated to statistics
73-
if (load.incrementAndGet() >= maxWorkerThreads) {
113+
int currentLoad = load.incrementAndGet();
114+
if (currentLoad >= maxWorkerThreads) {
115+
if (this.busyRejectLogPermit.getAsBoolean()) {
116+
LOG.warn("Rejected request due to high worker load, method={}, path={}, " +
117+
"currentLoad={}, maxWorkerThreads={}",
118+
context.getMethod(), context.getUriInfo().getPath(),
119+
currentLoad, maxWorkerThreads);
120+
}
74121
throw new ServiceUnavailableException(String.format(
75122
"The server is too busy to process the request, " +
76123
"you can config %s to adjust it or try again later",
77124
ServerOptions.MAX_WORKER_THREADS.name()));
78125
}
79126

80127
long minFreeMemory = config.get(ServerOptions.MIN_FREE_MEMORY);
81-
long allocatedMem = Runtime.getRuntime().totalMemory() -
82-
Runtime.getRuntime().freeMemory();
83-
long presumableFreeMem = (Runtime.getRuntime().maxMemory() -
84-
allocatedMem) / Bytes.MB;
128+
long presumableFreeMem = this.freeMemorySupplier.getAsLong();
85129
if (presumableFreeMem < minFreeMemory) {
86-
gcIfNeeded();
130+
boolean gcTriggered = this.gcTrigger.getAsBoolean();
131+
if (gcTriggered) {
132+
long recheckedFreeMem = this.freeMemorySupplier.getAsLong();
133+
if (recheckedFreeMem >= minFreeMemory) {
134+
if (this.memoryRejectLogPermit.getAsBoolean()) {
135+
LOG.warn("Low free memory recovered after GC, method={}, path={}, " +
136+
"presumableFreeMemMB={}, recheckedFreeMemMB={}, " +
137+
"minFreeMemoryMB={}",
138+
context.getMethod(), context.getUriInfo().getPath(),
139+
presumableFreeMem, recheckedFreeMem, minFreeMemory);
140+
}
141+
return;
142+
}
143+
if (this.memoryRejectLogPermit.getAsBoolean()) {
144+
LOG.warn("Rejected request due to low free memory after GC, " +
145+
"method={}, path={}, presumableFreeMemMB={}, " +
146+
"recheckedFreeMemMB={}, minFreeMemoryMB={}",
147+
context.getMethod(), context.getUriInfo().getPath(),
148+
presumableFreeMem, recheckedFreeMem, minFreeMemory);
149+
}
150+
presumableFreeMem = recheckedFreeMem;
151+
} else if (this.memoryRejectLogPermit.getAsBoolean()) {
152+
LOG.warn("Rejected request due to low free memory, method={}, path={}, " +
153+
"presumableFreeMemMB={}, minFreeMemoryMB={}",
154+
context.getMethod(), context.getUriInfo().getPath(),
155+
presumableFreeMem, minFreeMemory);
156+
}
87157
throw new ServiceUnavailableException(String.format(
88158
"The server available memory %s(MB) is below than " +
89159
"threshold %s(MB) and can't process the request, " +
@@ -92,18 +162,4 @@ public void filter(ContainerRequestContext context) {
92162
ServerOptions.MIN_FREE_MEMORY.name()));
93163
}
94164
}
95-
96-
public static boolean isWhiteAPI(ContainerRequestContext context) {
97-
List<PathSegment> segments = context.getUriInfo().getPathSegments();
98-
E.checkArgument(!segments.isEmpty(), "Invalid request uri '%s'",
99-
context.getUriInfo().getPath());
100-
String rootPath = segments.get(0).getPath();
101-
return WHITE_API_LIST.contains(rootPath);
102-
}
103-
104-
private static void gcIfNeeded() {
105-
if (GC_RATE_LIMITER.tryAcquire(1)) {
106-
System.gc();
107-
}
108-
}
109165
}

hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/UnitTestSuite.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
package org.apache.hugegraph.unit;
1919

2020
import org.apache.hugegraph.core.RoleElectionStateMachineTest;
21+
import org.apache.hugegraph.unit.api.filter.LoadDetectFilterTest;
2122
import org.apache.hugegraph.unit.api.filter.PathFilterTest;
2223
import org.apache.hugegraph.unit.api.gremlin.GremlinQueryAPITest;
2324
import org.apache.hugegraph.unit.auth.HugeGraphAuthProxyTest;
@@ -80,6 +81,7 @@
8081
@RunWith(Suite.class)
8182
@Suite.SuiteClasses({
8283
/* api filter */
84+
LoadDetectFilterTest.class,
8385
PathFilterTest.class,
8486

8587
/* api gremlin */

0 commit comments

Comments
 (0)