Scaling SaaS Before You Need To
Every founder says they will "fix architecture later." Later usually arrives at 2,000 concurrent users during a Product Hunt spike. Scalable SaaS is not about microservices on day one—it is about boundaries you will not regret: tenant isolation, async jobs for slow work, and database indexes you add before queries hit production.
Multi-Tenancy Done Pragmatically
Shared database with tenant_id on every row works for most B2B SaaS under $10M ARR. Row-level security or scoped Eloquent global scopes prevent cross-tenant leaks—test this in CI with automated tenancy violation tests. Separate schemas or databases make sense at enterprise compliance tiers, not for your MVP.
- Cache keys namespaced by tenant:
tenant:{id}:dashboard_stats - Background jobs always carry tenant context in payload
- File storage paths partitioned by tenant ID
Async Everything That Touches Email or PDFs
Request/response cycles should finish under 300ms for core UI actions. Invoice generation, webhooks, analytics aggregation belong in queues. Laravel Horizon or BullMQ on Node—pick what matches your stack. Monitor queue depth; sustained growth above 1,000 pending jobs means you need more workers, not bigger servers.
Design idempotent webhook handlers. Stripe will retry. Your checkout.session.completed handler must tolerate duplicates without double-provisioning accounts.
Database and API Discipline
Index foreign keys and filter columns you query in list endpoints. Paginate with cursors for large datasets, not offset pagination past page 50. Version your public API from v1 even if you have one customer—breaking changes without versioning destroy integrations.
Observability From Day One
Structured JSON logs with request_id, tenant_id, and user_id make production debugging possible. Ship OpenTelemetry traces from Laravel or Node through to database queries. Alert on error rate spikes and p95 latency per endpoint—not just CPU graphs nobody reads until midnight.
Feature flags decouple deploy from release. Launch billing v2 to 5% of tenants, watch failed payment webhooks, expand gradually. Database migrations behind flags let you ship code before schema changes propagate—critical for zero-downtime column additions on large tables.
Capacity plan with load tests simulating realistic think time, not naive 100% CPU hammering. A SaaS app at 500 concurrent users with heavy reporting may need read replicas before horizontal app scaling. Measure before buying Kubernetes.
Plan data export and deletion paths for GDPR before enterprise sales asks. Tenant offboarding jobs should purge S3 objects, anonymize logs, and cascade deletes without locking tables for hours—batch in chunks with progress tracking. Compliance features become deal blockers above $50k ACV contracts.
Billing and Entitlements
Stripe webhooks are at-least-once delivery—handle duplicates idempotently with event ID storage. Entitlement checks belong server-side on every gated API call, not cached client flags hackable in browser. Plan usage metering early if pricing includes API calls or seats—retrofit metering is painful.
Load test checkout and webhook paths separately from read-heavy dashboard paths. Black Friday traffic spikes break billing before it breaks blog pages; prioritize queue isolation so payment failures do not starve authentication workers.
Circuit-break slow payment gateways so checkout thread pools do not hang entirely. Bulkhead critical paths—authentication degradation should not take down read-only marketing pages served from the same monolith without route-level timeouts and tested graceful degradation.
Frequently Asked Questions
When should we split into microservices?
When team boundaries or scaling characteristics force it—usually after 15–20 engineers or when one subsystem needs independent deploy cadence. Monoliths scale further than Twitter threads suggest.
How do we handle noisy neighbor tenants?
Rate limit per tenant at API gateway. Separate queue priorities for enterprise plans. Monitor p95 latency by tenant to spot abusers early.
What is the biggest early scaling mistake?
Synchronous third-party API calls in user-facing requests. Wrap them in jobs with status polling UI.