Here at Tiny, we're always excited to talk about our products and their shiny new features. But, as with all enterprise-level software, it's not only what you can see that contributes to a product's success. There's a lot more that goes on behind the scenes to make it all work smoothly.
With the rapid speed at which technology advances and evolves, we need to review our infrastructure frequently, thinking of new, innovative ways to increase performance so there's one less thing for you and your users to worry about. We want you to know that we've got your back now and well into the future.
In this article, Tim Dettrick, one of our Senior Software Engineers, describes three problems that influenced the development of our new infrastructure (codename: Dispenser) for delivering TinyMCE on the cloud to our customers, as well as an overview of the solution.
For more information on what you can do to take full advantage of the performance benefits, check out our related article on 3 tips to improve TinyMCE performance in the cloud.
Ben Long, Developer Advocate at Tiny
The bundling problem
Our previous CDN infrastructure for Tiny Cloud (codename: Vostok) didn't start out serving TinyMCE. It originally served up one of our previous editor offerings, textbox.io, and started life in early 2015.
Vostok was a product of its day, and while 2015 might not seem so long ago, on the web it certainly is. HTTP/2 was released that year, and so was Windows 10. The latter of those is more important than it might sound, because Internet Explorer only acquired HTTP/2 support for Windows 10. So long as support for Internet Explorer remained important to Tiny, the choice to use HTTP/2 would depend on the Windows 10 adoption rate.
plugins.min.js. The rest of the assets were open source, or not particularly useful on their own, so they were served via a traditional CDN out of an S3 bucket.
tinymce.min.js wasn't so small anymore.
TinyMCE 5.0.14, in its fullest form with all the premium plugins, includes a lot of things. With PowerPaste and everything else bundled in, Vostok served tinymce.min.js as 986 kb uncompressed, or 303 kb gzipped as it traveled over the wire. Once it reached the other end, all of that had to be parsed and loaded, no matter where or how the editor was being used. It was apparent that a better solution going forward would be to deliver the core editor on its own, and deliver additional plugins separately as needed.
There was also the matter of caching, which we'll come back to later.
All said, Vostok was a reliable workhorse, just like the R7-series rockets that launched its namesake. It had got the job done, but something new was required.
The redirect problem
https://cloud.tinymce.com/5/tinymce.min.js?apiKey=<your API key>
Putting the API key in the query parameters sounds great, until you realize that to have it available for every resource, the editor would need to know about it and add it for every asset it loads. Oh, and we'd have to add that functionality to every editor version we released. To use a phrase familiar to our Australian-based engineering team: "Yeah, nah mate!"
So, how else could we provide the API key without modifying enormous amounts of editor code?
https://cdn.tiny.cloud/1/<your API key>/tinymce/5/tinymce.min.js
By putting it in the base of the URL, we'd ensure that as the editor loaded resources via relative links, every resource path requested from our servers would include the same API key.
Great…but then we'd have to change the URL for everybody currently using the cloud. Here at Tiny, we don't like breaking things for our customers, or forcing them to change their code based on our timelines. So we'd need to implement a redirect.
Redirects are not the sorts of things you do lightly for assets that might be required for a page to function. The additional latency might be fine if you're close to a server with the content, but our Australian engineering team grew up knowing what "ping time" means. The speed of light doesn't play favorites, whether you're an admiral who wants their intel or a knowledge worker who wants their editor.
So, if we had to implement a redirect in this new system we were building (codename: Dispenser), we were determined to get some extra value from it.
The caching problem
Vostok always had a problem with caching. We reduced latency and load times by using global edge-caching in front of our servers - when the editor is in the cache, you get it faster. However, we could never cache it for very long, because Vostok's response depended on what products you purchased.
If you think of Vostok's response as a function, here's what it looked like:
If you varied any of those inputs, the output would change. As you continue to add more Tiny features (changing "Set Product" in the process), we don't want you waiting long to get them. So, we were only able to cache for as long as we thought you'd tolerate waiting for "Set Product" to be updated with your new purchase.
There are only two hard things in Computer Science: cache invalidation and naming things.
At one point, possibly unaware of Phil Karlton's wisdom, the engineering team tried to handle this by selectively invalidating the global cache when "Set Product" changed. This makes some sense for intermediate caches, but not when it comes to browser caching, so it wasn't ideal. When we reviewed this in 2018, we realized our expenditure on the cache invalidation infrastructure was greater than all the rest of the serving infrastructure, while caching still remained limited to an hour. Continuing cache invalidation wasn't justifiable, so we adjusted the cache TTLs down to 15 minutes and turned off all the invalidation infrastructure.
We needed a better model.
Dispenser: Short-lived references, long-lived content
Our new CDN infrastructure, Dispenser, uses redirects to its advantage. There's always an initial
307 Temporary Redirect response when using Dispenser, with a short-lived (~10 minutes) cache TTL. That redirect has only one function: to send you to the latest version of the editor as you've specified it.
If you're using an old-style URL with a query parameter, we fix that at the same time too. Most of the time, this redirect will come from a cache not too far from you.
Following that redirect, we know that the editor version you're asking for will not be changing, so it can be cached for much longer. We do a few instrumentation tweaks for the cloud version of the core editor, so a request for
tinymce.min.js is not long-lived by most standards, but it's cached for at least an hour. Most of the time, that's good enough that the users in your region are probably getting a cache hit from an edge cache <30ms away. If they've visited your site recently, it's probably a 304.
If it is a user's first time loading that version of TinyMCE, Dispenser serves it in just 467kb uncompressed, or 177kb gzipped. That's because, instead of bundling all the plugins, you're just getting the editor. If you want PowerPaste, just include it in your plugin list, and it will be fetched on editor init just like a community plugin. There won't be a redirect, because the path already includes the resolved editor version. Dispenser does almost no bundling of files because, with HTTP/2 and its connection multiplexing, requests are cheap.
So how does this help when you purchase additional plugins, skins, or icons? Well, for any individual file you're downloading, we don't need to know everything you're entitled to.
ApiKey -> Set Product
Instead, for any given file, we just need to know if you have purchased the product you're after:
ApiKey -> Product -> Boolean
If you have some tolerance for false positives or negatives, caching whether an item is a member of a set is much more efficient than caching the entire set. When you request something you've purchased, we're happy for it to live out in the edge cache for at least a day. We don't mind providing an extra day of access to a product you have canceled. However, if you request something you haven't purchased, we assume a slower response (reminding you it hasn't been purchased) is acceptable, so we cache this for a very small time. As a result, when you purchase a plugin or theme from Tiny, your users will have it available within a few seconds.
Because of this focus on cacheable responses, you'll notice something a little unusual with Dispenser: the more users you have, the faster your editor loads. Why? Because there's a higher likelihood that the content is already in the cache, and a higher likelihood that the edge cache has a connection already open to one of our servers if it's not.
Making the most of Dispenser
It's important to note that, although Dispenser loves caching, if you make it difficult for Dispenser to cache when using TinyMCE on the cloud, then you'll get worse performance.
So, before you leave, check out our 3 tips on how to make the most of our new infrastructure.
We're always looking for more ways to improve our products and how they are delivered to you. Feel free to reach out to us at any time to discuss.