My growing disdain for serverless
When I started programming, it was an era where you would just FTP PHP files over to a shared hosting server and run them there, this solution worked for most sites and small to medium applications at the time. This setup, complete with your shared hosting providers embedded MySQL database, while slow it was highly functional. Especially given the internet speeds of the time, particularly in Australia.
The Evolution of Hosting Web Applications
As time and technology advanced, we transitioned from plain old PHP to using Composer for dependencies and frameworks like CodeIgniter and Laravel, eventually moving on to other runtimes like Node.js, Ruby, and Python.
This progression naturally led to a need for dedicated servers or VPSs, adding additional complications to the hosting story. Eventually, solutions like Heroku emerged simplifying the hosting story for apps requiring long-running processes. With this things were fine for a period of time until we all collectively dove headfirst into serverless.
The Serverless Craze
With AWS Lambda, the process of running apps became reminiscent of the shared hosting days, you would deploy a set of handlers which would be executed and have the result returned in whatever format you defined (HTML, JSON, XML, etc.). This was great at first but it also introduced a new set of challenges.
While Lambda was incredibly alluring due to the promise of scaling to zero and reducing the costs of hosting your 10-user application, it also brought along with it the now dreaded issue of cold starts.
We have collectively spent years optimizing for cold starts, with providers moving from VMs to microkernels and developers improving code through techniques like bundling and tree-shaking and even swapping to newer runtimes that compile to machine code (Golang, Rust, Zig). The allure of serverless, with its infinite scaling, was something we were all tempted by especially as we moved into a unicorn era with startups growing at an unprecedented rate. Yet, it wasn't without its drawbacks.
The Return to Servers?
Despite the serverless hype, running servers traditionally began to make sense again, especially when considering the costs for known sustained workloads. This has been seen across the industry with larger exits from tooling like Lambda and Cloud Functions, and onto those like Fargate, Cloud Run, and other container runtimes.
Unfortunately, at the same time we have seen an ever-growing push for the edge which comes with its own tradeoffs and limitations that I believe are even less well understood by the newer generations of developers. While global replication bringing your application closer to its users is great, one should also understand the implications of doing so and whether you need it at your current scale.
Growing Disdain
After years of embracing serverless, I've grown increasingly frustrated with its limitations. From dealing with ever changing landscape of providers and their limitations to ensuring deferred work executes despite serverless constraints.
Unfortunately, despite what some might tell you running an application on a serverless environment isn’t as simple as you might believe. While you can initially get up and running without needing to think about the caveats of serverless you will quickly start to hit them as you encounter any kind of traffic or start implementing non-trivial features.
This includes limitations surrounding incoming request sizes (typically 5-10MB at max) meaning that you need file uploads to use a whole other provider which introduce things like pre-signed urls for security and then ACLs for access and so forth. As well as several other limitations regarding how long the runtime can execute for, limitations surrounding how large the response from the runtime can be, dangling asynchronous work needing to be cleaned up, and more.
While initially the complexity seemed to outweigh the benefits, it was also well known that at a certain scale a serverless architecture also made no sense as the costs associated with running it would far outscale that of a serverful approach with newer tooling surrounding autoscaling.
Case Study: Documenso
For the past year or so I’ve been working on Documenso, an open-source signing platform. We at Documenso host our application with Vercel which is a serverless provider backed by AWS Lambda, that said being open-source we support many different ways of hosting our application including containers, systemd services, and more.
Because of our flexibility regarding hosting, we try to ensure that any dependencies we use are agnostic which is much harder when we consider our own hosting story.
Currently, we are experiencing a bit of pain as we need to start running background jobs for tasks that can take some time to complete (sending emails, processing PDF’s, etc.). Unfortunately, a traditional pull-based message queue wouldn’t be effective as our message-pulling story would be quite unfavourable. While providers like Trigger.dev, Defer.run and more exist; these also start to introduce hard dependencies on Documenso which we would like to avoid in order to ensure greater freedom for self-hosters.
This need to work around these issues can be seen with our webhook implementation. Instead of simply firing a message onto a queue to be processed by a worker we instead need to fire a request internally to another endpoint that has a longer timeout that will then attempt to perform the work. But because we do this we need to ensure the HTTP request is sent but not awaited so we wait for an arbitrary amount of time that may or may not be correct depending on network conditions, additionally we have to sign and verify our requests to stop a bad actor from hitting the processing endpoint and causing our database and serverless bill a bunch of grief.
This project and desire to be hosting agnostic has only amplified my frustrations with serverless, what should be simple can blow out, and our application while agnostic still has a bunch of logic pertaining to where it is hosted to ensure that things don’t break making it less optimal as a whole.
While I was aware of these tradeoffs and such having many years of cloud experience, trying to keep my feet in both the serverless and serverfull ponds has honestly made me much more cynical.
Reflections and Future Directions
The more I work with serverless, the more it feels like an unnecessary reinvention of the wheel, offering complications that outweigh its supposed benefits.
While serverless does have its place for younger startups keeping costs down in addition to handling high and low throughput traffic at a moment's notice (not really if you consider that you need to request additional concurrency with most providers), my recent experiences over the past few years have led me to reconsider its value in future projects.
The simplicity and reliability of traditional server-based hosting, even if it seems a step backward, increasingly appear as the more sensible path forward for most use cases. Particularly when paying attention to the latest form of hosting solutions that simply allow for a container to be hosted reducing traditional requirements for server management.
© Lucas Smith.