Attempt another fix for counter decay deadlocks
This commit is contained in:
parent
a558af1038
commit
c26ab2977f
1 changed files with 46 additions and 41 deletions
|
@ -12,12 +12,15 @@ import { CounterTriggerState } from "./entities/CounterTriggerState";
|
||||||
import moment from "moment-timezone";
|
import moment from "moment-timezone";
|
||||||
import { DAYS, DBDateFormat, HOURS, MINUTES } from "../utils";
|
import { DAYS, DBDateFormat, HOURS, MINUTES } from "../utils";
|
||||||
import { connection } from "./db";
|
import { connection } from "./db";
|
||||||
|
import { Queue } from "../Queue";
|
||||||
|
|
||||||
const DELETE_UNUSED_COUNTERS_AFTER = 1 * DAYS;
|
const DELETE_UNUSED_COUNTERS_AFTER = 1 * DAYS;
|
||||||
const DELETE_UNUSED_COUNTER_TRIGGERS_AFTER = 1 * DAYS;
|
const DELETE_UNUSED_COUNTER_TRIGGERS_AFTER = 1 * DAYS;
|
||||||
|
|
||||||
const MAX_COUNTER_VALUE = 2147483647; // 2^31-1, for MySQL INT
|
const MAX_COUNTER_VALUE = 2147483647; // 2^31-1, for MySQL INT
|
||||||
|
|
||||||
|
const decayQueue = new Queue();
|
||||||
|
|
||||||
async function deleteCountersMarkedToBeDeleted(): Promise<void> {
|
async function deleteCountersMarkedToBeDeleted(): Promise<void> {
|
||||||
await getRepository(Counter)
|
await getRepository(Counter)
|
||||||
.createQueryBuilder()
|
.createQueryBuilder()
|
||||||
|
@ -158,53 +161,55 @@ export class GuildCounters extends BaseGuildRepository {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async decay(id: number, decayPeriodMs: number, decayAmount: number) {
|
decay(id: number, decayPeriodMs: number, decayAmount: number) {
|
||||||
const counter = (await this.counters.findOne({
|
return decayQueue.add(async () => {
|
||||||
where: {
|
const counter = (await this.counters.findOne({
|
||||||
id,
|
where: {
|
||||||
},
|
id,
|
||||||
}))!;
|
},
|
||||||
|
}))!;
|
||||||
|
|
||||||
const diffFromLastDecayMs = moment.utc().diff(moment.utc(counter.last_decay_at!), "ms");
|
const diffFromLastDecayMs = moment.utc().diff(moment.utc(counter.last_decay_at!), "ms");
|
||||||
if (diffFromLastDecayMs < decayPeriodMs) {
|
if (diffFromLastDecayMs < decayPeriodMs) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const decayAmountToApply = Math.round((diffFromLastDecayMs / decayPeriodMs) * decayAmount);
|
const decayAmountToApply = Math.round((diffFromLastDecayMs / decayPeriodMs) * decayAmount);
|
||||||
if (decayAmountToApply === 0) {
|
if (decayAmountToApply === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate new last_decay_at based on the rounded decay amount we applied. This makes it so that over time, the decayed amount will stay accurate, even if we round some here.
|
// Calculate new last_decay_at based on the rounded decay amount we applied. This makes it so that over time, the decayed amount will stay accurate, even if we round some here.
|
||||||
const newLastDecayDate = moment
|
const newLastDecayDate = moment
|
||||||
.utc(counter.last_decay_at)
|
.utc(counter.last_decay_at)
|
||||||
.add((decayAmountToApply / decayAmount) * decayPeriodMs, "ms")
|
.add((decayAmountToApply / decayAmount) * decayPeriodMs, "ms")
|
||||||
.format(DBDateFormat);
|
.format(DBDateFormat);
|
||||||
|
|
||||||
const rawUpdate =
|
const rawUpdate =
|
||||||
decayAmountToApply >= 0
|
decayAmountToApply >= 0
|
||||||
? `GREATEST(value - ${decayAmountToApply}, 0)`
|
? `GREATEST(value - ${decayAmountToApply}, 0)`
|
||||||
: `LEAST(value + ${Math.abs(decayAmountToApply)}, ${MAX_COUNTER_VALUE})`;
|
: `LEAST(value + ${Math.abs(decayAmountToApply)}, ${MAX_COUNTER_VALUE})`;
|
||||||
|
|
||||||
// Using an UPDATE with ORDER BY in an attempt to avoid deadlocks from simultaneous decays
|
// Using an UPDATE with ORDER BY in an attempt to avoid deadlocks from simultaneous decays
|
||||||
// Also see https://dev.mysql.com/doc/refman/8.0/en/innodb-deadlocks-handling.html
|
// Also see https://dev.mysql.com/doc/refman/8.0/en/innodb-deadlocks-handling.html
|
||||||
await this.counterValues
|
await this.counterValues
|
||||||
.createQueryBuilder("CounterValue")
|
.createQueryBuilder("CounterValue")
|
||||||
.where("counter_id = :id", { id })
|
.where("counter_id = :id", { id })
|
||||||
.orderBy("id")
|
.orderBy("id")
|
||||||
.update({
|
.update({
|
||||||
value: () => rawUpdate,
|
value: () => rawUpdate,
|
||||||
})
|
})
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
await this.counters.update(
|
await this.counters.update(
|
||||||
{
|
{
|
||||||
id,
|
id,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
last_decay_at: newLastDecayDate,
|
last_decay_at: newLastDecayDate,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async markUnusedTriggersToBeDeleted(triggerIdsToKeep: number[]) {
|
async markUnusedTriggersToBeDeleted(triggerIdsToKeep: number[]) {
|
||||||
|
|
Loading…
Add table
Reference in a new issue