core: optimize query speed

This commit is contained in:
undefined committed 2021-09-12 05:28:38 +08:00
1 parent f5c63e02ce
commit f1e2409991
20 files changed
+116 -147

No files matched your search

+4 -12
View File
@@ -1,16 +1,8 @@
// @ts-nocheck
/*
* Why nocheck?
* BitInt requires at least ES2020, but we can't use it as
* NodeJS doesn't support some of this new features.
* e.g.: object?.property
*/
import {
STATUS, STATUS_CODES,
STATUS_TEXTS, USER_GENDER_FEMALE, USER_GENDER_ICONS, USER_GENDER_MALE, USER_GENDER_OTHER,
USER_GENDER_RANGE,
USER_GENDERS } from '@hydrooj/utils/lib/status';
STATUS, STATUS_CODES, STATUS_TEXTS,
USER_GENDER_FEMALE, USER_GENDER_ICONS, USER_GENDER_MALE,
USER_GENDER_OTHER, USER_GENDER_RANGE, USER_GENDERS,
} from '@hydrooj/utils/lib/status';
export * from '@hydrooj/utils/lib/status';
+1 -1
View File
@@ -591,7 +591,7 @@ export function count(domainId: string, query: any, type: Type = document.TYPE_C
export function getMulti<K extends Type & keyof document.DocType>(
domainId: string, query: FilterQuery<document.DocType[K]> = {}, type?: K,
) {
return document.getMulti(domainId, type || document.TYPE_CONTEST, query);
return document.getMulti(domainId, type || document.TYPE_CONTEST, query).sort({ beginAt: -1 });
}
export async function getAndListStatus(
+2 -2
View File
@@ -306,10 +306,10 @@ const t = Math.exp(-0.15);
async function updateSort() {
const cursor = document.coll.find({ docType: document.TYPE_DISCUSSION });
// eslint-disable-next-line no-await-in-loop
while (await cursor.hasNext()) {
while (true) {
// eslint-disable-next-line no-await-in-loop
const data = await cursor.next();
if (!data) return;
// eslint-disable-next-line no-await-in-loop
const rCount = await getMultiReply(data.domainId, data.docId).count();
const sort = ((data.sort || 100) + Math.max(rCount - (data.lastRCount || 0), 0) * 10) * t;
+34 -39
View File
@@ -1,11 +1,10 @@
/* eslint-disable object-curly-newline */
import assert from 'assert';
import {
Cursor, FilterQuery, ObjectID, OnlyFieldsOfType,
UpdateQuery } from 'mongodb';
Cursor, FilterQuery, ObjectID, OnlyFieldsOfType, UpdateQuery,
} from 'mongodb';
import {
Content,
DiscussionDoc, DiscussionReplyDoc, ProblemDoc, ProblemStatusDoc, Tdoc, TrainingDoc,
Content, DiscussionDoc, DiscussionReplyDoc, ProblemDoc, ProblemStatusDoc, Tdoc, TrainingDoc,
} from '../interface';
import * as bus from '../service/bus';
import db from '../service/db';
@@ -402,41 +401,37 @@ export async function revSetStatus<T extends keyof DocStatusType>(
return res.value;
}
async function ensureIndexes() {
const ic = coll.createIndex.bind(coll);
const is = collStatus.createIndex.bind(collStatus);
const u = { unique: true };
const s = { sparse: true };
await ic({ domainId: 1, docType: 1, docId: 1 }, u);
await ic({ domainId: 1, docType: 1, owner: 1, docId: -1 });
// For problem
await ic({ domainId: 1, docType: 1, search: 'text', title: 'text' }, s);
await ic({ domainId: 1, docType: 1, tag: 1, docId: 1 }, s);
await ic({ domainId: 1, docType: 1, hidden: 1, tag: 1, docId: 1 }, s);
// For problem solution
await ic({ domainId: 1, docType: 1, parentType: 1, parentId: 1, vote: -1, docId: -1 }, s);
// For discussion
await ic({ domainId: 1, docType: 1, pin: -1, updateAt: -1, docId: -1 }, s);
await ic({ domainId: 1, docType: 1, parentType: 1, parentId: 1, updateAt: -1, docId: -1 }, s);
// Hidden doc
await ic({ domainId: 1, docType: 1, hidden: 1, docId: -1 }, s);
// For contest
await ic({ domainId: 1, docType: 1, pids: 1 }, s);
await ic({ domainId: 1, docType: 1, rule: 1, docId: -1 }, s);
// For training
await ic({ domainId: 1, docType: 1, 'dag.pids': 1 }, s);
await is({ domainId: 1, docType: 1, uid: 1, docId: 1 }, u);
// For rp system
await is({ domainId: 1, docType: 1, docId: 1, status: 1, rid: 1, rp: 1 }, s);
// For contest rule OI
await is({ domainId: 1, docType: 1, docId: 1, score: -1 }, s);
// For contest rule ACM
await is({ domainId: 1, docType: 1, docId: 1, accept: -1, time: 1 }, s);
// For training
await is({ domainId: 1, docType: 1, uid: 1, enroll: 1, docId: 1 }, s);
}
bus.once('app/started', ensureIndexes);
bus.once('app/started', async () => {
await db.ensureIndexes(
coll,
{ key: { domainId: 1, docType: 1, docId: 1 }, name: 'basic', unique: true },
{ key: { domainId: 1, docType: 1, owner: 1, docId: -1 }, name: 'owner' },
// For problem
{ key: { domainId: 1, docType: 1, search: 'text', title: 'text' }, name: 'search', sparse: true },
{ key: { domainId: 1, docType: 1, tag: 1, docId: 1 }, name: 'tag', sparse: true },
{ key: { domainId: 1, docType: 1, hidden: 1, tag: 1, docId: 1 }, name: 'hidden', sparse: true },
// For problem solution
{ key: { domainId: 1, docType: 1, parentType: 1, parentId: 1, vote: -1, docId: -1 }, name: 'solution', sparse: true },
// For discussion
{ key: { domainId: 1, docType: 1, pin: -1, sort: -1, docId: -1 }, name: 'discussionSort', sparse: true },
{ key: { domainId: 1, docType: 1, parentType: 1, parentId: 1, pin: -1, sort: -1, docId: -1 }, name: 'discussionNodeSort', sparse: true },
// Hidden doc
{ key: { domainId: 1, docType: 1, hidden: 1, docId: -1 }, name: 'hiddenDoc', sparse: true },
// For contest
{ key: { domainId: 1, docType: 1, pids: 1 }, name: 'contest', sparse: true },
{ key: { domainId: 1, docType: 1, rule: 1, docId: -1 }, name: 'contestRule', sparse: true },
// For training
{ key: { domainId: 1, docType: 1, 'dag.pids': 1 }, name: 'training', sparse: true },
);
await db.ensureIndexes(
collStatus,
{ key: { domainId: 1, docType: 1, docId: 1, uid: 1 }, name: 'basic', unique: true },
{ key: { domainId: 1, docType: 1, docId: 1, status: 1, rid: 1, rp: 1 }, name: 'rp', sparse: true },
{ key: { domainId: 1, docType: 1, docId: 1, score: -1 }, name: 'contestRuleOI', sparse: true },
{ key: { domainId: 1, docType: 1, docId: 1, accept: -1, time: 1 }, name: 'contestRuleACM', sparse: true },
{ key: { domainId: 1, docType: 1, uid: 1, enroll: 1, docId: 1 }, name: 'training', sparse: true },
);
});
bus.on('domain/delete', (domainId) => Promise.all([
coll.deleteMany({ domainId }),
collStatus.deleteMany({ domainId }),
+11 -1
View File
@@ -275,6 +275,16 @@ class DomainModel {
}
}
bus.on('app/started', () => coll.createIndex({ lower: 1 }, { unique: true }));
bus.once('app/started', async () => {
await db.ensureIndexes(
coll,
{ key: { lower: 1 }, name: 'lower', unique: true },
);
await db.ensureIndexes(
collUser,
{ key: { domainId: 1, uid: 1 }, name: 'uid', unique: true },
{ key: { domainId: 1, uid: 1, rp: -1 }, name: 'rp', sparse: true },
);
});
export default DomainModel;
global.Hydro.model.domain = DomainModel;
+2 -1
View File
@@ -143,7 +143,8 @@ class RecordModel {
}
static getMulti(domainId: string, query: any) {
return RecordModel.coll.find({ domainId, ...query });
if (domainId) query = { domainId, ...query };
return RecordModel.coll.find(query);
}
static async update(
+7 -7
View File
@@ -29,7 +29,7 @@ export class StorageModel {
static async get(path: string, savePath?: string) {
const { value } = await StorageModel.coll.findOneAndUpdate(
{ path, autoDelete: { $exists: false } },
{ path, autoDelete: null },
{ $set: { lastUsage: new Date() } },
{ returnDocument: 'after' },
);
@@ -38,7 +38,7 @@ export class StorageModel {
static async rename(path: string, newPath: string) {
return await StorageModel.coll.updateOne(
{ path, autoDelete: { $exists: false } },
{ path, autoDelete: null },
{ $set: { path: newPath } },
);
}
@@ -46,7 +46,7 @@ export class StorageModel {
static async del(path: string[]) {
const autoDelete = moment().add(7, 'day').toDate();
await StorageModel.coll.updateMany(
{ path: { $in: path }, autoDelete: { $exists: false } },
{ path: { $in: path }, autoDelete: null },
{ $set: { autoDelete } },
);
}
@@ -57,11 +57,11 @@ export class StorageModel {
const results = recursive
? await StorageModel.coll.find({
path: { $regex: new RegExp(`^${escapeRegExp(target)}`, 'i') },
autoDelete: { $exists: false },
autoDelete: null,
}).toArray()
: await StorageModel.coll.find({
path: { $regex: new RegExp(`^${escapeRegExp(target)}[^/]+$`) },
autoDelete: { $exists: false },
autoDelete: null,
}).toArray();
return results.map((i) => ({
...i, name: i.path.split(target)[1], prefix: target,
@@ -70,7 +70,7 @@ export class StorageModel {
static async getMeta(path: string) {
const { value } = await StorageModel.coll.findOneAndUpdate(
{ path, autoDelete: { $exists: false } },
{ path, autoDelete: null },
{ $set: { lastUsage: new Date() } },
{ returnDocument: 'after' },
);
@@ -85,7 +85,7 @@ export class StorageModel {
static async signDownloadLink(target: string, filename?: string, noExpire = false, useAlternativeEndpointFor?: 'user' | 'judge') {
const res = await StorageModel.coll.findOneAndUpdate(
{ path: target, autoDelete: { $exists: false } },
{ path: target, autoDelete: null },
{ $set: { lastUsage: new Date() } },
);
return await storage.signDownloadLink(res.value?._id || target, filename, noExpire, useAlternativeEndpointFor);
-1
View File
@@ -93,7 +93,6 @@ class TaskModel {
static async add(task: Partial<Task> & { type: string }) {
const t: Task = {
...task,
count: task.count ?? 1,
priority: task.priority ?? 0,
executeAfter: task.executeAfter || new Date(),
_id: new ObjectID(),