A month or two ago, we started seeing a mysterious problem in production: every now and then, one of our Node.js web server processes supporting Sandstorm Oasis would suddenly jump to 100% CPU usage (of one core) and stay there until it was killed. The problem wasn’t an infinite loop, though: the process continued to respond to requests, just slowly. Since the process continued to respond to requests, it continued to pass health checks and was never restarted automatically. But for users assigned to that shard, the service was essentially unusable, as every action would take seconds to complete. The problem left nothing at all suspicious in the logs – other than a gap in which far fewer requests that normal were being handled. At first, the problem only struck about once a week, seemingly at random.
This kind of bug is a web developer’s worst nightmare. How do you debug something which you can only reproduce once a week, at random, with real users on the line? What could even cause a process to slow down but not stop in this way?
--perf_basic_prof_only_functions flag on the Node command-line. This flag is safe in production – it writes some data to disk over time, but we rebuild all our VMs weekly, so the files never get large enough to be a problem.
Armed with this new knowledge, we waited. Finally, after a few days, my pager went off. I shelled into the broken server, recorded a ten-second profile, restarted Node, and then downloaded the data for analysis. Upon running
perf, I was presented with this:
Well, this looks promising! Almost all the time is being spent in two C++ functions! The perf viewer makes it easy to jump directly into the disassembly:
Wow! Almost all of our CPU time is being spent on a handful of instructions. In fact, what we’re looking at here is two different inlined copies of the same C++ code:
What you are looking at is a loop that traverses a linked list trying to find the element with a particular ID. We were spending the majority of our CPU time scanning one linked list.
So, what is this code for?
The linked list in question implements a map from thread IDs to per-thread data, such as thread-local variables. Among other things, every time the process switches between fibers, the current thread is looked up in this table.
As any fresh CS grad knows, a linked list is not the ideal data structure for a lookup table – you probably want a hashtable, red-black tree, or the like. But as many more experienced engineers know, a linked list can be more efficient than those other structures in cases where the number of elements stays small. V8’s developers, as it turns out, had designed around the assumption of a fixed thread pool never containing more than a handful of threads. But node-fibers – especially as used by Meteor – doesn’t work this way. In Meteor, every concurrent operation gets its own fiber. Once a fiber completes, it is placed in a pool for reuse, but if many fibers are needed simultaneously, the pool can grow to any size. As the pool gets bigger, the linked list gets bigger, which makes fiber-switching slower, which makes the whole process permanently slower.
But our processes weren’t getting slower over time. They were getting suddenly slower all at once. One moment the process is fine, the next it is hosed. Under normal load, our servers were sitting steady at around 100 fibers – nowhere near enough to be a problem. So now we had a new mystery: What was causing these sudden spikes in fiber creation? It was around this time we started referring to the incidents as “fiber bombs”. Alas, our profiles only showed us the after-effects of a bomb having gone off; they told us nothing about how the fibers were created in the first place. So we were back to square one.
Early on the morning of September 1st, the problem became suddenly more urgent: Instead of once a week, the problem started happening approximately once an hour. Like any good production problem, this began just after midnight. After three or so iterations of “get paged, wake up, restart the process, go back to sleep”, I grudgingly accepted that this could not wait until the morning. By about 5AM I had hot-patched our servers to monitor their own fiber counts and kill themselves whenever the number went over 1000 or so. In the process, I observed that a typical “fiber bomb” created anywhere from 5,000 to 20,000 fibers – all at once.
Still, the root cause was a mystery. With the servers now managing their own restarts and the pager quieting down, I crawled back into bed.
The spikes continued to happen approximately once an hour from then on. This was actually wonderful: it meant I could now iterate on the problem 150x faster than I could before! I began manually instrumenting the codebase with a sort of poor-man’s sampling profiler that specifically sampled fiber creation, and specifically did so at times when fiber counts seemed to be spiking. This turned out not as easy as it sounds, as there were many places that would create fibers as a result of some task having been queued previously. At the time of fiber creation, the queue insertion was no longer on the stack. So, I had to instrument the queue inserts too, and so on.
Soon, I made a startling discovery: It turned out that Meteor had monkey-patched the global Promise implementation. Specifically, they had apparently decided that they wanted
.then() callbacks always to run in fibers, for convenience since most Meteor code requires that it be run in a fiber. Thus, they wrote code to intercept calls to
.then() and wrap the callback in another callback that creates a new fiber and runs the original callback inside it.
This might sound basically reasonable at first (it should be “compatible” with standard Promise semantics), but there is a problem: In code that makes heavy, idiomatic use of Promises, it is common to string together a long chain of short
.then() callbacks. As it so happens, Sandstorm itself contains a lot of Promise-based code, especially around communicating with its back-end, which it does using Cap’n Proto. Cap’n Proto’s API makes very heavy use of Promises, and does not expect to run in fibers. Thus, this code which seemingly had nothing to do with fibers was in fact the main creator of fibers in our system, creating massive quantities of totally unnecessary fibers, wasting memory and CPU time.
But even that didn’t actually explain the bombs. The way fibers work, if you start a new fiber that immediately completes, the fiber immediately goes back to the fiber pool. All of our Promise-heavy code operated in asynchronous style, therefore the callbacks would always complete immediately. So while the Promise code was needlessly starting lots of fibers, it should actually have been reusing the same Fiber object over and over again.
But there was one more wrinkle: It turns out that the V8 promise implementation itself sometimes calls
.then() recursively, passing along one callback from one promise to another. In fact, it has to do this to correctly implement the spec. But since
.then() had been monkey-patched, each time the same callback passed through another
.then() call, it received another wrapper layer spawning another fiber. In the end, one callback, when finally called, would start a fiber, which would start another fiber, which would start another fiber, and so on. Since each fiber in this chain was itself responsible for spawning the next, all the fibers would be started before any completed. If one callback managed to be wrapped 20,000 times, then you get 20,000 fibers, all at once.
I patched the Promise monkey-patch such that, after wrapping a callback, it would mark the wrapped callback object with a field like
alreadyWrapped = true. If the same callback came back to be wrapped again, the code would see this marking and avoid double-wrapping.
And just like that, the problem stopped.
Meanwhile, we’ve also filed an issue against V8, requesting that they replace their linked list with a hashtable. This wouldn’t have completely mitigated the fiber bombs, but it would have at least prevented them from permanently crippling the process.
August’s most visible change is that when new users join a Sandstorm server, some apps are installed automatically. By default, and on Oasis, users can jump into Davros, Etherpad, Rocket.Chat, and Wekan, and they can create collections using the Collections app. The server administrator can choose which apps come preinstalled for their users. We hope this helps people quickly become productive with Sandstorm!
We made some underlying technical changes this month, too. The most significant is that we migrated to Meteor 1.4, which allowed us to switch to the most recent long-term supported version of nodejs, node 4. This required some substantial upheaval behind the scenes. It also enabled a change we’ve wanted to make for a long time: users of our sandcats.io free HTTPS service now use ciphers supporting perfect forward secrecy. If you test your own sandcats-enabled server on the Qualys SSL Labs server test, you’ll see that your grade has improved from an A- to an A!
Sandstorm servers have automatic updates enabled by default, so to get these updates, you don’t have to do anything. Sandstorm checks for updates and smoothly switches to the latest code every 24 hours.
Here’s the full August changelog!
spk publishthrowing an exception due to a bug in email handling.
ETagheader, sandstorm-http-bridge will now log an error and drop it rather than throw an exception.
sandstorm uninstallshell command.
Today, we’re announcing that Sandstorm for Work is no longer in beta. Companies large and small – ourselves included – have been getting work done using Sandstorm for months. It’s time for you to join us!
Do you wish you could use web services like Google Apps, Slack, Trello, Dropbox, and others, but can’t for security, privacy, or compliance reasons? Frustrated by the setup and maintenance costs of most self-hosted solutions? Need to integrate with your corporate single-sign-on (LDAP, SAML, Active Directory) and enforce company-wide access control policies?
Sandstorm is a suite of web-based productivity software which you can deploy on your own servers with minimal effort. Any user can install the apps they need with a few clicks – like installing apps on your phone. Apps run inside secure sandboxes with single-sign-on and uniform fine-grained access control. And everything stays up-to-date automatically, so you can set it and forget it.
Sandstorm for Work is Sandstorm plus the ability to integrate with your corporate single-sign-on, priority support, and other features businesses need.
During the beta period we listened to your feedback on pricing, and we’ve decided to make some changes:
We only think you should be paying for Sandstorm if it is helping you make money. To that end:
If any of these situations describe you, tell us about it and we’ll set you up.
Sandstorm now bundles our four most-popular apps. Every user can immediately edit documents with Etherpad, create task boards with Wekan, create a chat room with Rocket.Chat, and synchronize & secure their files with Davros.
On Sandstorm, these apps can integrate in ways that aren’t possible when they run stand-alone. For example:
There are currently 61 apps and growing available on Sandstorm, and you can easily make your own. Need to run surveys, create spreadsheets, make diagrams, take notes, typeset scholarly papers, host code, publish web pages, or run wikis? We have all that, and more.
Sandstorm is the only server platform that uses fine-grained containerization, which protects you against security bugs in apps, so you can safely let your users install the apps they need, relying on Sandstorm’s automatic exploit mitigation and network isolation to keep data safe.
As always, once you’ve installed Sandstorm for Work, Sandstorm and apps will be kept up-to-date automatically, with no action needed on your part. Sandstorm is getting better every day, and your users will get those benefits without you lifting a finger.
Sandstorm for Work is 100% open source software with a thriving community. That means it will never disappear or stop working. Read more in our original announcement.
Lately, there has been a flurry of activity around decentralizing the web. Summits have been held. New projects – and companies – have been started. Having worked on this issue for several years now, we’re excited to see it becoming increasingly mainstream.
But what, exactly, are we trying to solve? Most people think it has something to do with privacy, and maybe also security. Some argue that data ownership and mobility are the most important things. Sometimes this leads decentralization projects to focus on data storage while neglecting compute. Some projects even propose that so long as storage is decentralized, it doesn’t matter where the software actually runs.
Privacy, security, ownership, and mobility are all important, but I feel there is a much more important goal that is often poorly understood:
The most important reason to decentralize is software—and developer—diversity.
By “diversity”, I mean this: Who can develop software that other people can plausibly use? Is it primarily mega-corps like Apple and Google? VC-backed startups? Or can one random person, working in their spare time, build just the right app and reach millions of people? Can a community of unfunded volunteers build an open source app that wins because it is the best? Can an employee of one company, having built a useful internal app to solve a problem they had, give (or sell) that app to another company in the same position, without a lot of hassle? Can a teenage fan of a particular video game build an app that assists players of the game, and then share that app with the rest of the game’s community?
All of the above happens regularly with mobile and desktop apps, but it is far more difficult on the web.
We live in a time when our tools are so good that a single competent application developer working weekends can create almost any web application you can imagine. However, rarely can that single developer also run a secure, scalable service and a business around it. As a result, when software is delivered as a service, the only software that is available is that which was deemed a priori to be sufficiently lucrative to interest a mega-corp like Google or a VC-backed startup. Therefore, the only software services we get come courtesy of these gatekeepers. Experimental, indie, or amateur projects are rare. Novel services serving a small niche community are rare. Services designed by people who aren’t well-represented in the tech industry are rare. Community-driven open source projects basically aren’t viable.
The only way to solve these problems is by decentralizing the software (not just the storage). Software must be provided as a package – not as a service – with each user running their own private copy. It doesn’t really matter if the user chooses to deploy to “the cloud” or to their own machine, as long as they can run any package they want. It is, however, important that the means to deploy software be accessible even to non-technical users, so that everyone can participate and developers can reach a wide audience. Deploying an app on your server must therefore be as easy as installing an app on your phone, and must be “secure by default”.
This is the focus of Sandstorm.
Starting work on a project in Sandstorm often means creating many grains, each pertaining to a different aspect of your work. In a typical project, you might have a spreadsheet, a chat room, a kanban board, and several source code repositories. Sandstorm makes it easy to share these grains with your collaborators, but until recently, you would need to share each grain to each collaborator separately — a task that could quickly become tedious. What if you want to share the entire project as a single unit?
Now that we have released the Collections app, we have a satisfying answer to that question; to share many grains at once, you add them to a collection.
A collection is a list of grains. Any collaborator with whom you share a collection gets access to all of the grains in it. On the flip side, when you remove a grain from the collection, your collaborators lose access to that grain. Moreover, when you revoke a collaborator from the collection, that (now former-) collaborator loses access to all of the collection’s grains.
And since a collection is itself a grain, sharing one works just like sharing anything else on Sandstorm, through the “Share access” button.
The fact that we have implemented collections as an app may come as a surprise, since the notion of a collection might seem fundamental enough to deserve being baked-in as a core feature of Sandstorm. However, Sandstorm bakes in an even more fundamental notion: the idea that grains can refer to and coordinate with one another. The embodiment of this idea is the powerbox, an interface — mediated and auditable by the user — through which grains can exchange capabilities.
When you click the “Add grain” button in a collection, the collection in fact initiates a powerbox request. Sandstorm then asks you, the user, to choose a grain with which to fulfill the request.
Once you select a grain, the collection receives a reference to that grain. It can then use that reference to retrieve metadata such as the grain’s icon and the the name of the app that created the grain. Crucially, it can also offer the reference to your collaborators, so that your single “Add grain” action can result in all of your collaborators receiving access to the grain. The behind-the-scenes details of how this all works can be found in our technical documentation.
Other apps can use the powerbox in the same way. You could write an alternative implementation of collections, either by starting from scratch or by forking ours. Apps whose primary purpose has nothing to do with collections can also benefit from the ability to request and offer grain references. For example, a chat room could be enhanced by the ability to embed Sandstorm-aware links to other grains. Indeed, the latest release of Rocket.Chat does just that.
Although a fast startup time and a small memory footprint are important performance goals for any Sandstorm app, they are especially important for the Collections app, as it provides such a central piece of functionality. Collections need to be lightweight so that their integration with the rest of Sandstorm can feel seamless. Our primary strategy for achieving such performance has been to develop the Collections app using the Rust programming language, interfacing directly with Sandstorm’s Cap’n Proto interfaces . Rust has worked well so far, and along the way we have produced some libraries and examples to help others also get started using it for Sandstorm app development.
Whether or not you are a developer, now is an exciting time to get involved with Sandstorm. The powerbox is still in its early stages, and the Collections app is a hint at the kinds of things it will enable. So try it out and let us know what you think!