As you may already know, the first app to be released to the Edge Network was CDN, a web service written in Node.js which predates the network in its current form, and that has been used in production for over five years by a wide range of content providers, from low traffic microsites to international publishers. It’s easy and quick to configure and comes packed a wide range of modifiers that provide comprehensive *just in time* asset manipulation, as well as CSS and JS compression. Its ability to rapidly crop, resize, compress, and reformat images and quickly serve cached resources made it ideal for an increasingly mobile consumer that expects high quality visuals with minimum delay.
When we first rolled it out into the network there was very little to change. It already had multi domain support, meaning a single instance could exist on each Host but be configured to support each domain slightly differently, with different sources, caching, and header configurations. CDN originally required its config to be stored on disk, so we added an endpoint that allowed it to be configured without a restart. If you’ve been running a Host in the last year, you’ve been successfully running a very powerful app that we are hugely proud of.
Those of you that have been paying close attention to the impact on your system may have noticed one rather glaring problem with CDN: it’s huge. Well, that’s not strictly true. It’s ~500mb on disk, which isn’t aggressively large, and certainly doesn’t affect operations when running on a single machine, however in a network where a high level of distribution is required, and where available disk space per device is relatively low, it can become a limiting factor.
When we release a new build of CDN, 390+ devices globally all need to pull a 0.5GB docker image. That’s 190GB from the registry for every release, and that just doesn’t scale. And on a 16GB machine that’s ~3% of the entire volume just for a single application. Those who have followed the more technical posts from the team will know that the network applications are primarily written in Go, and those that have been following the project since the early days may remember that the original prototype for the network was actually written in Node.js. Switching to Go wasn’t a difficult choice. It’s stricter, faster and more efficient than Node, and the resulting binary is far smaller.
Much of the logic from the Node.js build of CDN was so clearly written that the run up to rebuilding it in Go was far less daunting than we originally expected.
The Node.js build of CDN was built for a very different production environment, so migrating to Go was the perfect juncture to take stock of which features to carry over and which to drop. Here’s a short breakdown.
🔗Features to carry over
- Multiple domain configuration
- Remote file source for image
- Remove file source for CSS and JS transformation, as well as all other filetypes
🔗Features to drop
- SSL support, which is now handled at Gateway level
- S3 file source which will be replaced by Object Storage in the network
- Local filesystem source
- Redis cache which is replaced by Gateway-level caching
🔗Keeping it all production ready
We’ve built Content Distribution using the same logic as the previous version without introducing any breaking changes. Whilst the majority of users to the network are using our technology for the first time, there are a number of existing clients that have opted to migrate from their Cloud hosted CDN to the network, so it was of vital importance that the move was pain-free and invisible to the end-user.
🔗The benefits of writing in Go
🔗A single compiled binary
As mentioned earlier using Go to rebuild Content Distribution wasn’t a difficult choice, especially when we knew how significant the reduction in binary size would be. The newly released docker image is currently ~140MB, a number we intend to half over the next few releases. To put that into perspective, the Node.js build was ~568MB. This reduction in largely down to Go being a compiled language. In addition to the reduction in filesize, we are also seeing a 30-50% reduction in response times. The significance of a 100% increase in performance is a great achievement, and one we’re really proud of.
🔗Reducing potential for errors with Go
🔗Better resources utilization
Node.js uses a single CPU core so to take full advantage of a multi-core system, we rely on process multiplexers such as PM2. In Go the default application behaviour is to use all available CPU cores, and optional limits can be set with the environmental variable
Node.js has a performant event-loop which helps maximize CPU utilization, but that comes at a cost. Go on the other hand uses goroutines – simple, scaleable subroutines for concurrent and parallel workloads, without the high resource utilization.
🔗Benchmarking in Numbers
Thread Stats Avg Stdev Max +/- Stdev Latency 564.91ms 406.14ms 1.93s 70.07% Req/Sec 7.72 5.21 20.00 75.25% Latency Distribution 50% 475.04ms 75% 781.49ms 90% 1.16s 99% 1.85s 1295 requests in 30.04s, 32.77MB read Socket errors: connect 0, read 0, write 0, timeout 1 Requests/sec: 43.11 Transfer/sec: 1.09MB
Thread Stats Avg Stdev Max +/- Stdev Latency 636.30ms 415.39ms 1.99s 74.95% Req/Sec 4.28 3.67 20.00 77.07% Latency Distribution 50% 555.32ms 75% 735.90ms 90% 1.26s 99% 1.93s 519 requests in 30.04s, 55.79MB read Socket errors: connect 0, read 0, write 0, timeout 64 Requests/sec: 17.27 Transfer/sec: 1.86MB
We will be moving to use gRPC for communication between the Content Distribution app and Gateway, which will allow us to considerably increase our network performance by reducing latency and concurrent http/2 connections. We’ll also be further optimizing modifier performance and introducing new modifiers for video, audio, and live streams.