Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Performance #219

Merged
merged 6 commits into from
Nov 20, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions __tests__/root-statistics.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ function track(t: any) {
createdTasks.push(t.body.id);
}

jest.setTimeout(30000);

describe('Root statistics', function () {
test('some statistics', async () => {
const root = await global.asAdmin.post('/tasks', {
Expand Down Expand Up @@ -102,4 +104,33 @@ describe('Root statistics', function () {
}
`);
});

test('lots of statistics', async () => {
const root = await global.asAdmin.post('/tasks', {
...baseTask,
subject: 'urn:madoc:manifest:1',
});
track(root);
const rootId: string = (root.body as any).id;

// 100,000x status=1
for (let i = 0; i < 1000; i++) {
await global.asAdmin.post('/tasks', {
...baseTask,
subject: 'urn:madoc:manifest:1',
root_task: rootId,
parent_task: rootId,
status: 1,
});
}

const newList = await global.asAdmin.get(`/tasks/${rootId}?root_statistics=true`);
expect((newList.body as any).root_statistics).toEqual({
error: 0,
not_started: 0,
accepted: 1000,
progress: 0,
done: 0,
});
});
});
9 changes: 9 additions & 0 deletions migrations/2024-11-20T13-10.type-index.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
--type-index (up)

-- Create index on type column
create index tasks_type_index
on tasks (type);

-- Crete GIN index on context column
create index tasks_context_gin_index
on tasks using GIN (context jsonb_ops);
3 changes: 3 additions & 0 deletions migrations/down/2024-11-20T13-10.type-index.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
--type-index (down)
drop index if exists tasks_type_index;
drop index if exists tasks_context_gin_index;
10 changes: 5 additions & 5 deletions src/database/get-task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export async function getTask(
select t.*
from tasks t
left join tasks dt on t.delegated_task = dt.id
where t.context ?& ${sql.array(context, 'text')}
where t.context ?& ${sql.array(context, 'text')}::text[]
${userCheck}
and (t.id = ${id} or (t.parent_task = ${id} ${statusQuery} ${subjectsQuery})) order by t.created_at
`;
Expand All @@ -65,7 +65,7 @@ export async function getTask(
from task_list
where task_list.id = ${id}
union
(
(
select *
from task_list
where parent_task = ${id}
Expand All @@ -77,15 +77,15 @@ export async function getTask(
// Root statistics
const rootStats = rootStatistics
? await connection.one<Exclude<FullSingleTask['root_statistics'], undefined>>(sql`
select
select
sum((status = -1)::int) as error,
sum((status = 0)::int) as not_started,
sum((status = 1)::int) as accepted,
sum((status = 2 or status > 3)::int) as progress,
sum((status = 3)::int) as done
from tasks t
where t.context ?& ${sql.array(context, 'text')}
and t.root_task = ${id}
where t.context ?& ${sql.array(context, 'text')}::text[]
and t.root_task = ${id}
`)
: undefined;

Expand Down
6 changes: 2 additions & 4 deletions src/middleware/db-connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import { DatabasePoolType } from 'slonik';
export const dbConnection =
(pool: DatabasePoolType): Middleware =>
async (context, next) => {
await pool.connect(async (connection) => {
context.connection = connection;
await next();
});
context.connection = pool;
await next();
};
8 changes: 4 additions & 4 deletions src/routes/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ export const postEvent: RouteMiddleware<{ id: string; event: string }, { subject
events?: string[];
type: string;
}>`
SELECT t.assignee_id, t.creator_id, t.events, t.type
FROM tasks t
WHERE id = ${id}
AND context ?& ${sql.array(context.state.jwt.context, 'text')}
SELECT t.assignee_id, t.creator_id, t.events, t.type
FROM tasks t
WHERE id = ${id}
AND context ?& ${sql.array(context.state.jwt.context, 'text')}::text[]
`);

const taskWithId = { id, type, events };
Expand Down
2 changes: 1 addition & 1 deletion src/routes/export-tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export const exportTasks: RouteMiddleware = async (context) => {
}

const tasks = await context.connection.any(sql`
select * from tasks where context ?& ${sql.array(context.state.jwt.context, 'text')}
select * from tasks where context ?& ${sql.array(context.state.jwt.context, 'text')}::text[]
`);

context.response.body = { tasks };
Expand Down
6 changes: 3 additions & 3 deletions src/routes/get-all-tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,9 +138,9 @@ export const getAllTasks: RouteMiddleware = async (context) => {

try {
const countQuery = sql<{ total_items: number }>`
select COUNT(*) as total_items from tasks t
select COUNT(*) as total_items from tasks t
${dtJoin}
where t.context ?& ${sql.array(context.state.jwt.context, 'text')}
where t.context ?& ${sql.array(context.state.jwt.context, 'text')}::text[]
${subtaskExclusion}
${userExclusion}
${typeFilter}
Expand All @@ -160,7 +160,7 @@ export const getAllTasks: RouteMiddleware = async (context) => {
SELECT t.id, t.name, t.status, t.status_text, t.metadata, t.type ${detailedFields}
FROM tasks t
${dtJoin}
WHERE t.context ?& ${sql.array(context.state.jwt.context, 'text')}
WHERE t.context ?& ${sql.array(context.state.jwt.context, 'text')}::text[]
${subtaskExclusion}
${userExclusion}
${typeFilter}
Expand Down
8 changes: 5 additions & 3 deletions src/routes/get-statistics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@ export const getStatistics: RouteMiddleware<{ id?: string }> = async (context) =
const whereUser = context.query.user_id
? sql`(creator_id = ${context.query.user_id} or assignee_id = ${context.query.user_id})`
: undefined;
const whereContext = sql`context ?& ${sql.array(context.state.jwt.context, 'text')}`;
const whereContext = sql`context ?& ${sql.array(context.state.jwt.context, 'text')}::text[]`;
const counter = context.query.distinct_subjects ? sql`count(distinct subject)` : sql`count(*)`;
const fullWhere = sql.join([whereRoot, whereType, whereUser, whereContext, whereStatus].filter(Boolean) as any[], sql` and `);
const fullWhere = sql.join(
[whereRoot, whereType, whereUser, whereContext, whereStatus].filter(Boolean) as any[],
sql` and `
);
const isAdmin = context.state.jwt.scope.indexOf('tasks.admin') !== -1;
const canCreate = context.state.jwt.scope.indexOf('tasks.create') !== -1;
const groupBy = context.query.group_by;
Expand Down Expand Up @@ -51,7 +54,6 @@ export const getStatistics: RouteMiddleware<{ id?: string }> = async (context) =
);

if (returnField === 'status') {

let total = 0;
const statuses = query.reduce((state, next) => {
state[next.status] = next.total;
Expand Down
6 changes: 3 additions & 3 deletions src/routes/get-subject-statistics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,15 @@ export const getSubjectStatistics: RouteMiddleware<{ id: string }> = async (ctx)
const statusQuery = typeof status !== 'undefined' && !Number.isNaN(status) ? sql`and t.status = ${status}` : sql``;

const results = await ctx.connection.any(sql`
select t.subject, t.status ${assigneeFields} from tasks t
select t.subject, t.status ${assigneeFields} from tasks t
${parentJoin}
where ${parentTask ? sql`t.parent_task = ${taskId}` : sql`t.root_task = ${taskId}`}
where ${parentTask ? sql`t.parent_task = ${taskId}` : sql`t.root_task = ${taskId}`}
${subjectQuery}
${typeQuery}
${parentQuery}
${assignedToQuery}
${statusQuery}
and t.context ?& ${sql.array(context, 'text')}
and t.context ?& ${sql.array(context, 'text')}::text[]
`);

ctx.response.body = {
Expand Down
6 changes: 3 additions & 3 deletions src/routes/update-metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ export const updateMetadata: RouteMiddleware<{ id: string }> = async (context) =
metadata: any | null;
}>(sql`
SELECT t.id, t.metadata
FROM tasks t
WHERE t.id = ${id}
AND t.context ?& ${sql.array(context.state.jwt.context, 'text')}
FROM tasks t
WHERE t.id = ${id}
AND t.context ?& ${sql.array(context.state.jwt.context, 'text')}::text[]
`);

if (!currentTask) {
Expand Down
6 changes: 3 additions & 3 deletions src/routes/update-single-task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ export const updateSingleTask: RouteMiddleware<{ id: string }> = async (context)
type: string;
}>(sql`
SELECT t.assignee_id, t.creator_id, t.events, t.type, dt.assignee_id as delegated_assignee
FROM tasks t
FROM tasks t
left join tasks dt on t.delegated_task = dt.id
WHERE t.id = ${id}
AND t.context ?& ${sql.array(context.state.jwt.context, 'text')}
WHERE t.id = ${id}
AND t.context ?& ${sql.array(context.state.jwt.context, 'text')}::text[]
`);

const taskWithId = { id, type, events };
Expand Down
4 changes: 2 additions & 2 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { RouterParamContext } from '@koa/router';
import * as Koa from 'koa';
import { router } from './router';
import { DatabasePoolConnectionType } from 'slonik';
import { DatabasePoolType } from 'slonik';
import { Ajv } from 'ajv';
import { JobsOptions, ConnectionOptions, Queue, QueueOptions } from 'bullmq';
import { EventPrefix } from './utility/events';
Expand Down Expand Up @@ -75,7 +75,7 @@ export interface ApplicationState {

export interface ApplicationContext {
routes: typeof router;
connection: DatabasePoolConnectionType;
connection: DatabasePoolType;
getQueue?: (name: string) => Queue;
ajv: Ajv;
}
Expand Down
Loading