Skip to content

Commit ef49b65

Browse files
hiemanshubchrobot
andauthoredNov 3, 2022
feat: add redis caching layer, implement core, aggregates (#1483)
* feat: add redis caching layer, implement core and aggregates buckets Co-authored-by: Benjamin Chrobot <benjamin.chrobot@alum.mit.edu>
1 parent 47fa4b4 commit ef49b65

25 files changed

+829
-695
lines changed
 

‎libs/gql-schema/campaign.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ export const schema = `
108108
repliesStaleAfter: Int
109109
isAssignmentLimitedToTeams: Boolean!
110110
timezone: String
111-
createdAt: Date!
111+
createdAt: String!
112112
previewUrl: String
113113
landlinesFiltered: Boolean!
114114
externalSystem: ExternalSystem

‎package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@
141141
"material-ui-color-picker": "^1.0.1",
142142
"material-ui-datatables": "^0.18.2",
143143
"md5": "^2.2.1",
144-
"memoredis": "^1.1.1",
144+
"memoredis": "^2.0.0",
145145
"mysql": "^2.17.1",
146146
"nexmo": "^1.0.0-beta-7",
147147
"node-cron": "^2.0.3",

‎src/config.js

+14-9
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,20 @@ const validators = {
186186
"If REDISURL is set, then this will prefix keys CACHE_PREFIX, which might be useful if multiple applications use the same redis server.",
187187
default: undefined
188188
}),
189+
CACHING_BUCKETS: str({
190+
desc:
191+
"Comma seperated caching buckets to be used for memoization (other than core)",
192+
default: "core"
193+
}),
194+
// Note: redis url likely doesnt pass envalid url validation
195+
CACHING_URL: str({
196+
desc: "This enables caching using simple memoization (memoredis)",
197+
default: undefined
198+
}),
199+
CACHING_PREFIX: str({
200+
desc: "The key prefix to use for memoredis memoization (memoredis)",
201+
default: undefined
202+
}),
189203
CAMPAIGN_ID: num({
190204
desc:
191205
"Campaign ID used by dev-tools/export-query.js to identify which campaign should be exported.",
@@ -480,15 +494,6 @@ const validators = {
480494
default: 99999,
481495
isClient: true
482496
}),
483-
// Note: redis url likely doesnt pass envalid url validation
484-
MEMOREDIS_URL: str({
485-
desc: "This enables caching using simple memoization",
486-
default: undefined
487-
}),
488-
MEMOREDIS_PREFIX: str({
489-
desc: "The key prefix to use for memoredis memoization",
490-
default: undefined
491-
}),
492497
MODE: str({
493498
desc: "Server mode",
494499
choices: Object.values(ServerMode),

‎src/schema.graphql

+1-1
Original file line numberDiff line numberDiff line change
@@ -663,7 +663,7 @@ type Campaign {
663663
repliesStaleAfter: Int
664664
isAssignmentLimitedToTeams: Boolean!
665665
timezone: String
666-
createdAt: Date!
666+
createdAt: String!
667667
previewUrl: String
668668
landlinesFiltered: Boolean!
669669
externalSystem: ExternalSystem

‎src/server/api/assignment.js

+78-91
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import { sleep } from "../../lib/utils";
99
import logger from "../../logger";
1010
import { eventBus, EventType } from "../event-bus";
1111
import { selectionsByType } from "../lib/graphql";
12-
import { cacheOpts, memoizer } from "../memoredis";
1312
import { cacheableData, r } from "../models";
1413
import { sqlResolvers } from "./lib/utils";
1514

@@ -361,26 +360,25 @@ export async function allCurrentAssignmentTargets(organizationId) {
361360
return teamToCampaigns;
362361
}
363362

364-
const memoizedMyCurrentAssignmentTargets = memoizer.memoize(
365-
async ({
366-
myTeamIds,
367-
myEscalationTags,
368-
generalEnabledBit,
369-
campaignView,
370-
orgMaxRequestCount,
371-
assignmentType,
372-
organizationId
373-
}) => {
374-
const { rows: teamToCampaigns } = await r.reader.raw(
375-
/**
376-
* This query is the same as allCurrentAssignmentTargets, except
377-
* - it restricts teams to those with is_assignment_enabled = true via the where clause in team_assignment_options
378-
* - it adds all_possible_team_assignments to set up my_possible_team_assignments
379-
*
380-
* @> is the Postgresql array includes operator
381-
* ARRAY[1,2,3] @> ARRAY[1,2] is true
382-
*/
383-
`
363+
const memoizedMyCurrentAssignmentTargets = async ({
364+
myTeamIds,
365+
myEscalationTags,
366+
generalEnabledBit,
367+
campaignView,
368+
orgMaxRequestCount,
369+
assignmentType,
370+
organizationId
371+
}) => {
372+
const { rows: teamToCampaigns } = await r.reader.raw(
373+
/**
374+
* This query is the same as allCurrentAssignmentTargets, except
375+
* - it restricts teams to those with is_assignment_enabled = true via the where clause in team_assignment_options
376+
* - it adds all_possible_team_assignments to set up my_possible_team_assignments
377+
*
378+
* @> is the Postgresql array includes operator
379+
* ARRAY[1,2,3] @> ARRAY[1,2] is true
380+
*/
381+
`
384382
with needs_message_teams as (
385383
select * from team
386384
where assignment_type = 'UNSENT'
@@ -494,21 +492,19 @@ const memoizedMyCurrentAssignmentTargets = memoizer.memoize(
494492
select * from all_possible_team_assignments
495493
where enabled = true
496494
order by priority, id asc`,
497-
[myTeamIds, myTeamIds, myEscalationTags, organizationId]
498-
);
495+
[myTeamIds, myTeamIds, myEscalationTags, organizationId]
496+
);
499497

500-
const results = teamToCampaigns.map((ttc) =>
501-
Object.assign(ttc, {
502-
type: ttc.assignment_type,
503-
campaign: { id: ttc.id, title: ttc.title },
504-
count_left: 0
505-
})
506-
);
498+
const results = teamToCampaigns.map((ttc) =>
499+
Object.assign(ttc, {
500+
type: ttc.assignment_type,
501+
campaign: { id: ttc.id, title: ttc.title },
502+
count_left: 0
503+
})
504+
);
507505

508-
return results;
509-
},
510-
cacheOpts.MyCurrentAssignmentTargets
511-
);
506+
return results;
507+
};
512508

