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

Turn export service into a Vercel serverless function #3095

Merged
merged 8 commits into from
Dec 30, 2020

Conversation

taneliang
Copy link
Member

@taneliang taneliang commented Dec 29, 2020

Context

Stacked on #3092. Part of #3098, work to dedockerize NUSMods to streamline our deployment processes.

Observations

  • Cold startup time can take a while (~6s?), but I think it's well worth it.

  • No docs, but I think we should just update all the docs at once go once we've removed Docker.

  • Not sure if local dev environment is working. I couldn't get vercel dev to work locally, but I think that may just be because I'm using WSL. We could just fix this in a future PR.

  • The /export project has quite a bit of code that's not used, specifically the PM2 deployment and ability to load timetable data from the filesystem. With the move to serverless, all the router stuff will also not be needed anymore. I've left all the old code untouched in case this serverless stuff doesn't work out.

  • If we do decide to keep this serverless setup for good, we'll want to consider inlining the /export project into /website. This will allow us to have a working export button in every deploy 😍

Biggest risks

  • Not sure if we'll hit any pricing limits, but I guess we'll find out once real traffic comes in with the semester starting.

Test URLs

TODO after this PR

@vercel
Copy link

vercel bot commented Dec 29, 2020

This pull request is being automatically deployed with Vercel (learn more).
To see the status of your deployments, click below or on the icon next to each commit.

nusmods-website – ./website

🔍 Inspect: https://vercel.com/nusmodifications/nusmods-website/dhkenhm8x
✅ Preview: https://nusmods-website-git-eliang-vercel-export.nusmodifications.vercel.app

nusmods-export – ./export

🔍 Inspect: https://vercel.com/nusmodifications/nusmods-export/r3ub9qji4
✅ Preview: https://nusmods-export-git-eliang-vercel-export.nusmodifications.vercel.app

@codecov
Copy link

codecov bot commented Dec 29, 2020

Codecov Report

Merging #3095 (071d895) into master (a76344a) will not change coverage.
The diff coverage is n/a.

Impacted file tree graph

@@           Coverage Diff           @@
##           master    #3095   +/-   ##
=======================================
  Coverage   55.58%   55.58%           
=======================================
  Files         255      255           
  Lines        5311     5311           
  Branches     1218     1218           
=======================================
  Hits         2952     2952           
  Misses       2359     2359           

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update a76344a...071d895. Read the comment docs.

@nusmods-deploy-bot
Copy link

nusmods-deploy-bot bot commented Dec 29, 2020

Copy link
Member

@ZhangYiJiang ZhangYiJiang left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good as a direct port, but you might want to restructure the code a bit more since the code looks like it has a lot of copy-pasted chunks from the old Koa middleware code

Comment on lines 38 to 40
const dataCandidate = JSON.parse(request.query.data as never);
validateExportData(dataCandidate);
data = dataCandidate;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is that as never cast? Isn't this the same as

data = JSON.parse(request.query.data);
validateExportData(data);

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope:

image

Figured we should cast to never instead of the ideal string type since we aren't actually sure.

Comment on lines 13 to 11
const handler: NowApiHandler = async (request, response) => {
try {
await main(request, response);
} catch (e) {
const eventId = Sentry.captureException(e.original || e);

console.error(e);

if (e instanceof HttpError) {
if (e.code === 422) {
response.status(422).send(render422());
return;
}
}
response.status(500).send(render500(eventId));
}
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should definitely be lifted to a higher order function, and we can probably deduplicate most of the two main functions - right now they are very similar

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm okay.

I originally decided against that because I thought we'll want the handler file itself to perform all the response sending and error throwing, so that it's easier to see what's sent back to the client and also to trace the thrown error and where they're caught. So almost all the duplicated code just throws errors and creates responses.

But I drafted a hof and I think it'll still be easy to trace, so let's do that:

function makeHandler<T>(
  validateData: (request: NowRequest) => T,
  performExport: (page: Page, data: T, response: NowResponse) => void,
): NowApiHandler {
  // ...
}

@@ -286,6 +291,15 @@
dependencies:
"@types/node" "*"

"@vercel/[email protected]":
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Man, vercel node bundling it's own TypeScript is super awkward, especially when it diverge from our own version. It seems they forked ts-node for some reason https://github.com/vercel/vercel/blob/master/packages/now-node/src/typescript.ts

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm this could be problematic if they use their version of TS in prod

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, thankfully they don't.

From the build log (internal-only):

...
22:02:42.582  	Using TypeScript 4.1.2 (local user-provided)
...

Comment on lines 67 to 32
if (e.message.includes('ERR_CONNECTION_REFUSED')) {
throw new HttpError(
500,
`Could not open the page located at process.env.PAGE (${url}). Try opening it in your browser?`,
e,
);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this just for local development? If so a local env check + logger log might be better

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not necessarily, if the function was wrongly configured in prod or if nusmods.com goes down for some reason this will be shown too. But it sounds like we can improve this error message so that it'll make more sense in prod

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm going to leave this message as is because I expect this to only be encountered if the function isn't set up properly. nusmods.com shouldn't ever have downtime unless Vercel goes down, and if that ever happens the serverless functions will probably be dead too.

@taneliang
Copy link
Member Author

@ZhangYiJiang Yeah, there's quite a lot of copy pasted code in this PR 😆

I think apart from the handler code duplication discussed above, the other big copy paste is the render.ts -> render-serverless.ts file. I decided to fork it so that our handlers don't import puppeteer, and also so that it'll be easy to delete if/when we decide to drop the old Koa implementation.

I think part of this quick and dirty approach is also just to deploy it to prod and see how well it functions (pun not intended, but ha), before we really clean it up for real.

Copy link
Member

@ZhangYiJiang ZhangYiJiang left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair - let's go for the MVP then and see how well this works. We should also think of some way of testing this in prod just to see if anything comes up, maybe route a small amount of traffic to this

@taneliang
Copy link
Member Author

I think we don’t need progressive rollout for this one. Since the new Vercel-hosted /website will be deployed by a DNS change, we can just change the DNS record back if export (or website) fails. Should be pretty painless. Also, yolo :D

Base automatically changed from eliang/vercel to master December 30, 2020 13:29
@taneliang
Copy link
Member Author

Rebased on master and added 071d895 (#3095).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants