perf: improve monitor query performance

This commit is contained in:
moonrailgun 2024-04-24 00:33:30 +08:00
parent 89fed4666d
commit f5c13cb02f
10 changed files with 136 additions and 32 deletions

View File

@ -1,7 +1,13 @@
import { createTRPCReact, getQueryKey } from '@trpc/react-query'; import { createTRPCReact, getQueryKey } from '@trpc/react-query';
import type { inferRouterInputs, inferRouterOutputs } from '../../server/trpc'; import type { inferRouterInputs, inferRouterOutputs } from '../../server/trpc';
import type { AppRouter } from '../../server/trpc/routers'; import type { AppRouter } from '../../server/trpc/routers';
import { httpBatchLink, loggerLink, TRPCClientErrorLike } from '@trpc/client'; import {
httpBatchLink,
httpLink,
loggerLink,
splitLink,
TRPCClientErrorLike,
} from '@trpc/client';
import { getJWT } from './auth'; import { getJWT } from './auth';
import { message } from 'antd'; import { message } from 'antd';
import { isDev } from '../utils/env'; import { isDev } from '../utils/env';
@ -13,6 +19,14 @@ export const trpc = createTRPCReact<AppRouter>();
export type AppRouterInput = inferRouterInputs<AppRouter>; export type AppRouterInput = inferRouterInputs<AppRouter>;
export type AppRouterOutput = inferRouterOutputs<AppRouter>; export type AppRouterOutput = inferRouterOutputs<AppRouter>;
const url = '/trpc';
function headers() {
return {
Authorization: `Bearer ${getJWT()}`,
};
}
export const trpcClient = trpc.createClient({ export const trpcClient = trpc.createClient({
links: [ links: [
loggerLink({ loggerLink({
@ -20,13 +34,20 @@ export const trpcClient = trpc.createClient({
(isDev && typeof window !== 'undefined') || (isDev && typeof window !== 'undefined') ||
(opts.direction === 'down' && opts.result instanceof Error), (opts.direction === 'down' && opts.result instanceof Error),
}), }),
httpBatchLink({ splitLink({
url: '/trpc', condition(op) {
async headers() { // check for context property `skipBatch`
return { return op.context.skipBatch === true;
Authorization: `Bearer ${getJWT()}`,
};
}, },
true: httpLink({
url,
headers,
}),
// when condition is false, use batching
false: httpBatchLink({
url,
headers,
}),
}), }),
], ],
}); });

View File

@ -15,10 +15,19 @@ export const MonitorDataMetrics: React.FC<{
const { t } = useTranslation(); const { t } = useTranslation();
const workspaceId = useCurrentWorkspaceId(); const workspaceId = useCurrentWorkspaceId();
const { monitorId, monitorType, currectResponse } = props; const { monitorId, monitorType, currectResponse } = props;
const { data, isLoading } = trpc.monitor.dataMetrics.useQuery({ const { data, isLoading } = trpc.monitor.dataMetrics.useQuery(
workspaceId, {
monitorId, workspaceId,
}); monitorId,
},
{
trpc: {
context: {
skipBatch: true,
},
},
}
);
const provider = useMemo( const provider = useMemo(
() => getMonitorProvider(monitorType), () => getMonitorProvider(monitorType),

View File

@ -70,6 +70,9 @@ export const MonitorInfo: React.FC<MonitorInfoProps> = React.memo((props) => {
active: true, active: true,
}); });
trpcUtils.monitor.all.refetch({
workspaceId,
});
trpcUtils.monitor.get.refetch({ trpcUtils.monitor.get.refetch({
workspaceId, workspaceId,
monitorId, monitorId,
@ -87,6 +90,9 @@ export const MonitorInfo: React.FC<MonitorInfoProps> = React.memo((props) => {
active: false, active: false,
}); });
trpcUtils.monitor.all.refetch({
workspaceId,
});
trpcUtils.monitor.get.refetch({ trpcUtils.monitor.get.refetch({
workspaceId, workspaceId,
monitorId, monitorId,

View File

@ -139,7 +139,7 @@ export const ServerList: React.FC<ServerListProps> = React.memo((props) => {
cell: (props) => dayjs(props.getValue()).format('MMM D HH:mm:ss'), cell: (props) => dayjs(props.getValue()).format('MMM D HH:mm:ss'),
}), }),
]; ];
}, []); }, [t]);
return ( return (
<div className="flex h-full flex-col"> <div className="flex h-full flex-col">

View File

@ -10,7 +10,6 @@ import {
AlertDialogTrigger, AlertDialogTrigger,
} from '@/components/ui/alert-dialog'; } from '@/components/ui/alert-dialog';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Separator } from '@/components/ui/separator'; import { Separator } from '@/components/ui/separator';
import { Switch } from '@/components/ui/switch'; import { Switch } from '@/components/ui/switch';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';

View File

@ -0,0 +1,53 @@
import { describe, bench } from 'vitest';
import { prisma } from '../_client';
const workspaceId = process.env.BENCH_MONITOR_WORKSPACEID;
const monitorId = process.env.BENCH_MONITOR_ID;
describe.runIf(workspaceId && monitorId)('getMonitorRecentData', () => {
bench('find with join', async () => {
await prisma.monitorData
.findMany({
where: {
monitor: {
id: monitorId,
workspaceId,
},
},
take: 20,
orderBy: {
createdAt: 'desc',
},
select: {
value: true,
createdAt: true,
},
})
.then((arr) => arr.reverse());
});
bench('find with double check', async () => {
await prisma.monitor.findFirstOrThrow({
where: {
workspaceId,
id: monitorId,
},
});
await prisma.monitorData
.findMany({
where: {
monitorId,
},
take: 20,
orderBy: {
createdAt: 'desc',
},
select: {
value: true,
createdAt: true,
},
})
.then((arr) => arr.reverse());
});
});

View File

@ -48,17 +48,22 @@ export function getMonitorRecentData(
monitorId: string, monitorId: string,
take: number take: number
) { ) {
return prisma.monitorData.findMany({ return prisma.monitorData
where: { .findMany({
monitor: { where: {
id: monitorId, monitor: {
workspaceId, id: monitorId,
workspaceId,
},
}, },
}, take,
take: -take, orderBy: {
select: { createdAt: 'desc',
value: true, },
createdAt: true, select: {
}, value: true,
}); createdAt: true,
},
})
.then((arr) => arr.reverse());
} }

