DEV Community

Ntty
Ntty

Posted on

Running a Multi-Tenant SaaS on a Tight Budget: Lessons from My First Year

Why I Started With a Simple Stack

I built my first SaaS product two years ago. My goal was simple: deliver a useful feature for small businesses without raising venture money. That meant every decision had to weigh cost against value. I chose a language I already knew well, kept the architecture monolithic at first, and avoided managed services that charged per request.

Choosing the Right Database

At launch I needed a relational database that could store customer data and support basic reporting. I evaluated three options:

  1. A cloud‑hosted PostgreSQL instance.
  2. A managed MySQL service.
  3. A self‑hosted SQLite file.

The hosted options offered automatic backups and scaling, but the cost quickly outpaced my revenue. SQLite was cheap but lacked concurrency. I settled on a single‑node PostgreSQL on a low‑cost VM. To keep backups cheap I scripted daily dumps to a cheap object store and rotated them weekly. The trade‑off was occasional downtime for maintenance, but it fit my budget.

Managing Tenancy Without Over‑Engineering

Multi‑tenancy can be implemented in several ways: separate schemas per tenant, a shared schema with a tenant_id column, or completely separate databases. Each adds complexity and cost.

I started with the shared schema approach. All tables have a tenant_id column, and every query is filtered by that column. To enforce the filter I wrapped the ORM session in a middleware that automatically adds the tenant condition. This kept the codebase small and avoided the overhead of managing many schemas.

When a customer grew large enough to need dedicated resources, I migrated them to their own schema. The migration script copied the data, updated the middleware, and left the rest of the tenants on the shared schema. This incremental approach let me serve dozens of small customers and a few big ones without rebuilding the whole system.

Deploying Updates Safely

Continuous deployment is tempting, but each deployment can affect all tenants. I introduced a simple version flag in the database. New code checks the flag before using a new column or feature. When I needed to add a column, I first deployed a migration that added the column but left it empty. Then I flipped the flag, and the code started writing to the column.

This two‑step process gave me a rollback window of a few minutes if something went wrong. It also let me test the new code on a single tenant before rolling it out to everyone.

Monitoring Costs and Performance

With a tight budget, I could not afford a full‑blown APM suite. Instead I built a lightweight metrics collector that scraped CPU, memory, and request latency from the VM and stored the data in a local InfluxDB instance. Alerts were sent via email when thresholds were crossed.

I also added simple request logging that counted requests per tenant. This helped me spot a few abusive customers early and throttle them before they consumed too many resources.

Concrete Takeaway

If you are building a SaaS on a shoestring, start with the simplest architecture that meets your core requirements. Use a shared schema, keep your deployment process explicit, and build cheap monitoring that gives you visibility into both performance and cost. You can always add complexity later, but shedding it later is far harder.

Final Thoughts

Running a SaaS with limited funds taught me that elegance often comes from restraint. By avoiding managed services that charge per request, by keeping tenancy logic in code rather than in separate databases, and by building a minimal monitoring stack, I was able to stay afloat for the first twelve months. The lessons I learned are not exclusive to any language or framework; they are about making trade‑offs that match your financial reality.

Takeaway: Start small, automate the boring parts, and only add complexity when your revenue can support it.

Top comments (0)