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 improvements for optimal scaling #158

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions tasky/migrations/2024-11-19-090556_scaling_groups/down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
-- There should be no way back here
14 changes: 14 additions & 0 deletions tasky/migrations/2024-11-19-090556_scaling_groups/up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
CREATE TABLE group_members (
group_id INTEGER NOT NULL,
member_id INTEGER NOT NULL,
PRIMARY KEY (group_id, member_id)
);

INSERT INTO group_members (group_id, member_id)
SELECT
g.id AS group_id,
unnest(g.members) AS member_id
FROM
groups g;

ALTER TABLE groups DROP COLUMN members;
1 change: 1 addition & 0 deletions tasky/migrations/2024-11-19-091626_groups_fix/down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
-- This file should undo anything in `up.sql`
5 changes: 5 additions & 0 deletions tasky/migrations/2024-11-19-091626_groups_fix/up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
ALTER TABLE group_members
ADD CONSTRAINT fk_group_id
FOREIGN KEY (group_id)
REFERENCES groups(id)
ON DELETE CASCADE;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
-- This file should undo anything in `up.sql`
14 changes: 14 additions & 0 deletions tasky/migrations/2024-11-19-174343_scaling_assignments/up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
CREATE TABLE assignment_completions (
assignment_id INTEGER REFERENCES assignments(id) ON DELETE CASCADE,
member_id INTEGER NOT NULL,
PRIMARY KEY (assignment_Id, member_id)
);

INSERT INTO assignment_completions (assignment_id, member_id)
SELECT
a.id AS assignment_id,
unnest(a.completed_by) AS member_id
FROM
assignments a;

ALTER TABLE assignments DROP COLUMN completed_by;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
-- This file should undo anything in `up.sql`
14 changes: 14 additions & 0 deletions tasky/migrations/2024-11-19-211502_scalable_notifications/up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
CREATE TABLE notification_targets (
notification_id INTEGER NOT NULL REFERENCES notifications(id),
user_id INTEGER NOT NULL,
PRIMARY KEY (notification_id, user_id)
);

INSERT INTO notification_targets (notification_id, user_id)
SELECT
n.id AS notification_id,
unnest(n.targeted_users) AS user_id
FROM
notifications n;

