MyFeed Proxy Performance

TL;DR

After looking at the CPU performance, the next step in MyFeed performance is to look into the proxy. Unfortunately due to CORS I have to use a proxy to allow client side JavaScript to fetch the feeds. There's two sides to the proxy, one is the connection from the app to the proxy, the other is the connection between the proxy and the RSS source.

APP <-- 1 --> PROXY <-- 2 --> RSS SOURCE

Starting with the proxy to RSS source. I have to hope that the RSS source is hosted somewhere stable, which is out of my control. As for the proxy, hosting wise it has a few things going for it.

It's hosted as a Cloudflare Worker, which due to its ephemeral nature only boots and runs the code when a request happens. Luckily it has one of the lowest cold start times of all the public cloud serverless offerings. The other nice thing is it lives in a stable environment. What I mean is it has a fast connection, probably access to 40GbE / 100GbE NICs, and its performance is relatively predictable compared to the range of devices the app side runs on.

The app uses Promise.all() to fetch the feeds concurrently, though the downside is it will wait for all requests to resolve or reject. One slow response could hold up the feed from rendering. To stop this, within the proxy I use a timeout of 1.5 seconds and Promise.race() to race against the fetch the proxy makes. If the timeout wins the Promise will reject and return a 504 Gateway Timeout response to the app, the app will ignore that feed source and continue waiting for the rest to resolve.

I haven't added this timeout race to the app itself as it's safer to add to the proxy. Reason is the proxy connection to the sources is more predictable time wise. If I moved the timeout to run on the app it would have to be adjusted based on the app or devices current connection quality. As requests over fibre would be way faster than over 3G, it makes it hard to determine a sensible timeout for different network environments.

Switching focus, measuring the app to proxy side can be done using chrome dev tools. Looking at the size of the network requests there was no difference between transferred and resource size. In other words no compression was being used between the proxy and the app.

To confirm this I added the "Content Encoding" column to dev tools network tab, this usually shows gzip or br (for brotli compression). However only some feed responses were showing br, strange...

Showing the network tab in dev tools before compression

Cloudflare usually handles compressing files that flow through it, but for some reason this wasn't happening for the feed responses. After some digging I found a post explaining that Cloudflare will only compress certain content types.

To fix this I added code to change the content-type in the response to application/xml+rss. This enabled brotli compression and the results were quite striking.

Showing the network tab in dev tools after compression

Results

When fetching a total of 32 RSS feeds.

Before: 3.5 MB of data between app and proxy.

After: 984 kB of data between app and proxy using brotli compression, saving 2.5 MB.

2.5 MB is a huge saving when dealing with mobile networks, I can definitely feel the speed increase generating the feed on mobile.

The code for the CORS proxy is available here. I will continue to work on tuning performance, looking more into the proxy to allow fan out type requests. For example, sending a single request between the app and proxy, and having the proxy fan out to all the feed sources, returning a single response to the app.

Check out the app here https://myfeed.jasongorman.uk/