View File

@ -0,0 +1,8 @@
-- CreateIndex
CREATE INDEX "MonitorData_monitorId_createdAt_idx" ON "MonitorData"("monitorId", "createdAt" DESC);
-- CreateIndex
CREATE INDEX "MonitorData_monitorId_createdAt_value_idx" ON "MonitorData"("monitorId", "createdAt", "value");
-- CreateIndex
CREATE INDEX "MonitorEvent_monitorId_idx" ON "MonitorEvent"("monitorId");

View File

@ -318,6 +318,8 @@ model MonitorEvent {
createdAt DateTime @default(now()) @db.Timestamptz(6) createdAt DateTime @default(now()) @db.Timestamptz(6)
monitor Monitor @relation(fields: [monitorId], references: [id], onUpdate: Cascade, onDelete: Cascade) monitor Monitor @relation(fields: [monitorId], references: [id], onUpdate: Cascade, onDelete: Cascade)
@@index([monitorId])
} }
model MonitorData { model MonitorData {
@ -329,6 +331,8 @@ model MonitorData {
monitor Monitor @relation(fields: [monitorId], references: [id], onUpdate: Cascade, onDelete: Cascade) monitor Monitor @relation(fields: [monitorId], references: [id], onUpdate: Cascade, onDelete: Cascade)
@@index([createdAt]) @@index([createdAt])
@@index([monitorId, createdAt(sort: Desc)])
@@index([monitorId, createdAt, value])
} }
// Use for record latest monitor status, for example tls status // Use for record latest monitor status, for example tls status

View File

@ -300,14 +300,13 @@ export const monitorRouter = router({
return monitor; return monitor;
}), }),
recentData: publicProcedure recentData: publicProcedure
.meta({ .meta(
openapi: { buildMonitorOpenapi({
tags: [OPENAPI_TAG.MONITOR],
protect: false,
method: 'GET', method: 'GET',
path: `/monitor/{monitorId}/recentData`, protect: false,
}, path: '/{monitorId}/recentData',
}) })
)
.input( .input(
z.object({ z.object({
workspaceId: z.string().cuid2(), workspaceId: z.string().cuid2(),