513509
export async function cachedMyCurrentAssignmentTargets(userId, organizationId) {
514510
const {
@@ -755,14 +751,11 @@ export async function myCurrentAssignmentTarget(
755751
}
756752

757753
async function notifyIfAllAssigned(organizationId, teamsAssignedTo) {
758-
const doNotification = memoizer.memoize(
759-
async ({ team }) =>
760-
request
761-
.post(config.ASSIGNMENT_COMPLETE_NOTIFICATION_URL)
762-
.timeout(30000)
763-
.send({ team }),
764-
cacheOpts.AssignmentCompleteLock
765-
);
754+
const doNotification = async ({ team }) =>
755+
request
756+
.post(config.ASSIGNMENT_COMPLETE_NOTIFICATION_URL)
757+
.timeout(30000)
758+
.send({ team });
766759

767760
if (config.ASSIGNMENT_COMPLETE_NOTIFICATION_URL) {
768761
const assignmentTargets = await allCurrentAssignmentTargets(organizationId);
@@ -1031,48 +1024,45 @@ export async function fulfillPendingRequestFor(auth0Id) {
10311024
throw new AutoassignError(`No pending request exists for ${auth0Id}`);
10321025
}
10331026

1034-
const doAssignment = memoizer.memoize(
1035-
async ({ pendingAssignmentRequestId: _ignore }) => {
1036-
const numberAssigned = await r.knex.transaction(async (trx) => {
1037-
try {
1038-
const result = await giveUserMoreTexts(
1039-
pendingAssignmentRequest.user_id,
1040-
pendingAssignmentRequest.amount,
1041-
pendingAssignmentRequest.organization_id,
1042-
pendingAssignmentRequest.preferred_team_id,
1043-
trx
1044-
);
1027+
const doAssignment = async ({ pendingAssignmentRequestId: _ignore }) => {
1028+
const numberAssigned = await r.knex.transaction(async (trx) => {
1029+
try {
1030+
const result = await giveUserMoreTexts(
1031+
pendingAssignmentRequest.user_id,
1032+
pendingAssignmentRequest.amount,
1033+
pendingAssignmentRequest.organization_id,
1034+
pendingAssignmentRequest.preferred_team_id,
1035+
trx
1036+
);
10451037

1046-
await trx("assignment_request")
1047-
.update({
1048-
status: "approved"
1049-
})
1050-
.where({ id: pendingAssignmentRequest.id });
1051-
1052-
return result;
1053-
} catch (err) {
1054-
logger.info(
1055-
`Failed to give user ${auth0Id} more texts. Marking their request as rejected. `,
1056-
err
1057-
);
1038+
await trx("assignment_request")
1039+
.update({
1040+
status: "approved"
1041+
})
1042+
.where({ id: pendingAssignmentRequest.id });
10581043

1059-
// Mark as rejected outside the transaction so it is unaffected by the rollback
1060-
await r
1061-
.knex("assignment_request")
1062-
.update({
1063-
status: "rejected"
1064-
})
1065-
.where({ id: pendingAssignmentRequest.id });
1044+
return result;
1045+
} catch (err) {
1046+
logger.info(
1047+
`Failed to give user ${auth0Id} more texts. Marking their request as rejected. `,
1048+
err
1049+
);
10661050

1067-
const isFatal = err.isFatal !== undefined ? err.isFatal : true;
1068-
throw new AutoassignError(err.message, isFatal);
1069-
}
1070-
});
1051+
// Mark as rejected outside the transaction so it is unaffected by the rollback
1052+
await r
1053+
.knex("assignment_request")
1054+
.update({
1055+
status: "rejected"
1056+
})
1057+
.where({ id: pendingAssignmentRequest.id });
10711058

1072-
return numberAssigned;
1073-
},
1074-
cacheOpts.FullfillAssignmentLock
1075-
);
1059+
const isFatal = err.isFatal !== undefined ? err.isFatal : true;
1060+
throw new AutoassignError(err.message, isFatal);
1061+
}
1062+
});
1063+
1064+
return numberAssigned;
1065+
};
10761066

10771067
return doAssignment({
10781068
pendingAssignmentRequestId: pendingAssignmentRequest.id
@@ -1182,9 +1172,9 @@ export const resolvers = {
11821172
? assignment.texter
11831173
: loaders.user.load(assignment.user_id),
11841174
campaign: async (assignment) => {
1185-
const getCampaign = memoizer.memoize(async ({ campaignId }) => {
1175+
const getCampaign = async ({ campaignId }) => {
11861176
return r.reader("campaign").where({ id: campaignId }).first("*");
1187-
}, cacheOpts.CampaignOne);
1177+
};
11881178

11891179
return getCampaign({ campaignId: assignment.campaign_id });
11901180
},
@@ -1248,15 +1238,12 @@ export const resolvers = {
12481238
return contactsQuery;
12491239
},
12501240
campaignCannedResponses: async (assignment) => {
1251-
const getCannedResponses = memoizer.memoize(
1252-
async ({ campaignId, userId }) => {
1253-
return cacheableData.cannedResponse.query({
1254-
userId: userId || "",
1255-
campaignId
1256-
});
1257-
},
1258-
cacheOpts.CampaignCannedResponses
1259-
);
1241+
const getCannedResponses = async ({ campaignId, userId }) => {
1242+
return cacheableData.cannedResponse.query({
1243+
userId: userId || "",
1244+
campaignId
1245+
});
1246+
};
12601247

12611248
return getCannedResponses({ campaignId: assignment.campaign_id });
12621249
},

0 commit comments

Comments
 (0)
Please sign in to comment.