ALTER TABLE notifications DROP COLUMN targeted_users;
7 changes: 5 additions & 2 deletions tasky/src/models/assignment.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use super::notification::NotificationRepository;
use super::{Paginate, PaginatedModel, DB};
use crate::schema::assignments::dsl;
use crate::schema::{self, group_members};
use chrono::NaiveDateTime;
use diesel::associations::HasTable;
use diesel::dsl::not;
Expand Down Expand Up @@ -59,7 +60,6 @@ pub struct Assignment {
pub group_id: i32,
pub description: String,
pub language: AssignmentLanguage,
pub completed_by: Vec<Option<i32>>,
pub file_structure: Option<serde_json::Value>,
pub runner_cpu: String,
pub runner_memory: String,
Expand Down Expand Up @@ -166,7 +166,10 @@ impl AssignmentRepository {
dsl::assignments
.left_join(crate::schema::groups::table)
.left_join(crate::schema::solutions::table)
.filter(crate::schema::groups::dsl::members.contains(vec![Some(student_id)]))
.left_join(
schema::group_members::table.on(schema::groups::id.eq(group_members::group_id)),
)
.filter(group_members::dsl::member_id.eq(student_id))
.filter(not(crate::schema::solutions::dsl::submitter_id
.eq(student_id)
.and(
Expand Down
58 changes: 58 additions & 0 deletions tasky/src/models/assignment_completion.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use diesel::prelude::*;
use diesel::{
deserialize::Queryable, prelude::Insertable, BoolExpressionMethods, ExpressionMethods,
QueryDsl, Selectable,
};

use crate::schema::assignment_completions;

use super::{Paginate, PaginatedModel, DB};

#[derive(Queryable, Selectable, Clone, Insertable)]
#[diesel(primary_key(assignment_id, member_id))]
#[diesel(table_name = assignment_completions)]
#[diesel(check_for_backend(diesel::pg::Pg))]
pub struct AssignmentCompletion {
pub assignment_id: i32,
pub member_id: i32,
}

pub struct AssignmentCompletionRepository;

impl AssignmentCompletionRepository {
/// Checks whether a user has completed assignment
pub fn is_completed_by(assignment_id: i32, member_id: i32, conn: &mut DB) -> bool {
assignment_completions::dsl::assignment_completions
.filter(
assignment_completions::assignment_id
.eq(assignment_id)
.and(assignment_completions::member_id.eq(member_id)),
)
.first::<AssignmentCompletion>(conn)
.optional()
.expect("Cannot fetch is completed state")
.is_some()
}

/// Creates a new completion in the system
pub fn create_completion(comp: AssignmentCompletion, conn: &mut DB) {
diesel::insert_into(assignment_completions::table)
.values(comp)
.execute(conn)
.expect("Cannot insert assignment completion");
}

/// Gets all completion IDs for assignment
pub fn get_completion_ids_for_assignment(
assignment_id: i32,
page: i64,
conn: &mut DB,
) -> PaginatedModel<i32> {
assignment_completions::dsl::assignment_completions
.filter(assignment_completions::assignment_id.eq(assignment_id))
.select(assignment_completions::member_id)
.paginate(page)
.load_and_count_pages::<i32>(conn)
.expect("Cannot load completions")
}
}
4 changes: 2 additions & 2 deletions tasky/src/models/code_comment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ impl CodeCommentRepository {
let solution = SolutionRepository::get_solution_by_id(create.solution_id, conn).unwrap();
let group = GroupRepository::get_by_id(create.group_id, conn).unwrap();
let targeted_users = match solution.submitter_id == create.commentor {
true => vec![Some(group.tutor)],
false => vec![Some(solution.submitter_id)],
true => vec![group.tutor],
false => vec![solution.submitter_id],
};
NotificationRepository::create_notification(
&CreateNotification {
Expand Down
82 changes: 39 additions & 43 deletions tasky/src/models/group.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
use super::group_join_request::GroupJoinRequestRepository;
use super::Paginate;
use super::{PaginatedModel, DB};
use crate::schema;
use crate::schema::group_members;
use crate::schema::groups::dsl;
use chrono::NaiveDateTime;
use diesel::dsl::count_star;
use diesel::pg::Pg;
use diesel::prelude::*;
use diesel::{associations::HasTable, dsl::not};
use serde::{Deserialize, Serialize};
Expand All @@ -28,7 +27,6 @@ pub enum JoinRequestPolicy {
pub struct Group {
pub id: i32,
pub title: String,
pub members: Vec<Option<i32>>,
pub tutor: i32,
pub join_policy: JoinRequestPolicy,
pub created_at: NaiveDateTime,
Expand All @@ -42,7 +40,6 @@ pub struct Group {
pub struct CreateGroup {
pub title: String,
pub tutor: i32,
pub members: Vec<i32>,
pub join_policy: JoinRequestPolicy,
}

Expand Down Expand Up @@ -90,11 +87,13 @@ impl GroupRepository {
/// Gets all groups a user is not member or tutor of
pub fn get_groups_for_member(member_id: i32, conn: &mut DB) -> Vec<Group> {
dsl::groups
.left_join(group_members::table)
.filter(
dsl::tutor
.eq(member_id)
.or(dsl::members.contains(vec![Some(member_id)])),
.or(group_members::member_id.eq(member_id)),
)
.select(Group::as_select())
.get_results::<Group>(conn)
.expect("Cannot fetch groups for member")
}
Expand All @@ -106,11 +105,13 @@ impl GroupRepository {
conn: &mut DB,
) -> PaginatedModel<Group> {
let result = dsl::groups
.left_join(group_members::table)
.filter(
dsl::tutor
.eq(member_id)
.or(dsl::members.contains(vec![Some(member_id)])),
.or(group_members::member_id.eq(member_id)),
)
.select(Group::as_select())
.group_by((dsl::id, dsl::verified))
.order(dsl::verified.desc())
.paginate(page)
Expand Down Expand Up @@ -138,25 +139,43 @@ impl GroupRepository {
.map(|x| x.group_id)
.collect();

let total = dsl::groups
.into_boxed()
.filter(apply_search_filter(
member_id,
requested.clone(),
search.clone(),
))
.select(count_star())
.get_result::<i64>(conn)
.expect("Result cannot be fetched");
let base_predicate = not(dsl::tutor
.eq(member_id)
.or(dsl::id.eq_any(requested))
.or(group_members::dsl::member_id.eq(member_id)));

let results = dsl::groups
let total_base_query = dsl::groups
.left_join(group_members::dsl::group_members)
.select(count_star())
.filter(base_predicate.clone())
.into_boxed();

let total = match search.clone() {
None => total_base_query
.get_result::<i64>(conn)
.expect("Result cannot be fetched"),
Some(search_value) => total_base_query
.filter(dsl::title.like(format!("%{}%", search_value)))
.get_result::<i64>(conn)
.expect("Result cannot be fetched"),
};

let results_base_query = dsl::groups
.left_join(group_members::dsl::group_members)
.group_by((dsl::id, dsl::verified))
.into_boxed()
.filter(apply_search_filter(member_id, requested, search))
.select(Group::as_select())
.filter(base_predicate)
.order(dsl::verified.desc())
.limit(50)
.offset((page - 1) * 50)
.load::<Group>(conn);
.into_boxed();

let results = match search {
None => results_base_query.load::<Group>(conn),
Some(search_value) => results_base_query
.filter(dsl::title.like(format!("%{}%", search_value)))
.load::<Group>(conn),
};

if results.is_err() {
return PaginatedModel {
Expand All @@ -180,26 +199,3 @@ impl GroupRepository {
.expect("Cannot delete group");
}
}

fn apply_search_filter(
member_id: i32,
requested: Vec<i32>,
search: Option<String>,
) -> Box<dyn BoxableExpression<schema::groups::table, Pg, SqlType = diesel::sql_types::Bool>> {
let base_predicate = not(dsl::tutor
.eq(member_id)
.or(dsl::id.eq_any(requested))
.or(dsl::members.contains(vec![Some(member_id)])));
let query: Box<
dyn BoxableExpression<schema::groups::table, Pg, SqlType = diesel::sql_types::Bool>,
> = if let Some(ref search_value) = search {
Box::new(
dsl::title
.like(format!("%{}%", search_value))
.and(base_predicate),
)
} else {
Box::new(base_predicate)
};
query
}
2 changes: 1 addition & 1 deletion tasky/src/models/group_join_request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ impl GroupJoinRequestRepository {
&CreateNotification {
title: "Join request rejected".to_string(),
content: "One of your join requests has been rejected".to_string(),
targeted_users: vec![Some(req.requestor)],
targeted_users: vec![req.requestor],
},
conn,
);
Expand Down
Loading
Loading