Skip to content

Commit 1e28437

Browse files
committed
cache membership in redis, cache longer as we control invalidation
1 parent 636a1a1 commit 1e28437

File tree

3 files changed

+66
-34
lines changed

3 files changed

+66
-34
lines changed

src/api/functions/membership.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import { EntraGroupError } from "common/errors/index.js";
1616
import { EntraGroupActions } from "common/types/iam.js";
1717
import { pollUntilNoError } from "./general.js";
1818

19+
export const MEMBER_CACHE_SECONDS = 43200; // 12 hours
20+
1921
export async function checkExternalMembership(
2022
netId: string,
2123
list: string,

src/api/routes/membership.ts

Lines changed: 40 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
checkPaidMembershipFromEntra,
44
checkPaidMembershipFromTable,
55
setPaidMembershipInTable,
6+
MEMBER_CACHE_SECONDS,
67
} from "api/functions/membership.js";
78
import { validateNetId } from "api/functions/validation.js";
89
import { FastifyPluginAsync } from "fastify";
@@ -26,9 +27,7 @@ import rawbody from "fastify-raw-body";
2627
import { FastifyZodOpenApiTypeProvider } from "fastify-zod-openapi";
2728
import { z } from "zod";
2829
import { withTags } from "api/components/index.js";
29-
30-
const NONMEMBER_CACHE_SECONDS = 60; // 1 minute
31-
const MEMBER_CACHE_SECONDS = 43200; // 12 hours
30+
import { getKey, setKey } from "api/functions/redisCache.js";
3231

