As I approach the upcoming beta release of Wuvabli, I’ve been implementing solutions to keep costs low. Throughout my research, I was pleased to benefit from the composable features of swerve.
When I started working on SwizzyWeb, I built it form myself from the learnings I’ve had over the past 10 years of my career. The original feature I wanted to add was state management for SwapCacheDb, my custom database engine. But as I went on, I took some concepts I had explored around resource federation. At the time, a frontend engineer on my team was working on a proposal for module federation.
So what is module federation?
Module federation from my understanding basically boils down to a distributed registry of micro components, in this case micro frontends. So in this type of system, pages would be be composed of a collection of micro service backed frontends. The way it works is that you have a registry (pardon the terminology if not correct) of frontend components, and a pointer to their url. Then script tags are embedded into the web page, and it loads each component dynamically from the configured end point. This may or not be the true definition of module federation, but it is the approach I took with my team during a hackathon.
What’s this have to do with SwizzyWeb?
I took the concept of module federation and the “stackable” concept of combining several distributed plugable components, and applied it to the backend. In this case, npm acts as the registry, and web services can be comprised of a stack of backend microservices. Then swerve wires everything together to run it as a single node process.
So how does this save IMR?
To get a better idea of how this saves IMR (term for cost of infra), you need to understand the structure of Wuvabli. Wuvabli follows a microservice oriented architecture, so the website is actually backed by over 18 services at the time of writing, across frontend and backend packages.
Initially when setting up the infra I developed it to follow a micro service architecture, where each micro service was it’s own fargate stack. This means I had to pay for 18+ ecs instances, load balancers, nat gateways, etc. This racked up quickly and the base cost of just the wuvabli services was estimated to be ~$207 for per environment just idling. As I am working towards beta lauch, I am very conscious of the operating costs of the site, considering I am bootstrapping with only my own money.
Scalability vs Cost, it’s a 2 way door
The microservice architecture offers more flexible scaling than a monolith, allowing for scaling high load services up independently of eachother depending on load distribution. With that comes cost, but this is not the only option. With swerves stack support, I can stack multiple microservices onto the same instance. In fact, this is what I do for local testing. I have a single web-service-config.json with all dependency services configured, and they run on a single node instance.
Building off of the stack feature, I implemented a much simpler Monolith solution. This solution stacks all of the frontend web services onto a single instance, and backend stack onto another instance in a seperate security group. This cut IMR from $207 to $108, a ~50% savings. Not only that, but operations will be much easier to manage since we’re only dealing with 2 instances vs 32+. I plan on launching the beta with the monolith version.
But what if I want to switch between the two deployment types?
This was an interesting one, but not too difficult to figure out, it came down to understanding where the state lives, and how to migrate it. The state is stored in swap-cache-db, my own database engine. The database stores records on disk, so I would have to have a way to transfer this data to the new infrastructure.
EFS to the rescue
The solution to this was actually pretty easy, I would just mount an efs volume and re-attach it to the new infrastructure when I switch between microservice and monolith architectures. EFS multi attach functionality also lends itself nicely to horizontal scaling as well, just spin up a new instance and attach it to each instance in the fleet.
SwapCacheDb limitations
One caveat is that efs is a read on write based filesystem. This means that if you have an open file descriptor on the efs storage, and another instance writes to it, you need to re-read it to get the latest data before writing. Interestingly enough, SwapCacheDb has two underlying data layers that can be swapped out seemlessly. The origin data layer wrote each record as a single independent file, whereas I later added a paginated data layer that operates like a typical database, writing all records to a single db file, and an index file. In the paginated case there is a file description for writing that is opened once and re-used. In addition, the paginated data layer requires a single writer (thread) to avoid conflicts on write and potential data corruption.
The good news, though, is that the original data layer follows the proper read on write requirement to work with EFS. Although there are other complexities with the original implementation mainly related to backups as well as performance impacts of having a fd per record. In performance testing, the original data layer handled about 4-5k read TPS, whereas the paginated version got up to over 8K tps. This was run with both the instance and load generator on my laptop.
SwizzyAI Tooling
Beyond IMR savings related to infrastructure, the SwizzyAI tooling that I developed significantly reduces token utilization, development time, and correctness. Since the tooling generates almost all of the boiler plate deterministically, the AI does not have to generate the 100’s or 1000’s of lines of boilerplate or fill it’s context window. This has drastically reduced token consumption to the point where it’s actually common for me to go an entire 5 hour session without hitting my pro tier limits.
Wrapping up
Staying on top of IMR is important for swizzyweb to stay sustainable. I am bootstrapping this myself, so every dollar spent comes out of my pocket. Thankfully the Swizzy Web Service framework gave me some opportunities to reorganize my infrastructure and reduce costs, but allow me to scale out if necessary in the future.
Keep costs low, and keep it Swizzy,
J