Drop ThrowawayTransactions if database queues over 500 transactions.

This should reduce the likelihood of out of memory crashes due to slow database write performance.

Affects issues:
- Close #1963
This commit is contained in:
Aurora Lahtela 2022-05-26 08:40:01 +03:00
parent a1d53b8910
commit 24e955e428
2 changed files with 40 additions and 16 deletions

View File

@ -26,6 +26,7 @@ import com.djrapitops.plan.settings.config.paths.TimeSettings;
import com.djrapitops.plan.settings.locale.Locale;
import com.djrapitops.plan.settings.locale.lang.PluginLang;
import com.djrapitops.plan.storage.database.queries.Query;
import com.djrapitops.plan.storage.database.transactions.ThrowawayTransaction;
import com.djrapitops.plan.storage.database.transactions.Transaction;
import com.djrapitops.plan.storage.database.transactions.init.CreateIndexTransaction;
import com.djrapitops.plan.storage.database.transactions.init.CreateTablesTransaction;
@ -54,6 +55,7 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.function.Supplier;
@ -85,6 +87,8 @@ public abstract class SQLDB extends AbstractDatabase {
private Supplier<ExecutorService> transactionExecutorServiceProvider;
private ExecutorService transactionExecutor;
private final AtomicInteger transactionQueueSize = new AtomicInteger(0);
private final AtomicBoolean dropUnimportantTransactions = new AtomicBoolean(false);
private final AtomicBoolean ranIntoFatalError = new AtomicBoolean(false);
protected SQLDB(
@ -291,17 +295,10 @@ public abstract class SQLDB extends AbstractDatabase {
}
private void unloadDriverClassloader() {
// Unloading class loader causes issues when reloading.
// Unloading class loader using close() causes issues when reloading.
// It is better to leak this memory than crash the plugin on reload.
// try {
// if (driverClassLoader instanceof IsolatedClassLoader) {
// ((IsolatedClassLoader) driverClassLoader).close();
// }
driverClassLoader = null;
// } catch (IOException e) {
// errorLogger.error(e, ErrorContext.builder().build());
// }
}
public abstract Connection getConnection() throws SQLException;
@ -322,15 +319,38 @@ public abstract class SQLDB extends AbstractDatabase {
Exception origin = new Exception();
return CompletableFuture.supplyAsync(() -> {
accessLock.checkAccess(transaction);
if (!ranIntoFatalError.get()) {
transaction.executeTransaction(this);
}
if (determineIfShouldDropUnimportantTransactions(transactionQueueSize.incrementAndGet())
&& transaction instanceof ThrowawayTransaction) {
// Drop throwaway transaction immediately.
return CompletableFuture.completedFuture(null);
}
return CompletableFuture.supplyAsync(() -> {
try {
accessLock.checkAccess(transaction);
if (!ranIntoFatalError.get()) {
transaction.executeTransaction(this);
}
return CompletableFuture.completedFuture(null);
} finally {
transactionQueueSize.decrementAndGet();
}
}, getTransactionExecutor()).exceptionally(errorHandler(transaction, origin));
}
private boolean determineIfShouldDropUnimportantTransactions(int queueSize) {
boolean dropTransactions = dropUnimportantTransactions.get();
if (queueSize >= 500 && !dropTransactions) {
logger.warn("Database can't keep up with transactions (Queue size: " + queueSize + "), dropping some unimportant transactions from execution.");
dropUnimportantTransactions.set(true);
return true;
} else if (queueSize < 50 && dropTransactions) {
dropUnimportantTransactions.set(false);
return false;
}
return dropTransactions;
}
private Function<Throwable, CompletableFuture<Object>> errorHandler(Transaction transaction, Exception origin) {
return throwable -> {
if (throwable == null) {
@ -399,4 +419,8 @@ public abstract class SQLDB extends AbstractDatabase {
public Locale getLocale() {
return locale;
}
public boolean shouldDropUnimportantTransactions() {
return dropUnimportantTransactions.get();
}
}

View File

@ -70,8 +70,8 @@ public abstract class Transaction {
if (db.isUnderHeavyLoad()) {
try {
Thread.sleep(db.getHeavyLoadDelayMs());
Thread.yield();
Thread.sleep(db.getHeavyLoadDelayMs());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
@ -112,7 +112,7 @@ public abstract class Transaction {
if (!db.isUnderHeavyLoad()) {
db.getLogger().warn("Database appears to be under heavy load. Dropping some unimportant transactions and adding short pauses for next 10 minutes.");
db.getRunnableFactory().create(db::assumeNoMoreHeavyLoad)
.runTaskLaterAsynchronously(TimeAmount.toTicks(10, TimeUnit.MINUTES));
.runTaskLaterAsynchronously(TimeAmount.toTicks(2, TimeUnit.MINUTES));
}
db.increaseHeavyLoadDelay();
executeTransaction(db); // Recurse to attempt again.
@ -267,7 +267,7 @@ public abstract class Transaction {
}
public boolean dbIsNotUnderHeavyLoad() {
return !db.isUnderHeavyLoad();
return !db.isUnderHeavyLoad() && !db.shouldDropUnimportantTransactions();
}
public String getName() {