3332
const membershipPlugin: FastifyPluginAsync = async (fastify, _options) => {
3433
await fastify.register(rawbody, {
@@ -134,11 +133,7 @@ const membershipPlugin: FastifyPluginAsync = async (fastify, _options) => {
134133
message: `${netId} is already a paid member!`,
135134
});
136135
}
137-
fastify.nodeCache.set(
138-
`isMember_${netId}`,
139-
false,
140-
NONMEMBER_CACHE_SECONDS,
141-
);
136+
fastify.nodeCache.set(`isMember_${netId}`, false, MEMBER_CACHE_SECONDS);
142137
const secretApiConfig =
143138
(await getSecretValue(
144139
fastify.secretsManagerClient,
@@ -190,11 +185,17 @@ const membershipPlugin: FastifyPluginAsync = async (fastify, _options) => {
190185
async (request, reply) => {
191186
const netId = request.params.netId.toLowerCase();
192187
const list = request.query.list || "acmpaid";
193-
if (fastify.nodeCache.get(`isMember_${netId}_${list}`) !== undefined) {
188+
const cacheKey = `membership:${netId}:${list}`;
189+
const result = await getKey<{ isMember: boolean }>({
190+
redisClient: fastify.redisClient,
191+
key: cacheKey,
192+
logger: request.log,
193+
});
194+
if (result) {
194195
return reply.header("X-ACM-Data-Source", "cache").send({
195196
netId,
196197
list: list === "acmpaid" ? undefined : list,
197-
isPaidMember: fastify.nodeCache.get(`isMember_${netId}_${list}`),
198+
isPaidMember: result.isMember,
198199
});
199200
}
200201
if (list !== "acmpaid") {
@@ -203,11 +204,13 @@ const membershipPlugin: FastifyPluginAsync = async (fastify, _options) => {
203204
list,
204205
fastify.dynamoClient,
205206
);
206-
fastify.nodeCache.set(
207-
`isMember_${netId}_${list}`,
208-
isMember,
209-
MEMBER_CACHE_SECONDS,
210-
);
207+
await setKey({
208+
redisClient: fastify.redisClient,
209+
key: cacheKey,
210+
data: JSON.stringify({ isMember }),
211+
expiresIn: MEMBER_CACHE_SECONDS,
212+
logger: request.log,
213+
});
211214
return reply.header("X-ACM-Data-Source", "dynamo").send({
212215
netId,
213216
list,
@@ -219,11 +222,13 @@ const membershipPlugin: FastifyPluginAsync = async (fastify, _options) => {
219222
fastify.dynamoClient,
220223
);
221224
if (isDynamoMember) {
222-
fastify.nodeCache.set(
223-
`isMember_${netId}_${list}`,
224-
true,
225-
MEMBER_CACHE_SECONDS,
226-
);
225+
await setKey({
226+
redisClient: fastify.redisClient,
227+
key: cacheKey,
228+
data: JSON.stringify({ isMember: true }),
229+
expiresIn: MEMBER_CACHE_SECONDS,
230+
logger: request.log,
231+
});
227232
return reply
228233
.header("X-ACM-Data-Source", "dynamo")
229234
.send({ netId, isPaidMember: true });
@@ -241,22 +246,26 @@ const membershipPlugin: FastifyPluginAsync = async (fastify, _options) => {
241246
paidMemberGroup,
242247
);
243248
if (isAadMember) {
244-
fastify.nodeCache.set(
245-
`isMember_${netId}_${list}`,
246-
true,
247-
MEMBER_CACHE_SECONDS,
248-
);
249+
await setKey({
250+
redisClient: fastify.redisClient,
251+
key: cacheKey,
252+
data: JSON.stringify({ isMember: true }),
253+
expiresIn: MEMBER_CACHE_SECONDS,
254+
logger: request.log,
255+
});
249256
reply
250257
.header("X-ACM-Data-Source", "aad")
251258
.send({ netId, isPaidMember: true });
252259
await setPaidMembershipInTable(netId, fastify.dynamoClient);
253260
return;
254261
}
255-
fastify.nodeCache.set(
256-
`isMember_${netId}_${list}`,
257-
false,
258-
NONMEMBER_CACHE_SECONDS,
259-
);
262+
await setKey({
263+
redisClient: fastify.redisClient,
264+
key: cacheKey,
265+
data: JSON.stringify({ isMember: false }),
266+
expiresIn: MEMBER_CACHE_SECONDS,
267+
logger: request.log,
268+
});
260269
return reply
261270
.header("X-ACM-Data-Source", "aad")
262271
.send({ netId, isPaidMember: false });
@@ -315,6 +324,7 @@ const membershipPlugin: FastifyPluginAsync = async (fastify, _options) => {
315324
) {
316325
const customerEmail = event.data.object.customer_email;
317326
if (!customerEmail) {
327+
request.log.info("No customer email found.");
318328
return reply
319329
.code(200)
320330
.send({ handled: false, requestId: request.id });

src/api/sqs/handlers/provisionNewMember.ts

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
import { AvailableSQSFunctions } from "common/types/sqsMessage.js";
22
import { currentEnvironmentConfig, SQSHandlerFunction } from "../index.js";
33
import { getEntraIdToken } from "../../../api/functions/entraId.js";
4-
import { genericConfig } from "../../../common/config.js";
4+
import { genericConfig, SecretConfig } from "../../../common/config.js";
55

6-
import { setPaidMembership } from "api/functions/membership.js";
6+
import {
7+
MEMBER_CACHE_SECONDS,
8+
setPaidMembership,
9+
} from "api/functions/membership.js";
710
import { createAuditLogEntry } from "api/functions/auditLog.js";
811
import { Modules } from "common/modules.js";
9-
import { getAuthorizedClients } from "../utils.js";
12+
import { getAuthorizedClients, getSecretConfig } from "../utils.js";
1013
import { emailMembershipPassHandler } from "./emailMembershipPassHandler.js";
14+
import RedisModule from "ioredis";
15+
import { setKey } from "api/functions/redisCache.js";
1116

1217
export const provisionNewMemberHandler: SQSHandlerFunction<
1318
AvailableSQSFunctions.ProvisionNewMember
@@ -21,9 +26,16 @@ export const provisionNewMemberHandler: SQSHandlerFunction<
2126
secretName: genericConfig.EntraSecretName,
2227
logger,
2328
});
29+
const secretConfig: SecretConfig = await getSecretConfig({
30+
logger,
31+
commonConfig,
32+
});
33+
const redisClient = new RedisModule.default(secretConfig.redis_url);
34+
const netId = email.replace("@illinois.edu", "");
35+
const cacheKey = `membership:${netId}:acmpaid`;
2436
logger.info("Got authorized clients and Entra ID token.");
2537
const { updated } = await setPaidMembership({
26-
netId: email.replace("@illinois.edu", ""),
38+
netId,
2739
dynamoClient: clients.dynamoClient,
2840
entraToken,
2941
paidMemberGroup: currentEnvironmentConfig.PaidMemberGroupId,
@@ -45,4 +57,12 @@ export const provisionNewMemberHandler: SQSHandlerFunction<
4557
} else {
4658
logger.info(`${email} was already a paid member.`);
4759
}
60+
logger.info("Setting membership in Redis.");
61+
await setKey({
62+
redisClient,
63+
key: cacheKey,
64+
data: JSON.stringify({ isMember: true }),
65+
expiresIn: MEMBER_CACHE_SECONDS,
66+
logger,
67+
});
4868
};

0 commit comments

Comments
 (0)