I’ve been playing around with the idea of using a CDN service to speed up the MFE pages, in the end it’s just a javascript bundle so it seems like a good target for this.
I’ve done this with edxapp simply by using cloudfront and setting the LMS as the origin server. I’ve found this approach to be pretty straightforward, I just need to configure my cloudfront distribution and add STATIC_URL = "https://d1234567890.cloudfront.net/static/" to my settings.
I tried to do something similar with the MFEs, but ran into some blockers. The approach that seemed more similar to the previous one was to use output.publicPath and set it to the CDN URL. The problem is that it doesn’t seem to be particularly easy to modify publicPath in the tutor-mfe plugin at the moment. Even then, after applying manual changes and setting ENV PUBLIC_PATH='https://d1234567890.cloudfront.net/{{ app_name }}/', I was able to load the assets from cloudfront but I encountered a blank page when accessing the app. Upon further investigation, the error seems to be related to the history object which expects and actual path instead of the full URL.
One of my teammates faced the same issue and actually opened a PR (frontend-build#398).
I was wondering what kind of deployment strategies do you use for higher traffic installations, in particular those of you using tutor. I think this approach is quite straightforward once the frontend-build changes land, you would need a new MFE image but that doesn’t seem like a big deal.
I would like to push that PR forward, maybe have a discussion at the next BTR meeting?
This looks like a great initiative, and somewhat similar to some of the work by @ghassan. Do you have some numbers you can share in terms of performance improvement?
I suggested using Cloudflare but it’s really doesn’t matter, be it, AWS Cloudfront, Fastly…etc assuming the end goal is to server the MFEs assests from a CDN.
It’s worth noting that by average a MFE assests is ~5MB, and compressed it’s ~1MB, so the effect of this on perforamance would depend on the connection speed/latency of the user on the platform and the load on the server it’s just an educated guess I haven’t tested it yet.
I think though there is a slight difference between what you are suggesting and what I had in mind, I wanted not only to enable the MFEs assests to be served through CDNs but also to simplify the workflow of an MFEs to get deployed, that is each MFE would be deployed/built by an external service and the URL of the the MFE would be unique. e.g. instead app.myopenedx.com/learning it would be learning.myopenedx.com and with this approach you no longer need to modifiy the publicPath. The only thing is that it require more configuration when setting up the platform.
Do you have some numbers you can share in terms of performance improvement?
I only ran a small test in a k8s cluster that retrieved the static files used by the learning MFE (the ones listed when you check your network tab while accessing an wrong route /learning/nogood) with X amount of simultaneous connections. Looping through these files for 1 minute with 20 connections I made 3.1k requests and by grouping the files I got a response time (p95) of 2.35s. Doing the same test with 80 connections I made 4k requests and the p95 increased up to 9s. Repeating the 80 connections using cloudfront is obviously way faster with 20k+ requests and p(95) of 700ms.
This amount of load is obviously excessive in most scenarios, but it does show degradation in the performance and using a CDN seems like an easy fix. I’m not that concerned about speeding up delivery for users in ideal conditions where their own connection is the bottleneck, but rather when my service is under heavy load (an exam for example).
What I did was set the MFE domain (apps.myopenedx.com) as the origin for my distribution, at first cloudfront would hit my server for the files but after that it will cache them and serve them to the users.
I think though there is a slight difference between what you are suggesting and what I had in mind, I wanted not only to enable the MFEs assests to be served through CDNs but also to simplify the workflow of an MFEs to get deployed, that is each MFE would be deployed/built by an external service and the URL of the the MFE would be unique.
I would like to eventually land on a more sophisticated method that allows me to have independent deployments for each MFE, especially when we are working on a single fork but have to rebuild all of them. But I think this approach offers minimal friction at the moment.
I also have a use case for hosting the MFEs on certain paths: we want to use multiple sites, and serving the MFE in lms.myopenedx.com/learning is really handy.
For some reason the header and footer load fine but the content and XHR calls to get information from the LMS APIs don’t seem to be called. I don’t see any errors in the logs files either or in the browser console.
Could you help me figure out why the gradebook MFE content won’t load. Learning, account, profile frontends load fine without error.
All the JavaScript files seem to load fine with HTTP 200 response I see information in the files.
I just have publicPath: process.env.PUBLIC_PATH || '/', in the /frontend-build/config/webpack.prod.config.js for my release and I’m guessing that this PUBLIC_PATH is the full path the Cloudfront URL.
The frontend-app-learning loads no problem with the following configuration:
I created a Lambda function that would be called on the origin request of the Cloudfront behavior and check to see if the request was a path and route to the corresponding S3 bucket directory for the given MFE app. The example below looks for /gradebook/ path and routes the request to the S3 ./gradebook/index.html page.
<Route exact> is gone. Instead, routes with descendant routes (defined in other components) use a trailing * in their path to indicate they match deeply
@MoisesGonzalezS
Did you run into issues with the gradebook MFE looking for the first parameter for courseId. We used a Cloudfront distribution that pointed to S3 origin with /gradebook files in their own directory. When calling that endpoint with https://apps.maple3.ew-dev.com/gradebook/course-v1:CA+FAA-ACS-AM-IA-ACE+DEVELOPMENT, the MFE app picked this courseId up as 'gradebook' value and broke part of the logic in the app.
The MFE container in a default tutor local install builds all the MFE apps and puts the output at /openedx/dist with a corresponding directory for each app. I also noticed that calls has this defined for the gradebook. Notice the uri strip_prefix /gradebook portion. Is this how it prevents the MFE gradebook app from keep that value in the URL?
# Caddy configuration at /etc/caddy/Caddyfile for the Gradebook MFE.
@mfe_gradebook {
path /gradebook /gradebook/*
}
handle @mfe_gradebook {
uri strip_prefix /gradebook
root * /openedx/dist/gradebook
try_files /{path} /index.html
file_server
}
I think the only other alternative is to keep each MFE in it’s own separate Cloudfront/MFE configuration. I tried putting all the built MFE files into a single Cloudfront/S3 configuration, but I seem to run into the gradebook MFE breaking because of how the URL is structured.
@MoisesGonzalezS
I was able to get Cloudfront/S3 for the MFE gradebook endpoint working like so. It may still be helpful to see how you’ve done it on your end just to confirm though.
A separate Cloudfront distribution was set up to handle the gradebook.chooseaerospace.maple3.ew-dev.com domain with SSL certificate generated by AWS Certificate Manager. For now, I’m just testing the grading MFE endpoint for one of our branded sites.
The custom error page for error 403 Forbidden response from Cloudfront will be generated after the user tries to access a gradebook MFE React route path="/:courseId" of value /course-v1:CA+FAA-ACS-AM-IA-ACE+DEVELOPMENT. The Cloudfront error page will redirect the user to the React entry page /gradebook/index.html on S3 origin and a HTTP 200 success response is returned.
Here is the page working without error concerning the <h3>course-id</h3> and link << Back to Dashboard to take the learner back to the LMS MFE for the course.